Compare commits
60 Commits
Author | SHA1 | Date | |
---|---|---|---|
ec69eb2dcb | |||
882daddb51 | |||
a9fd6ac114 | |||
fd17a282fa | |||
9f26b27d5d | |||
d06740aa96 | |||
da86a0931c | |||
b6d86aab30 | |||
03c74c88b9 | |||
ca6c24dee1 | |||
057ab2d886 | |||
ccbfe93765 | |||
fd43f84bd0 | |||
72da075972 | |||
2e5d3f5624 | |||
9394b361bd | |||
e9912ea87f | |||
2afc356911 | |||
4c1c96b163 | |||
478984e944 | |||
e045bd8a1e | |||
06e25df962 | |||
777f7b4c37 | |||
deabba80ca | |||
29f67fad06 | |||
c0ca073b2d | |||
de4f6b45e5 | |||
0bca8c9d02 | |||
e3e3cf9d22 | |||
2be071d215 | |||
5cd09e6c53 | |||
537d131883 | |||
a792e4ea87 | |||
7312c17f6a | |||
b0017d134f | |||
796d05925a | |||
86ab5edf4b | |||
1edeb717b1 | |||
43fe80c82e | |||
8c9c348615 | |||
32f4dc5f2a | |||
523289523d | |||
45b0657858 | |||
629369487c | |||
3c17988b24 | |||
2ae54dea64 | |||
45b1aec1d9 | |||
861f4b02d1 | |||
e91836e9bb | |||
6882d102cc | |||
6114b5b06b | |||
934ac9d4e0 | |||
429122574f | |||
25aab1d7af | |||
c054df072b | |||
96fd19b178 | |||
7ec0dea7bf | |||
d420c48f5f | |||
2c133599bd | |||
b88e3e349a |
@ -1,6 +1,4 @@
|
|||||||
{
|
{
|
||||||
"HomepageLocation": "",
|
|
||||||
"AutoFillEnabled": false,
|
|
||||||
"AutofillAddressEnabled": false,
|
"AutofillAddressEnabled": false,
|
||||||
"AutofillCreditCardEnabled": false,
|
"AutofillCreditCardEnabled": false,
|
||||||
"BrowserSignin": 0,
|
"BrowserSignin": 0,
|
||||||
@ -20,7 +18,7 @@
|
|||||||
"PromptForDownloadLocation": false,
|
"PromptForDownloadLocation": false,
|
||||||
"BookmarkBarEnabled": false,
|
"BookmarkBarEnabled": false,
|
||||||
"PasswordManagerEnabled": false,
|
"PasswordManagerEnabled": false,
|
||||||
"URLBlacklist": [
|
"URLBlocklist": [
|
||||||
"file://*",
|
"file://*",
|
||||||
"chrome://policy"
|
"chrome://policy"
|
||||||
],
|
],
|
||||||
@ -28,11 +26,11 @@
|
|||||||
"cjpalhdlnbpafiamejdnhcphjbkeiagm;https://clients2.google.com/service/update2/crx",
|
"cjpalhdlnbpafiamejdnhcphjbkeiagm;https://clients2.google.com/service/update2/crx",
|
||||||
"mnjggcdmjocbbbhaepdhchncahnbgone;https://clients2.google.com/service/update2/crx"
|
"mnjggcdmjocbbbhaepdhchncahnbgone;https://clients2.google.com/service/update2/crx"
|
||||||
],
|
],
|
||||||
"ExtensionInstallWhitelist": [
|
"ExtensionInstallAllowlist": [
|
||||||
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
|
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
|
||||||
"mnjggcdmjocbbbhaepdhchncahnbgone"
|
"mnjggcdmjocbbbhaepdhchncahnbgone"
|
||||||
],
|
],
|
||||||
"ExtensionInstallBlacklist": [
|
"ExtensionInstallBlocklist": [
|
||||||
"*"
|
"*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
{
|
{
|
||||||
"HomepageLocation": "",
|
|
||||||
"AutoFillEnabled": false,
|
|
||||||
"AutofillAddressEnabled": false,
|
"AutofillAddressEnabled": false,
|
||||||
"AutofillCreditCardEnabled": false,
|
"AutofillCreditCardEnabled": false,
|
||||||
"BrowserSignin": 0,
|
"BrowserSignin": 0,
|
||||||
@ -21,7 +19,7 @@
|
|||||||
"BookmarkBarEnabled": false,
|
"BookmarkBarEnabled": false,
|
||||||
"PasswordManagerEnabled": false,
|
"PasswordManagerEnabled": false,
|
||||||
"BrowserLabsEnabled": false,
|
"BrowserLabsEnabled": false,
|
||||||
"URLBlacklist": [
|
"URLBlocklist": [
|
||||||
"file://*",
|
"file://*",
|
||||||
"chrome://policy"
|
"chrome://policy"
|
||||||
],
|
],
|
||||||
@ -29,11 +27,11 @@
|
|||||||
"cjpalhdlnbpafiamejdnhcphjbkeiagm;https://clients2.google.com/service/update2/crx",
|
"cjpalhdlnbpafiamejdnhcphjbkeiagm;https://clients2.google.com/service/update2/crx",
|
||||||
"mnjggcdmjocbbbhaepdhchncahnbgone;https://clients2.google.com/service/update2/crx"
|
"mnjggcdmjocbbbhaepdhchncahnbgone;https://clients2.google.com/service/update2/crx"
|
||||||
],
|
],
|
||||||
"ExtensionInstallWhitelist": [
|
"ExtensionInstallAllowlist": [
|
||||||
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
|
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
|
||||||
"mnjggcdmjocbbbhaepdhchncahnbgone"
|
"mnjggcdmjocbbbhaepdhchncahnbgone"
|
||||||
],
|
],
|
||||||
"ExtensionInstallBlacklist": [
|
"ExtensionInstallBlocklist": [
|
||||||
"*"
|
"*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
{
|
{
|
||||||
"HomepageLocation": "",
|
|
||||||
"AutoFillEnabled": false,
|
|
||||||
"AutofillAddressEnabled": false,
|
"AutofillAddressEnabled": false,
|
||||||
"AutofillCreditCardEnabled": false,
|
"AutofillCreditCardEnabled": false,
|
||||||
"BrowserSignin": 0,
|
"BrowserSignin": 0,
|
||||||
@ -20,7 +18,7 @@
|
|||||||
"PromptForDownloadLocation": false,
|
"PromptForDownloadLocation": false,
|
||||||
"BookmarkBarEnabled": false,
|
"BookmarkBarEnabled": false,
|
||||||
"PasswordManagerEnabled": false,
|
"PasswordManagerEnabled": false,
|
||||||
"URLBlacklist": [
|
"URLBlocklist": [
|
||||||
"file://*",
|
"file://*",
|
||||||
"chrome://policy"
|
"chrome://policy"
|
||||||
],
|
],
|
||||||
@ -28,11 +26,11 @@
|
|||||||
"cjpalhdlnbpafiamejdnhcphjbkeiagm;https://clients2.google.com/service/update2/crx",
|
"cjpalhdlnbpafiamejdnhcphjbkeiagm;https://clients2.google.com/service/update2/crx",
|
||||||
"mnjggcdmjocbbbhaepdhchncahnbgone;https://clients2.google.com/service/update2/crx"
|
"mnjggcdmjocbbbhaepdhchncahnbgone;https://clients2.google.com/service/update2/crx"
|
||||||
],
|
],
|
||||||
"ExtensionInstallWhitelist": [
|
"ExtensionInstallAllowlist": [
|
||||||
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
|
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
|
||||||
"mnjggcdmjocbbbhaepdhchncahnbgone"
|
"mnjggcdmjocbbbhaepdhchncahnbgone"
|
||||||
],
|
],
|
||||||
"ExtensionInstallBlacklist": [
|
"ExtensionInstallBlocklist": [
|
||||||
"*"
|
"*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
{
|
{
|
||||||
"HomepageLocation": "",
|
|
||||||
"AutoFillEnabled": false,
|
|
||||||
"AutofillAddressEnabled": false,
|
"AutofillAddressEnabled": false,
|
||||||
"AutofillCreditCardEnabled": false,
|
"AutofillCreditCardEnabled": false,
|
||||||
"BrowserSignin": 0,
|
"BrowserSignin": 0,
|
||||||
@ -20,7 +18,7 @@
|
|||||||
"PromptForDownloadLocation": false,
|
"PromptForDownloadLocation": false,
|
||||||
"BookmarkBarEnabled": false,
|
"BookmarkBarEnabled": false,
|
||||||
"PasswordManagerEnabled": false,
|
"PasswordManagerEnabled": false,
|
||||||
"URLBlacklist": [
|
"URLBlocklist": [
|
||||||
"file://*",
|
"file://*",
|
||||||
"edge://policy"
|
"edge://policy"
|
||||||
],
|
],
|
||||||
@ -28,11 +26,11 @@
|
|||||||
"cjpalhdlnbpafiamejdnhcphjbkeiagm;https://clients2.google.com/service/update2/crx",
|
"cjpalhdlnbpafiamejdnhcphjbkeiagm;https://clients2.google.com/service/update2/crx",
|
||||||
"mnjggcdmjocbbbhaepdhchncahnbgone;https://clients2.google.com/service/update2/crx"
|
"mnjggcdmjocbbbhaepdhchncahnbgone;https://clients2.google.com/service/update2/crx"
|
||||||
],
|
],
|
||||||
"ExtensionInstallWhitelist": [
|
"ExtensionInstallAllowlist": [
|
||||||
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
|
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
|
||||||
"mnjggcdmjocbbbhaepdhchncahnbgone"
|
"mnjggcdmjocbbbhaepdhchncahnbgone"
|
||||||
],
|
],
|
||||||
"ExtensionInstallBlacklist": [
|
"ExtensionInstallBlocklist": [
|
||||||
"*"
|
"*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
27
.docker/opera/Dockerfile
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
ARG BASE_IMAGE=m1k1o/neko:base
|
||||||
|
FROM $BASE_IMAGE
|
||||||
|
|
||||||
|
ARG SRC_URL="https://download.opera.com/download/get/?id=58545&location=415¬hanks=yes&sub=marine&utm_tryagain=yes"
|
||||||
|
|
||||||
|
ARG LIBFFMPEG_URL="https://github.com/nwjs-ffmpeg-prebuilt/nwjs-ffmpeg-prebuilt/releases/download/0.67.1/0.67.1-linux-x64.zip"
|
||||||
|
|
||||||
|
#
|
||||||
|
# install opera
|
||||||
|
RUN apt-get update
|
||||||
|
RUN wget -O /tmp/opera.deb $SRC_URL
|
||||||
|
RUN apt-get install -y --no-install-recommends openbox unzip /tmp/opera.deb
|
||||||
|
|
||||||
|
## install libffmpeg
|
||||||
|
RUN wget -O /tmp/libffmpeg.zip $LIBFFMPEG_URL
|
||||||
|
RUN unzip -o /tmp/libffmpeg.zip libffmpeg.so -d /usr/lib/x86_64-linux-gnu/opera/lib_extra
|
||||||
|
RUN echo '[]' > /usr/lib/x86_64-linux-gnu/opera/resources/ffmpeg_preload_config.json
|
||||||
|
#
|
||||||
|
# clean up
|
||||||
|
RUN apt-get clean -y
|
||||||
|
RUN rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||||
|
|
||||||
|
#
|
||||||
|
# copy configuation files
|
||||||
|
COPY supervisord.conf /etc/neko/supervisord/opera.conf
|
||||||
|
COPY openbox.xml /etc/neko/openbox.xml
|
||||||
|
|
763
.docker/opera/openbox.xml
Normal 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="Opera" name="Opera">
|
||||||
|
<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>
|
22
.docker/opera/supervisord.conf
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[program:opera]
|
||||||
|
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||||
|
command=/usr/bin/opera --no-sandbox --no-first-run --start-maximized --force-dark-mode --disable-gpu
|
||||||
|
stopsignal=INT
|
||||||
|
autorestart=true
|
||||||
|
priority=800
|
||||||
|
user=%(ENV_USER)s
|
||||||
|
stdout_logfile=/var/log/neko/opera.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
|
@ -5,6 +5,7 @@ ARG API_URL="https://api.github.com/repos/macchrome/linchrome/releases/latest"
|
|||||||
|
|
||||||
#
|
#
|
||||||
# install custom chromium build from woolyss with support for hevc/x265
|
# install custom chromium build from woolyss with support for hevc/x265
|
||||||
|
SHELL ["/bin/bash", "-c"]
|
||||||
RUN set -eux; apt-get update; \
|
RUN set -eux; apt-get update; \
|
||||||
apt-get install -y --no-install-recommends wget unzip libatk1.0-0 libatk-bridge2.0-0 libatomic1 \
|
apt-get install -y --no-install-recommends wget unzip libatk1.0-0 libatk-bridge2.0-0 libatomic1 \
|
||||||
libcups2 libgtk-3-0 libnss3 libpci3 libxcomposite1 libxss1 openbox xz-utils jq; \
|
libcups2 libgtk-3-0 libnss3 libpci3 libxcomposite1 libxss1 openbox xz-utils jq; \
|
||||||
@ -21,11 +22,27 @@ RUN set -eux; apt-get update; \
|
|||||||
#
|
#
|
||||||
# install widevine module
|
# install widevine module
|
||||||
WIDEVINE_VERSION=$(wget --quiet -O - https://dl.google.com/widevine-cdm/versions.txt | tail -n 1); \
|
WIDEVINE_VERSION=$(wget --quiet -O - https://dl.google.com/widevine-cdm/versions.txt | tail -n 1); \
|
||||||
wget -O /tmp/widevine.zip "https://dl.google.com/widevine-cdm/$WIDEVINE_VERSION-linux-x64.zip"; \
|
wget -O /tmp/widevine.zip "https://dl.google.com/widevine-cdm/${WIDEVINE_VERSION}-linux-x64.zip"; \
|
||||||
unzip -p /tmp/widevine.zip libwidevinecdm.so > /usr/lib/chromium/libwidevinecdm.so; \
|
unzip -p /tmp/widevine.zip libwidevinecdm.so > /usr/lib/chromium/libwidevinecdm.so; \
|
||||||
chmod 644 /usr/lib/chromium/libwidevinecdm.so; \
|
chmod 644 /usr/lib/chromium/libwidevinecdm.so; \
|
||||||
rm /tmp/widevine.zip; \
|
rm /tmp/widevine.zip; \
|
||||||
#
|
#
|
||||||
|
# install latest version of uBlock Origin and SponsorBlock for YouTube
|
||||||
|
CHROMIUM_VERSION="$(wget -O - "${API_URL}" 2>/dev/null | jq -r ".tag_name" | sed -e 's/v//' -e 's/-.*//')"; \
|
||||||
|
EXTENSIONS_DIR="/usr/share/chromium/extensions"; \
|
||||||
|
EXTENSIONS=( \
|
||||||
|
cjpalhdlnbpafiamejdnhcphjbkeiagm \
|
||||||
|
mnjggcdmjocbbbhaepdhchncahnbgone \
|
||||||
|
); \
|
||||||
|
mkdir -p "${EXTENSIONS_DIR}"; \
|
||||||
|
for EXT_ID in "${EXTENSIONS[@]}"; \
|
||||||
|
do \
|
||||||
|
EXT_URL="https://clients2.google.com/service/update2/crx?response=redirect&nacl_arch=x86-64&prodversion=${CHROMIUM_VERSION}&acceptformat=crx2,crx3&x=id%3D${EXT_ID}%26installsource%3Dondemand%26uc"; \
|
||||||
|
EXT_PATH="${EXTENSIONS_DIR}/${EXT_ID}.crx"; \
|
||||||
|
wget -O "${EXT_PATH}" "${EXT_URL}"; \
|
||||||
|
EXT_VERSION="$(unzip -p "${EXT_PATH}" manifest.json 2>/dev/null | jq -r ".version")"; \
|
||||||
|
echo -e "{\n \"external_crx\": \"${EXT_PATH}\",\n \"external_version\": \"${EXT_VERSION}\"\n}" > "${EXTENSIONS_DIR}"/"${EXT_ID}".json; \
|
||||||
|
done; \
|
||||||
# clean up
|
# clean up
|
||||||
apt-get --purge autoremove -y xz-utils jq; \
|
apt-get --purge autoremove -y xz-utils jq; \
|
||||||
apt-get clean -y; \
|
apt-get clean -y; \
|
||||||
@ -37,7 +54,3 @@ COPY supervisord.conf /etc/neko/supervisord/ungoogled-chromium.conf
|
|||||||
COPY preferences.json /usr/lib/chromium/master_preferences
|
COPY preferences.json /usr/lib/chromium/master_preferences
|
||||||
COPY policies.json /etc/chromium/policies/managed/policies.json
|
COPY policies.json /etc/chromium/policies/managed/policies.json
|
||||||
COPY openbox.xml /etc/neko/openbox.xml
|
COPY openbox.xml /etc/neko/openbox.xml
|
||||||
|
|
||||||
#
|
|
||||||
# copy extensions and policy files
|
|
||||||
COPY extensions /usr/share/chromium/extensions
|
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"external_crx": "/usr/share/chromium/extensions/cjpalhdlnbpafiamejdnhcphjbkeiagm.crx",
|
|
||||||
"external_version": "1.38.6"
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"external_crx": "/usr/share/chromium/extensions/mnjggcdmjocbbbhaepdhchncahnbgone.crx",
|
|
||||||
"external_version": "4.0.5"
|
|
||||||
}
|
|
@ -1,6 +1,4 @@
|
|||||||
{
|
{
|
||||||
"HomepageLocation": "",
|
|
||||||
"AutoFillEnabled": false,
|
|
||||||
"AutofillAddressEnabled": false,
|
"AutofillAddressEnabled": false,
|
||||||
"AutofillCreditCardEnabled": false,
|
"AutofillCreditCardEnabled": false,
|
||||||
"BrowserSignin": 0,
|
"BrowserSignin": 0,
|
||||||
@ -20,15 +18,15 @@
|
|||||||
"PromptForDownloadLocation": false,
|
"PromptForDownloadLocation": false,
|
||||||
"BookmarkBarEnabled": false,
|
"BookmarkBarEnabled": false,
|
||||||
"PasswordManagerEnabled": false,
|
"PasswordManagerEnabled": false,
|
||||||
"URLBlacklist": [
|
"URLBlocklist": [
|
||||||
"file://*",
|
"file://*",
|
||||||
"chrome://policy"
|
"chrome://policy"
|
||||||
],
|
],
|
||||||
"ExtensionInstallWhitelist": [
|
"ExtensionInstallAllowlist": [
|
||||||
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
|
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
|
||||||
"mnjggcdmjocbbbhaepdhchncahnbgone"
|
"mnjggcdmjocbbbhaepdhchncahnbgone"
|
||||||
],
|
],
|
||||||
"ExtensionInstallBlacklist": [
|
"ExtensionInstallBlocklist": [
|
||||||
"*"
|
"*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
42
.docker/vivaldi/Dockerfile
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
ARG BASE_IMAGE=m1k1o/neko:base
|
||||||
|
FROM $BASE_IMAGE
|
||||||
|
|
||||||
|
ARG VIVALDI_VERSION="5.3.2679.34-1"
|
||||||
|
# TODO: Get chromium version from vivaldi
|
||||||
|
ARG CHROMIUM_VERSION="102.0.5005.72"
|
||||||
|
|
||||||
|
#
|
||||||
|
# install vivaldi
|
||||||
|
SHELL ["/bin/bash", "-c"]
|
||||||
|
RUN set -eux; apt-get update; \
|
||||||
|
wget -O /tmp/vivaldi.deb "https://downloads.vivaldi.com/stable/vivaldi-stable_${VIVALDI_VERSION}_amd64.deb"; \
|
||||||
|
apt-get install -y --no-install-recommends wget unzip xz-utils jq openbox /tmp/vivaldi.deb; \
|
||||||
|
/opt/vivaldi/update-ffmpeg; \
|
||||||
|
#
|
||||||
|
# install latest version of uBlock Origin and SponsorBlock for YouTube
|
||||||
|
EXTENSIONS_DIR="/usr/share/chromium/extensions"; \
|
||||||
|
EXTENSIONS=( \
|
||||||
|
cjpalhdlnbpafiamejdnhcphjbkeiagm \
|
||||||
|
mnjggcdmjocbbbhaepdhchncahnbgone \
|
||||||
|
); \
|
||||||
|
mkdir -p "${EXTENSIONS_DIR}"; \
|
||||||
|
for EXT_ID in "${EXTENSIONS[@]}"; \
|
||||||
|
do \
|
||||||
|
EXT_URL="https://clients2.google.com/service/update2/crx?response=redirect&nacl_arch=x86-64&prodversion=${CHROMIUM_VERSION}&acceptformat=crx2,crx3&x=id%3D${EXT_ID}%26installsource%3Dondemand%26uc"; \
|
||||||
|
EXT_PATH="${EXTENSIONS_DIR}/${EXT_ID}.crx"; \
|
||||||
|
wget -O "${EXT_PATH}" "${EXT_URL}"; \
|
||||||
|
EXT_VERSION="$(unzip -p "${EXT_PATH}" manifest.json 2>/dev/null | jq -r ".version")"; \
|
||||||
|
echo -e "{\n \"external_crx\": \"${EXT_PATH}\",\n \"external_version\": \"${EXT_VERSION}\"\n}" > "${EXTENSIONS_DIR}"/"${EXT_ID}".json; \
|
||||||
|
done; \
|
||||||
|
#
|
||||||
|
# clean up
|
||||||
|
apt-get --purge autoremove -y xz-utils jq; \
|
||||||
|
apt-get clean -y; \
|
||||||
|
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||||
|
|
||||||
|
#
|
||||||
|
# copy configuation files
|
||||||
|
COPY supervisord.conf /etc/neko/supervisord/vivaldi-browser.conf
|
||||||
|
COPY --chown=neko preferences.json /home/neko/.config/vivaldi/Default/Preferences
|
||||||
|
COPY policies.json /etc/opt/vivaldi/policies/managed/policies.json
|
||||||
|
COPY openbox.xml /etc/neko/openbox.xml
|
763
.docker/vivaldi/openbox.xml
Normal 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="Vivaldi-stable*" name="vivaldi-stable*" role="browser">
|
||||||
|
<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>
|
36
.docker/vivaldi/policies.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"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,
|
||||||
|
"URLBlocklist": [
|
||||||
|
"file://*",
|
||||||
|
"chrome://policy"
|
||||||
|
],
|
||||||
|
"ExtensionInstallForcelist": [
|
||||||
|
"cjpalhdlnbpafiamejdnhcphjbkeiagm;https://clients2.google.com/service/update2/crx",
|
||||||
|
"mnjggcdmjocbbbhaepdhchncahnbgone;https://clients2.google.com/service/update2/crx"
|
||||||
|
],
|
||||||
|
"ExtensionInstallAllowlist": [
|
||||||
|
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
|
||||||
|
"mnjggcdmjocbbbhaepdhchncahnbgone"
|
||||||
|
],
|
||||||
|
"ExtensionInstallBlocklist": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
339
.docker/vivaldi/preferences.json
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
"default_search_provider_data": {
|
||||||
|
"image_template_url_data": {
|
||||||
|
"alternate_urls": ["https://www.google.com/#q={searchTerms}", "https://www.google.com/search#q={searchTerms}", "https://www.google.com/webhp#q={searchTerms}", "https://www.google.com/s#q={searchTerms}", "https://www.google.com/s?q={searchTerms}"],
|
||||||
|
"contextual_search_url": "",
|
||||||
|
"created_by_policy": false,
|
||||||
|
"created_from_play_api": false,
|
||||||
|
"date_created": "0",
|
||||||
|
"doodle_url": "",
|
||||||
|
"favicon_url": "https://www.google.com/favicon.ico",
|
||||||
|
"id": "11",
|
||||||
|
"image_url": "https://www.google.com/searchbyimage/upload",
|
||||||
|
"image_url_post_params": "encoded_image={google:imageThumbnail},image_url={google:imageURL},sbisrc={google:imageSearchSource},original_width={google:imageOriginalWidth},original_height={google:imageOriginalHeight}",
|
||||||
|
"input_encodings": ["UTF-8"],
|
||||||
|
"is_active": 0,
|
||||||
|
"keyword": "g",
|
||||||
|
"last_modified": "0",
|
||||||
|
"last_visited": "0",
|
||||||
|
"logo_url": "",
|
||||||
|
"new_tab_url": "",
|
||||||
|
"originating_url": "",
|
||||||
|
"position": "IhxEc1dKb3ZJanppM1NCWTZhRHNGN0ljYkRHeVk9",
|
||||||
|
"preconnect_to_search_url": false,
|
||||||
|
"prepopulate_id": 1,
|
||||||
|
"safe_for_autoreplace": true,
|
||||||
|
"search_url_post_params": "",
|
||||||
|
"short_name": "Google",
|
||||||
|
"side_search_param": "",
|
||||||
|
"suggestions_url": "",
|
||||||
|
"suggestions_url_post_params": "",
|
||||||
|
"url": "https://www.google.com/search?q={searchTerms}&{google:originalQueryForSuggestion}{google:prefetchSource}{google:sourceId}{google:contextualSearchVersion}ie={inputEncoding}",
|
||||||
|
"usage_count": 0
|
||||||
|
},
|
||||||
|
"private_template_url_data": {
|
||||||
|
"alternate_urls": [],
|
||||||
|
"contextual_search_url": "",
|
||||||
|
"created_by_policy": false,
|
||||||
|
"created_from_play_api": false,
|
||||||
|
"date_created": "0",
|
||||||
|
"doodle_url": "",
|
||||||
|
"favicon_url": "https://duckduckgo.com/favicon.ico",
|
||||||
|
"id": "4",
|
||||||
|
"image_url": "",
|
||||||
|
"image_url_post_params": "",
|
||||||
|
"input_encodings": ["UTF-8"],
|
||||||
|
"is_active": 0,
|
||||||
|
"keyword": "d",
|
||||||
|
"last_modified": "0",
|
||||||
|
"last_visited": "0",
|
||||||
|
"logo_url": "",
|
||||||
|
"new_tab_url": "",
|
||||||
|
"originating_url": "",
|
||||||
|
"position": "Ih3bMkFheVB5WitEdGZmeU9hV0d5K3RGUXRhR3RFPQ==",
|
||||||
|
"preconnect_to_search_url": false,
|
||||||
|
"prepopulate_id": 7,
|
||||||
|
"safe_for_autoreplace": true,
|
||||||
|
"search_url_post_params": "",
|
||||||
|
"short_name": "DuckDuckGo",
|
||||||
|
"side_search_param": "",
|
||||||
|
"suggestions_url": "https://duckduckgo.com/ac/?q={searchTerms}&type=list",
|
||||||
|
"suggestions_url_post_params": "",
|
||||||
|
"url": "https://duckduckgo.com/?q={searchTerms}&{ddg:Referral}",
|
||||||
|
"usage_count": 0
|
||||||
|
},
|
||||||
|
"speeddials_template_url_data": {
|
||||||
|
"alternate_urls": ["https://www.google.com/#q={searchTerms}", "https://www.google.com/search#q={searchTerms}", "https://www.google.com/webhp#q={searchTerms}", "https://www.google.com/s#q={searchTerms}", "https://www.google.com/s?q={searchTerms}"],
|
||||||
|
"contextual_search_url": "",
|
||||||
|
"created_by_policy": false,
|
||||||
|
"created_from_play_api": false,
|
||||||
|
"date_created": "0",
|
||||||
|
"doodle_url": "",
|
||||||
|
"favicon_url": "https://www.google.com/favicon.ico",
|
||||||
|
"id": "11",
|
||||||
|
"image_url": "https://www.google.com/searchbyimage/upload",
|
||||||
|
"image_url_post_params": "encoded_image={google:imageThumbnail},image_url={google:imageURL},sbisrc={google:imageSearchSource},original_width={google:imageOriginalWidth},original_height={google:imageOriginalHeight}",
|
||||||
|
"input_encodings": ["UTF-8"],
|
||||||
|
"is_active": 0,
|
||||||
|
"keyword": "g",
|
||||||
|
"last_modified": "0",
|
||||||
|
"last_visited": "0",
|
||||||
|
"logo_url": "",
|
||||||
|
"new_tab_url": "",
|
||||||
|
"originating_url": "",
|
||||||
|
"position": "IhxEc1dKb3ZJanppM1NCWTZhRHNGN0ljYkRHeVk9",
|
||||||
|
"preconnect_to_search_url": false,
|
||||||
|
"prepopulate_id": 1,
|
||||||
|
"safe_for_autoreplace": true,
|
||||||
|
"search_url_post_params": "",
|
||||||
|
"short_name": "Google",
|
||||||
|
"side_search_param": "",
|
||||||
|
"suggestions_url": "",
|
||||||
|
"suggestions_url_post_params": "",
|
||||||
|
"url": "https://www.google.com/search?q={searchTerms}&{google:originalQueryForSuggestion}{google:prefetchSource}{google:sourceId}{google:contextualSearchVersion}ie={inputEncoding}",
|
||||||
|
"usage_count": 0
|
||||||
|
},
|
||||||
|
"template_url_data": {
|
||||||
|
"alternate_urls": ["https://www.google.com/#q={searchTerms}", "https://www.google.com/search#q={searchTerms}", "https://www.google.com/webhp#q={searchTerms}", "https://www.google.com/s#q={searchTerms}", "https://www.google.com/s?q={searchTerms}"],
|
||||||
|
"contextual_search_url": "",
|
||||||
|
"created_by_policy": false,
|
||||||
|
"created_from_play_api": false,
|
||||||
|
"date_created": "0",
|
||||||
|
"doodle_url": "",
|
||||||
|
"favicon_url": "https://www.google.com/favicon.ico",
|
||||||
|
"id": "11",
|
||||||
|
"image_url": "https://www.google.com/searchbyimage/upload",
|
||||||
|
"image_url_post_params": "encoded_image={google:imageThumbnail},image_url={google:imageURL},sbisrc={google:imageSearchSource},original_width={google:imageOriginalWidth},original_height={google:imageOriginalHeight}",
|
||||||
|
"input_encodings": ["UTF-8"],
|
||||||
|
"is_active": 0,
|
||||||
|
"keyword": "g",
|
||||||
|
"last_modified": "0",
|
||||||
|
"last_visited": "0",
|
||||||
|
"logo_url": "",
|
||||||
|
"new_tab_url": "",
|
||||||
|
"originating_url": "",
|
||||||
|
"position": "IhxEc1dKb3ZJanppM1NCWTZhRHNGN0ljYkRHeVk9",
|
||||||
|
"preconnect_to_search_url": false,
|
||||||
|
"prepopulate_id": 1,
|
||||||
|
"safe_for_autoreplace": true,
|
||||||
|
"search_url_post_params": "",
|
||||||
|
"short_name": "Google",
|
||||||
|
"side_search_param": "",
|
||||||
|
"suggestions_url": "",
|
||||||
|
"suggestions_url_post_params": "",
|
||||||
|
"url": "https://www.google.com/search?q={searchTerms}&{google:originalQueryForSuggestion}{google:prefetchSource}{google:sourceId}{google:contextualSearchVersion}ie={inputEncoding}",
|
||||||
|
"usage_count": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
"enable_do_not_track": true,
|
||||||
|
"profile": {
|
||||||
|
"avatar_index": 34,
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
"vivaldi": {
|
||||||
|
"address_bar": {
|
||||||
|
"autocomplete": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"omnibox": {
|
||||||
|
"show_browser_history": false,
|
||||||
|
"show_search_history": false,
|
||||||
|
"show_typed_history": false
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"display": 1,
|
||||||
|
"in_new_tab": false
|
||||||
|
},
|
||||||
|
"show_full_url": true,
|
||||||
|
"show_qr_generator": true,
|
||||||
|
"visible": true
|
||||||
|
},
|
||||||
|
"bookmarks": {
|
||||||
|
"deleted_partners": [
|
||||||
|
"f79cd6e8-ebc0-444d-ac96-00da456dcb59",
|
||||||
|
"d680347f-1073-46b9-a546-ae0238e7b9d9"
|
||||||
|
],
|
||||||
|
"language": "en-US",
|
||||||
|
"version": "24"
|
||||||
|
},
|
||||||
|
"downloads": {
|
||||||
|
"notify_on_complete": false,
|
||||||
|
"open_panel_on_new": false,
|
||||||
|
"start_automatically": false,
|
||||||
|
"update_default_download_when_saving_as": false
|
||||||
|
},
|
||||||
|
"history": {
|
||||||
|
"days_to_keep_visits": 0
|
||||||
|
},
|
||||||
|
"homepage": "vivaldi://startpage",
|
||||||
|
"incognito": {
|
||||||
|
"show_intro": false
|
||||||
|
},
|
||||||
|
"language_at_install": "en-US",
|
||||||
|
"menu": {
|
||||||
|
"icon_type": 1
|
||||||
|
},
|
||||||
|
"mouse_gestures": {
|
||||||
|
"enabled": false,
|
||||||
|
"rocker_gestures": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"popups": {
|
||||||
|
"show_in_tab": true
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"in_tab": true
|
||||||
|
},
|
||||||
|
"startpage": {
|
||||||
|
"navigation": 1,
|
||||||
|
"speed_dial": {
|
||||||
|
"add_button_visible": false,
|
||||||
|
"allow_dnd": false,
|
||||||
|
"columns": 4,
|
||||||
|
"delete_visible": false,
|
||||||
|
"display_search": true,
|
||||||
|
"privacy_stats_show": false,
|
||||||
|
"tracker_suggestion_show": false,
|
||||||
|
"width": 170
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"startup": {
|
||||||
|
"check_is_default": false,
|
||||||
|
"has_seen_feature": 1
|
||||||
|
},
|
||||||
|
"status_bar": {
|
||||||
|
"display": 0,
|
||||||
|
"minimized": 0
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"show_exit_confirmation_dialog": true
|
||||||
|
},
|
||||||
|
"tabs": {
|
||||||
|
"new_placement": 3,
|
||||||
|
"open_new_in_background": false,
|
||||||
|
"stacking": {
|
||||||
|
"open_accordions": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"schedule": {
|
||||||
|
"o_s": {
|
||||||
|
"dark": "Vivaldi2",
|
||||||
|
"light": "Vivaldi2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use_animation": false
|
||||||
|
},
|
||||||
|
"translate": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"windows": {
|
||||||
|
"use_native_decoration": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
.docker/vivaldi/supervisord.conf
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[program:vivaldi-stable]
|
||||||
|
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||||
|
command=/usr/bin/vivaldi-stable --no-sandbox --window-position=0,0 --display=%(ENV_DISPLAY)s --user-data-dir=/home/neko/.config/vivaldi --no-first-run --start-maximized --bwsi --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage
|
||||||
|
stopsignal=INT
|
||||||
|
autorestart=true
|
||||||
|
priority=800
|
||||||
|
user=%(ENV_USER)s
|
||||||
|
stdout_logfile=/var/log/neko/vivaldi.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
|
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
github: [ m1k1o ]
|
2
.github/workflows/build.yml
vendored
@ -50,7 +50,7 @@ jobs:
|
|||||||
# Will build all images even if some fail.
|
# Will build all images even if some fail.
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
tags: [ firefox, chromium, google-chrome, ungoogled-chromium, microsoft-edge, brave, tor-browser, remmina, vlc, xfce ]
|
tags: [ firefox, chromium, google-chrome, ungoogled-chromium, microsoft-edge, brave, vivaldi, opera, tor-browser, remmina, vlc, xfce ]
|
||||||
env:
|
env:
|
||||||
DOCKER_TAG: ${{ matrix.tags }}
|
DOCKER_TAG: ${{ matrix.tags }}
|
||||||
steps:
|
steps:
|
||||||
|
4
.github/workflows/tags.yml
vendored
@ -78,6 +78,10 @@ jobs:
|
|||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
- tag: brave
|
- tag: brave
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
|
- tag: vivaldi
|
||||||
|
platforms: linux/amd64
|
||||||
|
- tag: opera
|
||||||
|
platforms: linux/amd64
|
||||||
- tag: tor-browser
|
- tag: tor-browser
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
- tag: remmina
|
- tag: remmina
|
||||||
|
102
README.md
@ -1,12 +1,23 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://github.com/m1k1o/neko" title="Neko's Github repository.">
|
<a href="https://github.com/m1k1o/neko" title="Neko's Github repository.">
|
||||||
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/logo.png" width="450" height="auto"/>
|
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/logo.png" width="400" height="auto"/>
|
||||||
</a>
|
</a>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/github/v/release/m1k1o/neko" alt="release">
|
<a href="https://github.com/m1k1o/neko/releases">
|
||||||
<img src="https://img.shields.io/github/license/m1k1o/neko" alt="license">
|
<img src="https://img.shields.io/github/v/release/m1k1o/neko" alt="release">
|
||||||
<img src="https://img.shields.io/docker/pulls/m1k1o/neko" alt="pulls">
|
</a>
|
||||||
<img src="https://img.shields.io/github/issues/m1k1o/neko" alt="issues">
|
<a href="https://github.com/m1k1o/neko/blob/master/LICENSE">
|
||||||
|
<img src="https://img.shields.io/github/license/m1k1o/neko" alt="license">
|
||||||
|
</a>
|
||||||
|
<a href="https://hub.docker.com/u/m1k1o/neko">
|
||||||
|
<img src="https://img.shields.io/docker/pulls/m1k1o/neko" alt="pulls">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/m1k1o/neko/issues">
|
||||||
|
<img src="https://img.shields.io/github/issues/m1k1o/neko" alt="issues">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/sponsors/m1k1o">
|
||||||
|
<img src="https://img.shields.io/badge/-sponsor-red" alt="issues">
|
||||||
|
</a>
|
||||||
<a href="https://discord.gg/3U6hWpC">
|
<a href="https://discord.gg/3U6hWpC">
|
||||||
<img src="https://discordapp.com/api/guilds/665851821906067466/widget.png" alt="Chat on discord">
|
<img src="https://discordapp.com/api/guilds/665851821906067466/widget.png" alt="Chat on discord">
|
||||||
</a>
|
</a>
|
||||||
@ -14,11 +25,7 @@
|
|||||||
<img src="https://github.com/m1k1o/neko/actions/workflows/build.yml/badge.svg" alt="build">
|
<img src="https://github.com/m1k1o/neko/actions/workflows/build.yml/badge.svg" alt="build">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<img src="https://i.imgur.com/ZSzbQr7.gif" width="650" height="auto"/>
|
<img src="https://i.imgur.com/ZSzbQr7.gif" width="650" height="auto"/>
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
# n.eko
|
# n.eko
|
||||||
@ -27,14 +34,77 @@ This app uses WebRTC to stream a desktop inside of a docker container, original
|
|||||||
|
|
||||||
Then I found [this](https://github.com/nurdism/neko) project and started to dig into it. I really liked the idea of having collaborative browser browsing together with mutliple people, so I created a fork. Initially, I wanted to merge my changes to the upstream repository, but the original author did not have time for this project anymore and it got eventually archived.
|
Then I found [this](https://github.com/nurdism/neko) project and started to dig into it. I really liked the idea of having collaborative browser browsing together with mutliple people, so I created a fork. Initially, I wanted to merge my changes to the upstream repository, but the original author did not have time for this project anymore and it got eventually archived.
|
||||||
|
|
||||||
|
## Use-cases and comparison
|
||||||
|
|
||||||
|
Neko started as a virtual browser that is streamed using WebRTC to multiple users.
|
||||||
|
- It is **not only limited to a browser**; it can run anything that runs on linux (e.g. VLC). Browser only happens to be the most popular and widely used use-case.
|
||||||
|
- In fact, it is not limited to a single program either; you can install a full desktop environment (e.g. XFCE).
|
||||||
|
- Speaking of limits, it does not need to run in a container; you could install neko on your host, connect to your X server and control your whole VM.
|
||||||
|
- Theoretically it is not limited to only X server, anything that can be controlled and scraped periodically for images could be used instead.
|
||||||
|
- Like implementing RDP or VNC protocol, where neko would only act as WebRTC relay server. This is currently only future.
|
||||||
|
|
||||||
|
Primary use case is connecting with multiple people, leveraging real time synchronization and interactivity:
|
||||||
|
- **Watch party** - watching video content together with multiple people and reacting to it (chat, emotes) - open source alternative to [giggl.app](https://giggl.app/).
|
||||||
|
- **Interactive presentation** - not only screen sharing, but others can control the screen.
|
||||||
|
- **Collaborative tool** - brainstorming ideas, cobrowsing, code debugging together.
|
||||||
|
- **Support/Teaching** - interactively guiding people in controlled environment.
|
||||||
|
- **Embed anything** - embed virtual browser in your web app - open source alternative to [hyperbeam](https://hyperbeam.com/).
|
||||||
|
- open any third-party website or application, synchronize audio and video flawlessly among multiple participants.
|
||||||
|
- request rooms using API with [neko-rooms](https://github.com/m1k1o/neko-rooms).
|
||||||
|
|
||||||
|
Other use cases that benefit from single-user:
|
||||||
|
- **Personal workspace** - streaming containerized apps and desktops to end-users - similar to [kasm](https://www.kasmweb.com/).
|
||||||
|
- **Persistent browser** - own browser with persistent cookies available anywhere - similar to [mightyapp](https://www.mightyapp.com/).
|
||||||
|
- no state is left on the host browser after terminating the connection.
|
||||||
|
- sensitive data like cookies are not transferred - only video is shared.
|
||||||
|
- **Throwaway browser** - a better solution for planning secret parties and buying birthday gifts off the internet.
|
||||||
|
- use Tor Browser and [VPN](https://github.com/m1k1o/neko-vpn) for additional anonymity.
|
||||||
|
- mitigates risk of OS fingerprinting and browser vulnerabilities by running in container.
|
||||||
|
- **Session broadcasting** - broadcast room content using RTMP (to e.g. twitch or youtube...).
|
||||||
|
- **Session recording** - broadcast RTMP can be saved to a file using e.g. [nginx-rtmp](https://www.nginx.com/products/nginx/modules/rtmp-media-streaming/)
|
||||||
|
- have clean environment when recording tutorials.
|
||||||
|
- no need to hide bookmarks or use incognito mode.
|
||||||
|
- **Jump host** - access your internal applications securely without the need for VPN.
|
||||||
|
- **Automated browser** - you can install [playwright](https://playwright.dev/) or [puppeteer](https://pptr.dev/) and automate tasks while being able to actively intercept them.
|
||||||
|
|
||||||
|
Compared to clientless remote desktop gateway (e.g. [Apache Guacamole](https://guacamole.apache.org/) or [websockify](https://github.com/novnc/websockify) with [noVNC](https://novnc.com/)), installed with remote desktop server along with desired program (e.g. [linuxserver/firefox](https://docs.linuxserver.io/images/docker-firefox)) provides neko additionally:
|
||||||
|
- **Smooth video** because it uses WebRTC and not images sent over WebSockets.
|
||||||
|
- **Built in audio** support, what is not part of Apache Guacamole or noVNC.
|
||||||
|
- **Multi-participant control**, what is not natively supported by Apache Guacamole or noVNC.
|
||||||
|
|
||||||
|
### Supported browsers
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/firefox.svg" title="m1k1o/neko:firefox" width="60" height="auto"/>
|
||||||
|
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/google-chrome.svg" title="m1k1o/neko:google-chrome" width="60" height="auto"/>
|
||||||
|
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/chromium.svg" title="m1k1o/neko:chromium" width="60" height="auto"/>
|
||||||
|
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/microsoft-edge.svg" title="m1k1o/neko:microsoft-edge" width="60" height="auto"/>
|
||||||
|
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/brave.svg" title="m1k1o/neko:brave" width="60" height="auto"/>
|
||||||
|
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/vivaldi.svg" title="m1k1o/neko:vivaldi" width="60" height="auto"/>
|
||||||
|
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/opera.svg" title="m1k1o/neko:opera" width="60" height="auto"/>
|
||||||
|
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/tor-browser.svg" title="m1k1o/neko:tor-browser" width="60" height="auto"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Other programs
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/remmina.png" title="m1k1o/neko:remmina" width="60" height="auto"/>
|
||||||
|
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/vlc.svg" title="m1k1o/neko:vlc" width="60" height="auto"/>
|
||||||
|
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/xfce.svg" title="m1k1o/neko:xfce" width="60" height="auto"/>
|
||||||
|
|
||||||
|
... others in <a href="https://github.com/m1k1o/neko-apps">m1k1o/neko-apps</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* Text Chat (With basic markdown support, discord flavor)
|
* Text Chat (With basic markdown support, discord flavor)
|
||||||
* Admin users (Kick, Ban & Force Give/Release Controls)
|
* Admin users (Kick, Ban & Force Give/Release Controls, Lock room)
|
||||||
* Clipboard synchronization (on [supported browsers](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/readText))
|
* Clipboard synchronization (on [supported browsers](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/readText))
|
||||||
* Emote overlay
|
* Emote overlay
|
||||||
* Ignore user (chat and emotes)
|
* Ignore user (chat and emotes)
|
||||||
* Persistent settings
|
* Persistent settings
|
||||||
|
* Automatic Login with custom url args. (add `?usr=<your-user-name>&pwd=<room-pass>` to the url.)
|
||||||
|
* Broadcasting room content using RTMP (to e.g. twitch or youtube...)
|
||||||
|
|
||||||
### Why n.eko?
|
### Why n.eko?
|
||||||
|
|
||||||
@ -42,13 +112,13 @@ I like cats 🐱 (`Neko` is the Japanese word for cat), I'm a weeb/nerd.
|
|||||||
|
|
||||||
***But why the cat butt?*** Because cats are *assholes*, but you love them anyways.
|
***But why the cat butt?*** Because cats are *assholes*, but you love them anyways.
|
||||||
|
|
||||||
# Multiple rooms
|
## Multiple rooms
|
||||||
|
|
||||||
For n.eko room management software, visit [neko-rooms](https://github.com/m1k1o/neko-rooms).
|
For n.eko room management software, visit [neko-rooms](https://github.com/m1k1o/neko-rooms).
|
||||||
|
|
||||||
It also offers zero-knowledge [installation script](https://github.com/m1k1o/neko-rooms/#zero-knowledge-installation).
|
It also offers zero-knowledge [installation script (with HTTPS and Traefik)](https://github.com/m1k1o/neko-rooms/#zero-knowledge-installation-with-https-and-traefik).
|
||||||
|
|
||||||
# Documentation
|
## Documentation
|
||||||
|
|
||||||
* [Getting Started](https://neko.m1k1o.net/#/getting-started/)
|
* [Getting Started](https://neko.m1k1o.net/#/getting-started/)
|
||||||
* [Quick Start](https://neko.m1k1o.net/#/getting-started/quick-start)
|
* [Quick Start](https://neko.m1k1o.net/#/getting-started/quick-start)
|
||||||
@ -62,6 +132,10 @@ It also offers zero-knowledge [installation script](https://github.com/m1k1o/nek
|
|||||||
* [Technologies](https://neko.m1k1o.net/#/technologies)
|
* [Technologies](https://neko.m1k1o.net/#/technologies)
|
||||||
* [Changelog](https://neko.m1k1o.net/#/changelog)
|
* [Changelog](https://neko.m1k1o.net/#/changelog)
|
||||||
|
|
||||||
# How to contribute? How to build?
|
## How to contribute? How to build?
|
||||||
|
|
||||||
Navigate to [.docker](.docker) folder for further information.
|
Navigate to [.docker](.docker) folder for further information.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
If you want to support this project, you can do it [here](https://github.com/sponsors/m1k1o).
|
||||||
|
@ -20,50 +20,50 @@
|
|||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^6.1.1",
|
"@fortawesome/fontawesome-free": "^6.1.2",
|
||||||
"animejs": "^3.2.0",
|
"animejs": "^3.2.0",
|
||||||
"axios": "^0.21.4",
|
"axios": "^0.21.4",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.29.1",
|
||||||
"emoji-datasource": "^6.0.1",
|
"emoji-datasource": "^6.0.1",
|
||||||
"eventemitter3": "^4.0.7",
|
"eventemitter3": "^4.0.7",
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"simple-markdown": "^0.7.2",
|
"simple-markdown": "^0.7.2",
|
||||||
"sweetalert2": "^11.4.14",
|
"sweetalert2": "^11.4.24",
|
||||||
"typed-vuex": "^0.1.21",
|
"typed-vuex": "^0.1.21",
|
||||||
"v-tooltip": "^2.0.3",
|
"v-tooltip": "^2.0.3",
|
||||||
"vue": "^2.6.14",
|
"vue": "^2.7.8",
|
||||||
"vue-class-component": "^7.2.6",
|
"vue-class-component": "^7.2.6",
|
||||||
"vue-clickaway": "^2.2.2",
|
"vue-clickaway": "^2.2.2",
|
||||||
"vue-context": "^5.2.0",
|
"vue-context": "^5.2.0",
|
||||||
"vue-i18n": "^8.27.1",
|
"vue-i18n": "^8.27.2",
|
||||||
"vue-notification": "^1.3.20",
|
"vue-notification": "^1.3.20",
|
||||||
"vue-property-decorator": "^9.1.2",
|
"vue-property-decorator": "^9.1.2",
|
||||||
"vuex": "^3.5.1"
|
"vuex": "^3.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/animejs": "^3.1.4",
|
"@types/animejs": "^3.1.5",
|
||||||
"@types/node": "^14.18.18",
|
"@types/node": "^14.18.23",
|
||||||
"@types/vue": "^2.0.0",
|
"@types/vue": "^2.0.0",
|
||||||
"@types/vue-clickaway": "^2.2.0",
|
"@types/vue-clickaway": "^2.2.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||||
"@typescript-eslint/parser": "^4.33.0",
|
"@typescript-eslint/parser": "^4.33.0",
|
||||||
"@vue/cli-plugin-babel": "^4.5.17",
|
"@vue/cli-plugin-babel": "^4.5.19",
|
||||||
"@vue/cli-plugin-eslint": "^4.5.17",
|
"@vue/cli-plugin-eslint": "^4.5.19",
|
||||||
"@vue/cli-plugin-typescript": "^4.5.17",
|
"@vue/cli-plugin-typescript": "^4.5.19",
|
||||||
"@vue/cli-plugin-vuex": "^4.5.17",
|
"@vue/cli-plugin-vuex": "^4.5.19",
|
||||||
"@vue/cli-service": "^4.5.17",
|
"@vue/cli-service": "^4.5.19",
|
||||||
"@vue/eslint-config-prettier": "^6.0.0",
|
"@vue/eslint-config-prettier": "^6.0.0",
|
||||||
"@vue/eslint-config-typescript": "^7.0.0",
|
"@vue/eslint-config-typescript": "^7.0.0",
|
||||||
"core-js": "^3.22.5",
|
"core-js": "^3.24.1",
|
||||||
"emojilib": "^3.0.6",
|
"emojilib": "^3.0.7",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-plugin-prettier": "^3.4.1",
|
"eslint-plugin-prettier": "^3.4.1",
|
||||||
"eslint-plugin-vue": "^7.20.0",
|
"eslint-plugin-vue": "^7.20.0",
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^2.7.1",
|
||||||
"sass": "^1.51.0",
|
"sass": "^1.54.0",
|
||||||
"sass-loader": "^10.2.1",
|
"sass-loader": "^10.3.1",
|
||||||
"ts-node": "^9.1.1",
|
"ts-node": "^9.1.1",
|
||||||
"typescript": "^4.6.4",
|
"typescript": "^4.7.4",
|
||||||
"vue-template-compiler": "^2.6.14"
|
"vue-template-compiler": "^2.7.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,11 @@
|
|||||||
<neko-emote :id="index" :key="index" />
|
<neko-emote :id="index" :key="index" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<textarea
|
||||||
ref="overlay"
|
ref="overlay"
|
||||||
class="overlay"
|
class="overlay"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
data-gramm="false"
|
||||||
@click.stop.prevent
|
@click.stop.prevent
|
||||||
@contextmenu.stop.prevent
|
@contextmenu.stop.prevent
|
||||||
@wheel.stop.prevent="onWheel"
|
@wheel.stop.prevent="onWheel"
|
||||||
@ -173,6 +174,12 @@
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
cursor: default;
|
||||||
|
outline: 0;
|
||||||
|
border: 0;
|
||||||
|
color: transparent;
|
||||||
|
background: transparent;
|
||||||
|
resize: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-aspect {
|
.player-aspect {
|
||||||
@ -209,7 +216,7 @@
|
|||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
@Ref('component') readonly _component!: HTMLElement
|
@Ref('component') readonly _component!: HTMLElement
|
||||||
@Ref('container') readonly _container!: HTMLElement
|
@Ref('container') readonly _container!: HTMLElement
|
||||||
@Ref('overlay') readonly _overlay!: HTMLElement
|
@Ref('overlay') readonly _overlay!: HTMLTextAreaElement
|
||||||
@Ref('aspect') readonly _aspect!: HTMLElement
|
@Ref('aspect') readonly _aspect!: HTMLElement
|
||||||
@Ref('player') readonly _player!: HTMLElement
|
@Ref('player') readonly _player!: HTMLElement
|
||||||
@Ref('video') readonly _video!: HTMLVideoElement
|
@Ref('video') readonly _video!: HTMLVideoElement
|
||||||
|
@ -1365,60 +1365,7 @@ Guacamole.Keyboard = function Keyboard(element) {
|
|||||||
|
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
/**
|
// NEKO: Do not automatically type text entered into the wrapped field
|
||||||
* Handles the given "input" event, typing the data within the input text.
|
|
||||||
* If the event is complete (text is provided), handling of "compositionend"
|
|
||||||
* events is suspended, as such events may conflict with input events.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {!InputEvent} e
|
|
||||||
* The "input" event to handle.
|
|
||||||
*/
|
|
||||||
var handleInput = function handleInput(e) {
|
|
||||||
|
|
||||||
// Only intercept if handler set
|
|
||||||
if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return;
|
|
||||||
|
|
||||||
// Ignore events which have already been handled
|
|
||||||
if (!markEvent(e)) return;
|
|
||||||
|
|
||||||
// Type all content written
|
|
||||||
if (e.data && !e.isComposing) {
|
|
||||||
element.removeEventListener("compositionend", handleComposition, false);
|
|
||||||
guac_keyboard.type(e.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the given "compositionend" event, typing the data within the
|
|
||||||
* composed text. If the event is complete (composed text is provided),
|
|
||||||
* handling of "input" events is suspended, as such events may conflict
|
|
||||||
* with composition events.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {!CompositionEvent} e
|
|
||||||
* The "compositionend" event to handle.
|
|
||||||
*/
|
|
||||||
var handleComposition = function handleComposition(e) {
|
|
||||||
|
|
||||||
// Only intercept if handler set
|
|
||||||
if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return;
|
|
||||||
|
|
||||||
// Ignore events which have already been handled
|
|
||||||
if (!markEvent(e)) return;
|
|
||||||
|
|
||||||
// Type all content written
|
|
||||||
if (e.data) {
|
|
||||||
element.removeEventListener("input", handleInput, false);
|
|
||||||
guac_keyboard.type(e.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// Automatically type text entered into the wrapped field
|
|
||||||
element.addEventListener("input", handleInput, false);
|
|
||||||
element.addEventListener("compositionend", handleComposition, false);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
4
docs/_media/icons/brave.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="512" cy="512" r="512" style="fill:#fb542b"/>
|
||||||
|
<path d="M586.5 266.2c3 0 5.9 1.3 7.8 3.6l38.4 44.3 1.5-.3c9.2-1.6 18.8-2.2 27.9-1.2 11.7 1.4 21.7 5.5 29.1 13 7.8 7.9 15.5 16 22.8 23.8 2.6 2.7 4.9 5.2 7 7.5.7.8 1.4 1.5 1.9 2.1 3.4 3.8 4.2 8.2 2.7 12l-9.9 24.8 13.2 38.4c.7 2 .8 4.2.2 6.2-.1.4-.1.4-.5 2.1-.6 2.4-.6 2.4-1.5 5.9-1.6 6.3-3.5 13.4-5.4 21-5.6 21.8-11.3 43.6-16.5 63.9-13 50.2-21.5 83.3-23.5 91.6-11.2 44.9-20 60.5-48.6 81-25.1 18-77.4 54-87.4 60.4-1 .6-2 1.3-3.4 2.3-.5.4-3.2 2.2-4 2.7-4.9 3.4-8.4 5.5-12.1 7.3-4.8 2.2-9.3 3.5-14 3.5-4.6 0-9.2-1.3-14-3.5-3.8-1.8-7.2-3.9-12.1-7.3-.8-.5-3.4-2.4-4-2.7-1.4-1-2.5-1.7-3.4-2.3-10.1-6.4-62.4-42.5-87.4-60.4-28.6-20.5-37.5-36.1-48.6-81a49038 49038 0 0 0-23.5-91.2c-5.4-20.7-11-42.5-16.6-64.3-2-7.6-3.8-14.7-5.5-21-.9-3.5-.9-3.5-1.5-5.9-.4-1.7-.4-1.7-.5-2.1-.5-2.1-.4-4.2.2-6.2l13.2-38.4-9.9-24.8c-1.5-3.8-.7-8.2 2-11.2 1.2-1.3 1.9-2 2.6-2.8 2.1-2.2 4.4-4.7 7-7.5 7.3-7.8 15-15.9 22.8-23.8 7.4-7.6 17.4-11.6 29.1-13 9.1-1.1 18.7-.5 27.9 1.2l1.5.3 38.4-44.3c2-2.3 4.8-3.6 7.8-3.6h148.8zm-4.7 21.2H442.2l-39.3 45.5c-2.6 3.1-6.8 4.3-10.6 3.2-.2-.1-.7-.2-1.5-.4-1.3-.3-2.9-.6-4.6-.9-7.4-1.3-15-1.8-21.9-1-7.4.9-13.1 3.2-16.8 7-7.6 7.8-15.3 15.7-22.5 23.4-1.7 1.8-3.3 3.5-4.8 5.1l8.9 22.2c1 2.4 1 5.1.2 7.5l-13.4 39.1c.4 1.4.5 1.9 1.3 4.8 1.6 6.3 3.5 13.4 5.4 21 5.6 21.8 11.3 43.6 16.5 63.9 13 50.4 21.5 83.4 23.6 91.7 10 40.4 16.4 51.6 40.4 68.8 24.8 17.8 76.8 53.6 86.5 59.8 1.2.8 2.5 1.6 4.1 2.7.6.4 3.2 2.2 3.9 2.7 4 2.8 6.7 4.4 9.3 5.6 2.2 1.1 4 1.5 5.1 1.5 1.2 0 2.9-.5 5.1-1.5 2.5-1.2 5.2-2.9 9.2-5.6.7-.5 3.4-2.3 4-2.7 1.6-1.1 2.9-2 4.1-2.7 9.7-6.2 61.7-42 86.5-59.8 24-17.2 30.4-28.4 40.4-68.8 2.1-8.3 10.6-41.4 23.5-91.4 5.4-20.7 11-42.5 16.6-64.3 2-7.6 3.8-14.7 5.4-21 .8-2.9.9-3.4 1.3-4.8l-13.4-39c-.8-2.4-.8-5.1.2-7.5l8.9-22.2c-1.5-1.6-3.1-3.3-4.8-5.1-7.2-7.7-14.9-15.6-22.5-23.4-3.7-3.8-9.4-6.1-16.8-7-6.8-.8-14.5-.3-21.9 1-1.7.3-3.2.6-4.6.9-.8.2-1.3.3-1.5.4-3.9 1.1-8-.2-10.6-3.2l-39.3-45.5zM512 584.7c2.8 0 21 6.5 35.6 14.2 14.6 7.7 25.2 13.1 28.5 15.3 3.4 2.1 1.3 6.2-1.8 8.4s-44.5 34.9-48.5 38.5c-4 3.6-9.9 9.6-13.9 9.6s-9.9-6-13.9-9.6c-4-3.6-45.4-36.2-48.5-38.5-3.1-2.2-5.1-6.3-1.8-8.4 3.4-2.2 14-7.6 28.5-15.3 14.8-7.7 33-14.2 35.8-14.2m.1-232.5c.7 0 8.8.2 20.6 4.2 12.4 4.2 25.9 9.5 32.1 9.5s52.3-9 52.3-9 54.6 67.2 54.6 81.6c0 14.4-6.9 18.2-13.8 25.6-6.9 7.5-37.1 40.1-40.9 44.3-3.9 4.2-11.9 10.5-7.2 22 4.7 11.4 11.7 26 4 40.7-7.8 14.8-21.1 24.6-29.7 23-8.5-1.6-28.6-12.3-36-17.2-7.4-4.9-30.8-24.5-30.8-32s24.2-21 28.6-24.1c4.5-3.1 24.8-14.9 25.2-19.6.4-4.7.3-6-5.8-17.5-6-11.5-16.8-26.9-15-37.1 1.8-10.2 19.3-15.5 31.7-20.3 12.4-4.8 36.4-13.8 39.4-15.2 3-1.4 2.2-2.7-6.8-3.6-9.1-.9-34.8-4.4-46.4-1.1-11.6 3.3-31.4 8.3-33.1 11-1.6 2.7-3 2.7-1.4 11.9 1.7 9.2 10.1 53.2 11 61 .8 7.8 2.4 13-5.8 14.9-8.3 1.9-22.2 5.3-27 5.3-4.8 0-18.7-3.4-27-5.3s-6.7-7.1-5.8-14.9c.8-7.8 9.3-51.9 11-61 1.6-9.2.2-9.3-1.4-11.9-1.6-2.7-21.4-7.7-33.1-11-11.6-3.3-37.4.2-46.4 1.1-9.1.9-9.8 2.2-6.8 3.6 3 1.4 27 10.5 39.4 15.2 12.5 4.8 29.9 10.1 31.7 20.3s-9 25.6-15 37.1-6.2 12.9-5.8 17.5 20.8 16.5 25.2 19.6c4.5 3.1 28.6 16.6 28.6 24.1s-23.4 27.2-30.8 32c-7.4 4.9-27.4 15.6-36 17.2-8.5 1.6-21.9-8.2-29.7-23-7.8-14.8-.8-29.3 4-40.7s-3.3-17.8-7.2-22c-3.9-4.2-34-36.8-40.9-44.3-6.9-7.5-13.8-11.3-13.8-25.6 0-14.4 54.6-81.6 54.6-81.6s46.1 9 52.3 9c6.2 0 19.7-5.3 32.1-9.5 12.6-4.2 20.9-4.2 21-4.2z" style="fill:#fff"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.4 KiB |
1
docs/_media/icons/chromium.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="190px" height="190px" viewBox="1 1 190 190" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="c"><circle cx="96" cy="96" r="88"/></clipPath><linearGradient id="d" x1="110.87" x2="52.54" y1="164.5" y2="130.33" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1a237e" stop-opacity=".4"/><stop offset=".33" stop-color="#1a237e" stop-opacity="0"/></linearGradient><linearGradient id="e" x1="30.43" x2="86.54" y1="74.4" y2="41.61" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1a237e" stop-opacity=".2"/><stop offset=".66" stop-color="#1a237e" stop-opacity="0"/></linearGradient><linearGradient id="f" x1="118.57" x2="54.58" y1="169.11" y2="131.57" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1a237e" stop-opacity=".2"/><stop offset=".33" stop-color="#1a237e" stop-opacity="0"/></linearGradient><clipPath id="g"><use xlink:href="#R"/></clipPath><linearGradient id="a" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1a237e" stop-opacity=".3"/><stop offset=".66" stop-color="#1a237e" stop-opacity="0"/></linearGradient><linearGradient id="h" x1="121.86" x2="136.55" y1="49.8" y2="114.13" xlink:href="#a"/><linearGradient id="i" x1="121.87" x2="136.29" y1="50.07" y2="113.01" xlink:href="#a"/><clipPath id="j"><use xlink:href="#S"/></clipPath><linearGradient id="k" x1="29.34" x2="81.84" y1="75.02" y2="44.35" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1a237e" stop-opacity=".6"/><stop offset=".66" stop-color="#1a237e" stop-opacity="0"/></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1a237e" stop-opacity=".2"/><stop offset="1" stop-color="#1a237e" stop-opacity="0"/></linearGradient><radialGradient id="l" cx="92.18" cy="55.95" r="84.08" xlink:href="#b"/><clipPath id="n"><path d="M61.36 116L96 56h88V8H21.97v40.34"/></clipPath><linearGradient id="m" x1="8" x2="130.65" y1="104.24" y2="104.24" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f4b400" stop-opacity=".4"/><stop offset=".09" stop-color="#f2a700" stop-opacity=".3"/><stop offset=".22" stop-color="#f19800" stop-opacity=".13"/><stop offset=".33" stop-color="#f09300" stop-opacity="0"/></linearGradient><radialGradient id="o" cx="21.87" cy="48.52" r="78.04" xlink:href="#b"/><radialGradient id="p" cx="95.84" cy="96.14" r="87.87" xlink:href="#b"/><radialGradient id="q" cx="34.29" cy="32.01" r="176.75" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff" stop-opacity=".1"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></radialGradient><g clip-path="url(#c)"><use fill="#5e97f6" xlink:href="#R"/><use fill="url(#d)" xlink:href="#R"/><use fill="#3367d6" xlink:href="#T"/><use fill="url(#e)" xlink:href="#T"/><path fill="#1a237e" fill-opacity=".15" d="M62.3 115.65l-39.83-68.3-.58 1 39.54 67.8z"/><use fill="#5e97f6" xlink:href="#R"/><use fill="url(#f)" xlink:href="#R"/><path fill="#1a237e" fill-opacity=".15" d="M129.84 117.33l-.83-.48L90.62 184h1.15l38.1-66.64z"/><g clip-path="url(#g)"><use fill="#a1c2fa" xlink:href="#S"/><use fill="url(#h)" xlink:href="#S"/></g><use fill="#a1c2fa" xlink:href="#S"/><use fill="url(#i)" xlink:href="#S"/><g clip-path="url(#j)"><use fill="#3367d6" xlink:href="#T"/><use fill="url(#k)" xlink:href="#T"/></g><path fill="url(#l)" d="M96 56v20.95L174.4 56z"/><use fill="url(#m)" clip-path="url(#n)" xlink:href="#R"/><path fill="url(#o)" d="M21.97 48.45l57.25 57.24L61.36 116z"/><path fill="url(#p)" d="M91.83 183.9l20.96-78.2 17.86 10.3z"/><circle cx="96" cy="96" r="40" fill="#f5f5f5"/><circle cx="96" cy="96" r="32" fill="#4285f4"/><path fill="#1a237e" fill-opacity=".2" d="M96 55a40 40 0 00-40 40v1a40 40 0 0140-40h88v-1z"/><path fill="#fff" fill-opacity=".1" d="M130.6 116A39.96 39.96 0 0196 136a39.97 39.97 0 01-34.61-20h-.04L8 24.48v1L61.36 117h.04a39.94 39.94 0 0069.21 0h.05v-1z"/><path fill="#1a237e" d="M97 56c-.17 0-.33.02-.5.03a39.98 39.98 0 010 79.94c.17 0 .33.03.5.03a40 40 0 000-80z" opacity=".1"/><path fill="#fff" fill-opacity=".2" d="M131 117.33a39.72 39.72 0 003.5-32.05 39.72 39.72 0 01-3.87 30.69l.02.04-38.87 68h1.16l38.1-66.64zM96 9a88 88 0 0187.99 87.5l.01-.5A88 88 0 008 96v.5A88 88 0 0196 9z"/><path fill="#1a237e" fill-opacity=".15" d="M96 183a88 88 0 0087.99-87.5l.01.5A88 88 0 018 96l.01-.5A88 88 0 0096 183z"/></g><circle cx="96" cy="96" r="88" fill="url(#q)"/><defs><path id="R" d="M8 184h83.77l38.88-38.88V116H61.36L8 24.48z"/><path id="S" d="M96 56l34.65 60-38.88 68H184V56z"/><path id="T" d="M21.97 8v108h39.4L96 56h88V8z"/></defs></svg>
|
After Width: | Height: | Size: 4.5 KiB |
111
docs/_media/icons/firefox.svg
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
|
<svg width="77.42" height="79.97" version="1.1" viewBox="0 0 77.42 79.97" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>Firefox Browser logo</title>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="a" x1="70.79" x2="6.447" y1="12.39" y2="74.47" gradientTransform="translate(-1.3 -.004086)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#fff44f" offset=".048"/>
|
||||||
|
<stop stop-color="#ffe847" offset=".111"/>
|
||||||
|
<stop stop-color="#ffc830" offset=".225"/>
|
||||||
|
<stop stop-color="#ff980e" offset=".368"/>
|
||||||
|
<stop stop-color="#ff8b16" offset=".401"/>
|
||||||
|
<stop stop-color="#ff672a" offset=".462"/>
|
||||||
|
<stop stop-color="#ff3647" offset=".534"/>
|
||||||
|
<stop stop-color="#e31587" offset=".705"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="b" cx="-7907" cy="-8515" r="80.8" gradientTransform="translate(7974 8524)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#ffbd4f" offset=".129"/>
|
||||||
|
<stop stop-color="#ffac31" offset=".186"/>
|
||||||
|
<stop stop-color="#ff9d17" offset=".247"/>
|
||||||
|
<stop stop-color="#ff980e" offset=".283"/>
|
||||||
|
<stop stop-color="#ff563b" offset=".403"/>
|
||||||
|
<stop stop-color="#ff3750" offset=".467"/>
|
||||||
|
<stop stop-color="#f5156c" offset=".71"/>
|
||||||
|
<stop stop-color="#eb0878" offset=".782"/>
|
||||||
|
<stop stop-color="#e50080" offset=".86"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="c" cx="-7937" cy="-8482" r="80.8" gradientTransform="translate(7974 8524)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#960e18" offset=".3"/>
|
||||||
|
<stop stop-color="#b11927" stop-opacity=".74" offset=".351"/>
|
||||||
|
<stop stop-color="#db293d" stop-opacity=".343" offset=".435"/>
|
||||||
|
<stop stop-color="#f5334b" stop-opacity=".094" offset=".497"/>
|
||||||
|
<stop stop-color="#ff3750" stop-opacity="0" offset=".53"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="d" cx="-7927" cy="-8533" r="58.53" gradientTransform="translate(7974 8524)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#fff44f" offset=".132"/>
|
||||||
|
<stop stop-color="#ffdc3e" offset=".252"/>
|
||||||
|
<stop stop-color="#ff9d12" offset=".506"/>
|
||||||
|
<stop stop-color="#ff980e" offset=".526"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="e" cx="-7946" cy="-8461" r="38.47" gradientTransform="translate(7974 8524)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#3a8ee6" offset=".353"/>
|
||||||
|
<stop stop-color="#5c79f0" offset=".472"/>
|
||||||
|
<stop stop-color="#9059ff" offset=".669"/>
|
||||||
|
<stop stop-color="#c139e6" offset="1"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="f" cx="-7936" cy="-8492" r="20.4" gradientTransform="matrix(.972 -.235 .275 1.138 10090 7834)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#9059ff" stop-opacity="0" offset=".206"/>
|
||||||
|
<stop stop-color="#8c4ff3" stop-opacity=".064" offset=".278"/>
|
||||||
|
<stop stop-color="#7716a8" stop-opacity=".45" offset=".747"/>
|
||||||
|
<stop stop-color="#6e008b" stop-opacity=".6" offset=".975"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="g" cx="-7938" cy="-8518" r="27.68" gradientTransform="translate(7974 8524)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#ffe226" offset="0"/>
|
||||||
|
<stop stop-color="#ffdb27" offset=".121"/>
|
||||||
|
<stop stop-color="#ffc82a" offset=".295"/>
|
||||||
|
<stop stop-color="#ffa930" offset=".502"/>
|
||||||
|
<stop stop-color="#ff7e37" offset=".732"/>
|
||||||
|
<stop stop-color="#ff7139" offset=".792"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="h" cx="-7916" cy="-8536" r="118.1" gradientTransform="translate(7974 8524)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#fff44f" offset=".113"/>
|
||||||
|
<stop stop-color="#ff980e" offset=".456"/>
|
||||||
|
<stop stop-color="#ff5634" offset=".622"/>
|
||||||
|
<stop stop-color="#ff3647" offset=".716"/>
|
||||||
|
<stop stop-color="#e31587" offset=".904"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="i" cx="-7927" cy="-8523" r="86.5" gradientTransform="matrix(.105 .995 -.653 .069 -4685 8470)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#fff44f" offset="0"/>
|
||||||
|
<stop stop-color="#ffe847" offset=".06"/>
|
||||||
|
<stop stop-color="#ffc830" offset=".168"/>
|
||||||
|
<stop stop-color="#ff980e" offset=".304"/>
|
||||||
|
<stop stop-color="#ff8b16" offset=".356"/>
|
||||||
|
<stop stop-color="#ff672a" offset=".455"/>
|
||||||
|
<stop stop-color="#ff3647" offset=".57"/>
|
||||||
|
<stop stop-color="#e31587" offset=".737"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="j" cx="-7938" cy="-8508" r="73.72" gradientTransform="translate(7974 8524)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#fff44f" offset=".137"/>
|
||||||
|
<stop stop-color="#ff980e" offset=".48"/>
|
||||||
|
<stop stop-color="#ff5634" offset=".592"/>
|
||||||
|
<stop stop-color="#ff3647" offset=".655"/>
|
||||||
|
<stop stop-color="#e31587" offset=".904"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="k" cx="-7919" cy="-8504" r="80.69" gradientTransform="translate(7974 8524)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#fff44f" offset=".094"/>
|
||||||
|
<stop stop-color="#ffe141" offset=".231"/>
|
||||||
|
<stop stop-color="#ffaf1e" offset=".509"/>
|
||||||
|
<stop stop-color="#ff980e" offset=".626"/>
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient id="l" x1="70.01" x2="15.27" y1="12.06" y2="66.81" gradientTransform="translate(-1.3 -.004086)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#fff44f" stop-opacity=".8" offset=".167"/>
|
||||||
|
<stop stop-color="#fff44f" stop-opacity=".634" offset=".266"/>
|
||||||
|
<stop stop-color="#fff44f" stop-opacity=".217" offset=".489"/>
|
||||||
|
<stop stop-color="#fff44f" stop-opacity="0" offset=".6"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path d="m74.62 26.83c-1.684-4.052-5.1-8.427-7.775-9.81a40.27 40.27 0 0 1 3.925 11.76l7e-3 0.065c-4.382-10.92-11.81-15.33-17.88-24.92-0.307-0.485-0.614-0.971-0.913-1.484-0.171-0.293-0.308-0.557-0.427-0.8a7.053 7.053 0 0 1-0.578-1.535 0.1 0.1 0 0 0-0.088-0.1 0.138 0.138 0 0 0-0.073 0c-5e-3 0-0.013 9e-3 -0.019 0.011s-0.019 0.011-0.028 0.015l0.015-0.026c-9.735 5.7-13.04 16.25-13.34 21.53a19.39 19.39 0 0 0-10.67 4.111 11.59 11.59 0 0 0-1-0.758 17.97 17.97 0 0 1-0.109-9.473 28.7 28.7 0 0 0-9.329 7.21h-0.018c-1.536-1.947-1.428-8.367-1.34-9.708a6.928 6.928 0 0 0-1.294 0.687 28.22 28.22 0 0 0-3.788 3.245 33.84 33.84 0 0 0-3.623 4.347v6e-3 -7e-3a32.73 32.73 0 0 0-5.2 11.74l-0.052 0.256c-0.073 0.341-0.336 2.049-0.381 2.42 0 0.029-6e-3 0.056-9e-3 0.085a36.94 36.94 0 0 0-0.629 5.343v0.2a38.76 38.76 0 0 0 76.95 6.554c0.065-0.5 0.118-0.995 0.176-1.5a39.86 39.86 0 0 0-2.514-19.47zm-44.67 30.34c0.181 0.087 0.351 0.181 0.537 0.264l0.027 0.017q-0.282-0.135-0.564-0.281zm8.878-23.38m31.95-4.934v-0.037l7e-3 0.041z" style="fill:url(#a)"/>
|
||||||
|
<path d="m74.62 26.83c-1.684-4.052-5.1-8.427-7.775-9.81a40.27 40.27 0 0 1 3.925 11.76v0.037l7e-3 0.041a35.1 35.1 0 0 1-1.206 26.16c-4.442 9.531-15.19 19.3-32.02 18.82-18.18-0.515-34.2-14.01-37.19-31.68-0.545-2.787 0-4.2 0.274-6.465a28.88 28.88 0 0 0-0.623 5.348v0.2a38.76 38.76 0 0 0 76.95 6.554c0.065-0.5 0.118-0.995 0.176-1.5a39.86 39.86 0 0 0-2.514-19.47z" style="fill:url(#b)"/>
|
||||||
|
<path d="m74.62 26.83c-1.684-4.052-5.1-8.427-7.775-9.81a40.27 40.27 0 0 1 3.925 11.76v0.037l7e-3 0.041a35.1 35.1 0 0 1-1.206 26.16c-4.442 9.531-15.19 19.3-32.02 18.82-18.18-0.515-34.2-14.01-37.19-31.68-0.545-2.787 0-4.2 0.274-6.465a28.88 28.88 0 0 0-0.623 5.348v0.2a38.76 38.76 0 0 0 76.95 6.554c0.065-0.5 0.118-0.995 0.176-1.5a39.86 39.86 0 0 0-2.514-19.47z" style="fill:url(#c)"/>
|
||||||
|
<path d="m55.78 31.38c0.084 0.059 0.162 0.118 0.241 0.177a21.1 21.1 0 0 0-3.6-4.695c-12.05-12.05-3.157-26.12-1.658-26.84l0.015-0.022c-9.735 5.7-13.04 16.25-13.34 21.53 0.452-0.031 0.9-0.069 1.362-0.069a19.56 19.56 0 0 1 16.98 9.917z" style="fill:url(#d)"/>
|
||||||
|
<path d="m38.82 33.79c-0.064 0.964-3.47 4.289-4.661 4.289-11.02 0-12.81 6.667-12.81 6.667 0.488 5.614 4.4 10.24 9.129 12.68 0.216 0.112 0.435 0.213 0.654 0.312q0.569 0.252 1.138 0.466a17.24 17.24 0 0 0 5.043 0.973c19.32 0.906 23.06-23.1 9.119-30.07a13.38 13.38 0 0 1 9.345 2.269 19.56 19.56 0 0 0-16.98-9.917c-0.46 0-0.91 0.038-1.362 0.069a19.39 19.39 0 0 0-10.67 4.111c0.591 0.5 1.258 1.168 2.663 2.553 2.63 2.591 9.375 5.275 9.39 5.59z" style="fill:url(#e)"/>
|
||||||
|
<path d="m38.82 33.79c-0.064 0.964-3.47 4.289-4.661 4.289-11.02 0-12.81 6.667-12.81 6.667 0.488 5.614 4.4 10.24 9.129 12.68 0.216 0.112 0.435 0.213 0.654 0.312q0.569 0.252 1.138 0.466a17.24 17.24 0 0 0 5.043 0.973c19.32 0.906 23.06-23.1 9.119-30.07a13.38 13.38 0 0 1 9.345 2.269 19.56 19.56 0 0 0-16.98-9.917c-0.46 0-0.91 0.038-1.362 0.069a19.39 19.39 0 0 0-10.67 4.111c0.591 0.5 1.258 1.168 2.663 2.553 2.63 2.591 9.375 5.275 9.39 5.59z" style="fill:url(#f)"/>
|
||||||
|
<path d="m24.96 24.36c0.314 0.2 0.573 0.374 0.8 0.531a17.97 17.97 0 0 1-0.109-9.473 28.7 28.7 0 0 0-9.329 7.21c0.189-5e-3 5.811-0.106 8.638 1.732z" style="fill:url(#g)"/>
|
||||||
|
<path d="m0.354 42.16c2.991 17.67 19.01 31.17 37.19 31.68 16.83 0.476 27.58-9.294 32.02-18.82a35.1 35.1 0 0 0 1.206-26.16v-0.037c0-0.029-6e-3 -0.046 0-0.037l7e-3 0.065c1.375 8.977-3.191 17.67-10.33 23.56l-0.022 0.05c-13.91 11.33-27.22 6.834-29.91 5q-0.282-0.135-0.564-0.281c-8.109-3.876-11.46-11.26-10.74-17.6a9.953 9.953 0 0 1-9.181-5.775 14.62 14.62 0 0 1 14.25-0.572 19.3 19.3 0 0 0 14.55 0.572c-0.015-0.315-6.76-3-9.39-5.59-1.405-1.385-2.072-2.052-2.663-2.553a11.59 11.59 0 0 0-1-0.758c-0.23-0.157-0.489-0.327-0.8-0.531-2.827-1.838-8.449-1.737-8.635-1.732h-0.018c-1.536-1.947-1.428-8.367-1.34-9.708a6.928 6.928 0 0 0-1.294 0.687 28.22 28.22 0 0 0-3.788 3.245 33.84 33.84 0 0 0-3.638 4.337v6e-3 -7e-3a32.73 32.73 0 0 0-5.2 11.74c-0.019 0.079-1.396 6.099-0.717 9.221z" style="fill:url(#h)"/>
|
||||||
|
<path d="m52.42 26.86a21.1 21.1 0 0 1 3.6 4.7c0.213 0.161 0.412 0.321 0.581 0.476 8.787 8.1 4.183 19.55 3.84 20.36 7.138-5.881 11.7-14.58 10.33-23.56-4.384-10.93-11.82-15.34-17.88-24.93-0.307-0.485-0.614-0.971-0.913-1.484-0.171-0.293-0.308-0.557-0.427-0.8a7.053 7.053 0 0 1-0.578-1.535 0.1 0.1 0 0 0-0.088-0.1 0.138 0.138 0 0 0-0.073 0c-5e-3 0-0.013 9e-3 -0.019 0.011s-0.019 0.011-0.028 0.015c-1.499 0.711-10.39 14.79 1.66 26.83z" style="fill:url(#i)"/>
|
||||||
|
<path d="m56.6 32.04c-0.169-0.155-0.368-0.315-0.581-0.476-0.079-0.059-0.157-0.118-0.241-0.177a13.38 13.38 0 0 0-9.345-2.269c13.94 6.97 10.2 30.97-9.119 30.07a17.24 17.24 0 0 1-5.043-0.973q-0.569-0.213-1.138-0.466c-0.219-0.1-0.438-0.2-0.654-0.312l0.027 0.017c2.694 1.839 16 6.332 29.91-5l0.022-0.05c0.347-0.81 4.951-12.26-3.84-20.36z" style="fill:url(#j)"/>
|
||||||
|
<path d="m21.35 44.74s1.789-6.667 12.81-6.667c1.191 0 4.6-3.325 4.661-4.289a19.3 19.3 0 0 1-14.55-0.572 14.62 14.62 0 0 0-14.25 0.572 9.953 9.953 0 0 0 9.181 5.775c-0.718 6.337 2.632 13.72 10.74 17.6 0.181 0.087 0.351 0.181 0.537 0.264-4.733-2.445-8.641-7.069-9.129-12.68z" style="fill:url(#k)"/>
|
||||||
|
<path d="m74.62 26.83c-1.684-4.052-5.1-8.427-7.775-9.81a40.27 40.27 0 0 1 3.925 11.76l7e-3 0.065c-4.382-10.92-11.81-15.33-17.88-24.92-0.307-0.485-0.614-0.971-0.913-1.484-0.171-0.293-0.308-0.557-0.427-0.8a7.053 7.053 0 0 1-0.578-1.535 0.1 0.1 0 0 0-0.088-0.1 0.138 0.138 0 0 0-0.073 0c-5e-3 0-0.013 9e-3 -0.019 0.011s-0.019 0.011-0.028 0.015l0.015-0.026c-9.735 5.7-13.04 16.25-13.34 21.53 0.452-0.031 0.9-0.069 1.362-0.069a19.56 19.56 0 0 1 16.98 9.917 13.38 13.38 0 0 0-9.345-2.269c13.94 6.97 10.2 30.97-9.119 30.07a17.24 17.24 0 0 1-5.043-0.973q-0.569-0.213-1.138-0.466c-0.219-0.1-0.438-0.2-0.654-0.312l0.027 0.017q-0.282-0.135-0.564-0.281c0.181 0.087 0.351 0.181 0.537 0.264-4.733-2.446-8.641-7.07-9.129-12.68 0 0 1.789-6.667 12.81-6.667 1.191 0 4.6-3.325 4.661-4.289-0.015-0.315-6.76-3-9.39-5.59-1.405-1.385-2.072-2.052-2.663-2.553a11.59 11.59 0 0 0-1-0.758 17.97 17.97 0 0 1-0.109-9.473 28.7 28.7 0 0 0-9.329 7.21h-0.018c-1.536-1.947-1.428-8.367-1.34-9.708a6.928 6.928 0 0 0-1.294 0.687 28.22 28.22 0 0 0-3.788 3.245 33.84 33.84 0 0 0-3.623 4.347v6e-3 -7e-3a32.73 32.73 0 0 0-5.2 11.74l-0.052 0.256c-0.073 0.341-0.4 2.073-0.447 2.445v0a45.09 45.09 0 0 0-0.572 5.403v0.2a38.76 38.76 0 0 0 76.95 6.554c0.065-0.5 0.118-0.995 0.176-1.5a39.86 39.86 0 0 0-2.514-19.47zm-3.845 1.991 7e-3 0.041z" style="fill:url(#l)"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 12 KiB |
8
docs/_media/icons/google-chrome.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path fill="#4285F4" d="M11.9733005,16.4144516 C9.59429509,16.4144516 7.65884342,14.4788121 7.65884342,12.0999945 C7.65884342,9.72075429 9.59429509,7.78530262 11.9733005,7.78530262 C14.3523059,7.78530262 16.2879454,9.72075429 16.2879454,12.0999945 C16.2879924,14.4788121 14.3523529,16.4144516 11.9733005,16.4144516 L11.9733005,16.4144516 Z"/>
|
||||||
|
<path fill="#4AAE48" d="M13.7910066,17.1810894 C13.1872494,17.4007528 12.5549364,17.5116882 11.9068426,17.5116882 C10.6352186,17.5116882 9.39370022,17.0652225 8.41129728,16.2548117 C7.61431896,15.5972306 7.02033082,14.7318218 6.69297277,13.7500294 L6.691235,13.7442055 L1.93641793,5.50854377 C0.228238232,8.1357683 -0.377867323,11.2684017 0.230163868,14.3493248 C0.849467072,17.4870306 2.65331799,20.1955546 5.30970885,21.9764857 C6.69353637,22.9040785 8.22657716,23.5227711 9.86735961,23.8160783 L13.7910066,17.1810894 L13.7910066,17.1810894 Z"/>
|
||||||
|
<path fill="#EA3939" d="M22.7599128,6.70666487 C19.7903479,0.731840215 12.5393434,-1.70437065 6.56465968,1.26519432 C5.01832731,2.03375777 3.65958002,3.12347966 2.57441389,4.45616042 L6.59105498,11.413435 C6.85172029,9.39264477 8.25048322,7.60626547 10.3219975,6.95469613 C10.8217704,6.79763941 11.341739,6.71403864 11.865371,6.70666487 L22.7599128,6.70666487 Z"/>
|
||||||
|
<path fill="#FED14B" d="M11.9264747,24 C14.936431,24 17.8171819,22.8712018 20.0368292,20.8218558 C22.2681243,18.7619893 23.6231612,15.9588274 23.8523118,12.9290041 C23.983349,11.1937716 23.7261592,9.41711443 23.1082181,7.78530262 L15.2520944,7.78530262 C16.5738788,8.83162726 17.3494403,10.4306093 17.3416908,12.1250278 C17.3360548,13.3449884 16.9177692,14.5439079 16.1598672,15.5064909 L11.1518525,23.9751076 C11.4097938,23.9916399 11.6696606,24 11.926052,24 L11.9264747,24 L11.9264747,24 Z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
44
docs/_media/icons/microsoft-edge.svg
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg width="256px" height="256px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="linearGradient-1" gradientUnits="userSpaceOnUse" x1="63.3343" y1="757.83" x2="241.6165" y2="757.83" gradientTransform="matrix(1 0 0 1 -4.63 -580.8098)">
|
||||||
|
<stop offset="0" style="stop-color:#0C59A4"/>
|
||||||
|
<stop offset="1" style="stop-color:#114A8B"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="radialGradient-1" cx="161.83" cy="788.4008" r="95.38" gradientTransform="matrix(0.9999 0 0 0.9498 -4.6217 -570.3868)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.72" style="stop-color:#000000;stop-opacity:0"/>
|
||||||
|
<stop offset="0.95" style="stop-color:#000000;stop-opacity:0.53"/>
|
||||||
|
<stop offset="1" style="stop-color:#000000"/>
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient id="linearGradient-2" gradientUnits="userSpaceOnUse" x1="157.4013" y1="680.5561" x2="46.0276" y2="801.8683" gradientTransform="matrix(1 0 0 1 -4.63 -580.8098)">
|
||||||
|
<stop offset="0" style="stop-color:#1B9DE2"/>
|
||||||
|
<stop offset="0.16" style="stop-color:#1595DF"/>
|
||||||
|
<stop offset="0.67" style="stop-color:#0680D7"/>
|
||||||
|
<stop offset="1" style="stop-color:#0078D4"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="radialGradient-2" cx="-773.6357" cy="746.7146" r="143.24" gradientTransform="matrix(0.15 -0.9898 0.8 0.12 -410.7182 -656.3412)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.76" style="stop-color:#000000;stop-opacity:0"/>
|
||||||
|
<stop offset="0.95" style="stop-color:#000000;stop-opacity:0.5"/>
|
||||||
|
<stop offset="1" style="stop-color:#000000"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="radialGradient-3" cx="230.5926" cy="-106.0381" r="202.4299" gradientTransform="matrix(-3.999750e-02 0.9998 -2.1299 -7.998414e-02 -190.7749 -191.6354)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" style="stop-color:#35C1F1"/>
|
||||||
|
<stop offset="0.11" style="stop-color:#34C1ED"/>
|
||||||
|
<stop offset="0.23" style="stop-color:#2FC2DF"/>
|
||||||
|
<stop offset="0.31" style="stop-color:#2BC3D2"/>
|
||||||
|
<stop offset="0.67" style="stop-color:#36C752"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="radialGradient-4" cx="536.3567" cy="-117.7029" r="97.34" gradientTransform="matrix(0.28 0.9598 -0.78 0.23 -1.9279 -410.3179)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" style="stop-color:#66EB6E"/>
|
||||||
|
<stop offset="1" style="stop-color:#66EB6E;stop-opacity:0"/>
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
<g>
|
||||||
|
<path fill="url(#linearGradient-1)" d="M231,190.5c-3.4,1.8-6.9,3.4-10.5,4.7c-11.5,4.3-23.6,6.5-35.9,6.5c-47.3,0-88.5-32.5-88.5-74.3 c0.1-11.4,6.4-21.9,16.4-27.3c-42.8,1.8-53.8,46.4-53.8,72.5c0,73.9,68.1,81.4,82.8,81.4c7.9,0,19.8-2.3,27-4.6l1.3-0.4 c27.6-9.5,51-28.1,66.6-52.8c1.2-1.9,0.6-4.3-1.2-5.5C233.9,189.9,232.3,189.8,231,190.5z"/>
|
||||||
|
<path opacity="0.35" fill="url(#radialGradient-1)" enable-background="new " d="M231,190.5c-3.4,1.8-6.9,3.4-10.5,4.7 c-11.5,4.3-23.6,6.5-35.9,6.5c-47.3,0-88.5-32.5-88.5-74.3c0.1-11.4,6.4-21.9,16.4-27.3c-42.8,1.8-53.8,46.4-53.8,72.5 c0,73.9,68.1,81.4,82.8,81.4c7.9,0,19.8-2.3,27-4.6l1.3-0.4c27.6-9.5,51-28.1,66.6-52.8c1.2-1.9,0.6-4.3-1.2-5.5 C233.9,189.9,232.3,189.8,231,190.5z"/>
|
||||||
|
<path fill="url(#linearGradient-2)" d="M105.7,241.4c-8.9-5.5-16.6-12.8-22.7-21.3c-26.3-36-18.4-86.5,17.6-112.8c3.8-2.7,7.7-5.2,11.9-7.2 c3.1-1.5,8.4-4.1,15.5-4c10.1,0.1,19.6,4.9,25.7,13c4,5.4,6.3,11.9,6.4,18.7c0-0.2,24.5-79.6-80-79.6c-43.9,0-80,41.7-80,78.2 c-0.2,19.3,4,38.5,12.1,56c27.6,58.8,94.8,87.6,156.4,67.1C147.5,256.1,124.5,253.2,105.7,241.4L105.7,241.4z"/>
|
||||||
|
<path opacity="0.41" fill="url(#radialGradient-2)" enable-background="new " d="M105.7,241.4c-8.9-5.5-16.6-12.8-22.7-21.3 c-26.3-36-18.4-86.5,17.6-112.8c3.8-2.7,7.7-5.2,11.9-7.2c3.1-1.5,8.4-4.1,15.5-4c10.1,0.1,19.6,4.9,25.7,13 c4,5.4,6.3,11.9,6.4,18.7c0-0.2,24.5-79.6-80-79.6c-43.9,0-80,41.7-80,78.2c-0.2,19.3,4,38.5,12.1,56 c27.6,58.8,94.8,87.6,156.4,67.1C147.5,256.1,124.5,253.2,105.7,241.4L105.7,241.4z"/>
|
||||||
|
<path fill="url(#radialGradient-3)" d="M152.3,148.9c-0.8,1-3.3,2.5-3.3,5.7c0,2.6,1.7,5.1,4.7,7.2c14.4,10,41.5,8.7,41.6,8.7 c10.7,0,21.1-2.9,30.3-8.3c18.8-11,30.4-31.1,30.4-52.9c0.3-22.4-8-37.3-11.3-43.9C223.5,23.9,177.7,0,128,0C58,0,1,56.2,0,126.2 c0.5-36.5,36.8-66,80-66c3.5,0,23.5,0.3,42,10.1c16.3,8.6,24.9,18.9,30.8,29.2c6.2,10.7,7.3,24.1,7.3,29.5 C160.1,134.3,157.4,142.3,152.3,148.9z"/>
|
||||||
|
<path fill="url(#radialGradient-4)" d="M152.3,148.9c-0.8,1-3.3,2.5-3.3,5.7c0,2.6,1.7,5.1,4.7,7.2c14.4,10,41.5,8.7,41.6,8.7 c10.7,0,21.1-2.9,30.3-8.3c18.8-11,30.4-31.1,30.4-52.9c0.3-22.4-8-37.3-11.3-43.9C223.5,23.9,177.7,0,128,0C58,0,1,56.2,0,126.2 c0.5-36.5,36.8-66,80-66c3.5,0,23.5,0.3,42,10.1c16.3,8.6,24.9,18.9,30.8,29.2c6.2,10.7,7.3,24.1,7.3,29.5 C160.1,134.3,157.4,142.3,152.3,148.9z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.7 KiB |
19
docs/_media/icons/opera.svg
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="100px" height="100px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
|
||||||
|
<g transform="scale(0.521)">
|
||||||
|
<g transform="scale(1.8) translate(3,3)">
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="RG1" cx="50%" cy="50%" fx="50%" fy="50%" r="50%">
|
||||||
|
<stop style="stop-color:rgb(255,29,48);stop-opacity:0.75;" offset="0%"/>
|
||||||
|
<stop style="stop-color:rgb(180,4,15);stop-opacity:1;" offset="100%"/>
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient id="LG1" x1="20%" y1="80%" x2="20%" y2="0%">
|
||||||
|
<stop style="stop-color:rgb(253,76,86);stop-opacity:0.75;" offset="0%"/>
|
||||||
|
<stop style="stop-color:rgb(158,4,4);stop-opacity:1;" offset="100%"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path style="fill:url(#RG1)" d="m 81,11 a 50,50 1 1 0 0,78 a 36.9,45 0 1 1 0,-78"/>
|
||||||
|
<path style="fill:url(#LG1)" d="m 36,19 a 36.9,45 1 0 1 45,-8 a 50,50 1 0 1 0,78 a 36.9,45 1 0 1 -45,-8 a 26.5,35 1 1 0 0,-62"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
BIN
docs/_media/icons/remmina.png
Normal file
After Width: | Height: | Size: 11 KiB |
31
docs/_media/icons/tor-browser.svg
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="512px" height="512px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<defs>
|
||||||
|
<linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1">
|
||||||
|
<stop stop-color="#420C5D" offset="0%"/>
|
||||||
|
<stop stop-color="#951AD1" offset="100%"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path d="M25,29 C152.577777,29 256,131.974508 256,259 C256,386.025492 152.577777,489 25,489 L25,29 Z" id="path-2"/>
|
||||||
|
<filter x="-18.2%" y="-7.4%" width="129.4%" height="114.8%" filterUnits="objectBoundingBox" id="filter-3">
|
||||||
|
<feOffset dx="-8" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"/>
|
||||||
|
<feGaussianBlur stdDeviation="10" in="shadowOffsetOuter1" result="shadowBlurOuter1"/>
|
||||||
|
<feColorMatrix values="0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0.2 0" type="matrix" in="shadowBlurOuter1"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<g id="tor-browser-icon" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="icon_512x512">
|
||||||
|
<g id="Group">
|
||||||
|
<g id="tb_icon/Stable">
|
||||||
|
<g id="Stable">
|
||||||
|
<circle id="background" fill="#F2E4FF" fill-rule="nonzero" cx="256" cy="256" r="246"/>
|
||||||
|
<path d="M256.525143,465.439707 L256.525143,434.406609 C354.826191,434.122748 434.420802,354.364917 434.420802,255.992903 C434.420802,157.627987 354.826191,77.8701558 256.525143,77.5862948 L256.525143,46.5531962 C371.964296,46.8441537 465.446804,140.489882 465.446804,255.992903 C465.446804,371.503022 371.964296,465.155846 256.525143,465.439707 Z M256.525143,356.820314 C311.970283,356.529356 356.8487,311.516106 356.8487,255.992903 C356.8487,200.476798 311.970283,155.463547 256.525143,155.17259 L256.525143,124.146588 C329.115485,124.430449 387.881799,183.338693 387.881799,255.992903 C387.881799,328.654211 329.115485,387.562455 256.525143,387.846316 L256.525143,356.820314 Z M256.525143,201.718689 C286.266674,202.00255 310.3026,226.180407 310.3026,255.992903 C310.3026,285.812497 286.266674,309.990353 256.525143,310.274214 L256.525143,201.718689 Z M0,255.992903 C0,397.384044 114.60886,512 256,512 C397.384044,512 512,397.384044 512,255.992903 C512,114.60886 397.384044,0 256,0 C114.60886,0 0,114.60886 0,255.992903 Z" id="center" fill="url(#linearGradient-1)"/>
|
||||||
|
<g id="half" transform="translate(140.500000, 259.000000) scale(-1, 1) translate(-140.500000, -259.000000) ">
|
||||||
|
<use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-2"/>
|
||||||
|
<use fill="url(#linearGradient-1)" fill-rule="evenodd" xlink:href="#path-2"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
1
docs/_media/icons/vivaldi.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="1900px" height="1900px" viewBox="0 0 1900 1900" xmlns="http://www.w3.org/2000/svg"><path fill="#ef3939" d="M944 1830c386 0 600 0 740-140s140-354 140-740 0-600-140-740S1330 70 944 70s-600 0-740 140S64 564 64 950s0 600 140 740 354 140 740 140z"/><linearGradient id="a" x1="61.24" x2="145.33" y1="37.94" y2="183.58" gradientUnits="userSpaceOnUse"><stop offset="0" stop-opacity=".2"/><stop offset=".79" stop-opacity=".05"/></linearGradient><path fill="url(#a)" d="M151.6 62.4A66 66 0 0030.5 78a65.57 65.57 0 006.8 50.4c.1.2.2.4.4.6l31 53.8 25.5.2c17.1 0 30.9 0 42.2-1.2 14-1.5 24.1-5 31.9-12.8 11.3-11.3 13.5-27.6 13.9-53.8l-30.6-52.8z" transform="scale(10)"/><path fill="#fff" d="M1407 484a657.9 657.9 0 00-932 0 660.9 660.9 0 000 933 657.9 657.9 0 00932 0 660.9 660.9 0 000-933zm-39 304l-326 567c-20 35-49 56-90 59-45 3-80-16-103-55L519 786c-42-73 5-162 89-166 44-2 78 18 101 57 31 52 61 105 91 158l66 114c33 55 80 85 144 89 90 5 174-60 185-156 1-7 1-14 2-18 0-31-6-57-19-82-34-68 2-143 75-160 60-13 121 31 129 91 4 27-1 52-14 75z"/></svg>
|
After Width: | Height: | Size: 1.0 KiB |
107
docs/_media/icons/vlc.svg
Normal file
After Width: | Height: | Size: 237 KiB |
95
docs/_media/icons/xfce.svg
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="760" height="760" id="svg586">
|
||||||
|
<title id="title3704">XFCE 4 Logo</title>
|
||||||
|
<metadata id="metadata12">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||||
|
<dc:title>XFCE 4 Logo</dc:title>
|
||||||
|
<dc:creator>
|
||||||
|
<cc:Agent>
|
||||||
|
<dc:title>Savvas Radevic</dc:title>
|
||||||
|
</cc:Agent>
|
||||||
|
</dc:creator>
|
||||||
|
<dc:source>http://www.xfce.org/about/artwork</dc:source>
|
||||||
|
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/"/>
|
||||||
|
<dc:description>XFCE logo
|
||||||
|
* Based on xfce_logo.svg from http://www.xfce.org/about/artwork
|
||||||
|
* Optimized colours
|
||||||
|
* Added "X" and "XFCE" text.
|
||||||
|
</dc:description>
|
||||||
|
</cc:Work>
|
||||||
|
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/3.0/">
|
||||||
|
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
|
||||||
|
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
|
||||||
|
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
|
||||||
|
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
|
||||||
|
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
|
||||||
|
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
|
||||||
|
</cc:License>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<defs id="defs588">
|
||||||
|
<linearGradient id="linearGradient4380">
|
||||||
|
<stop id="stop4382" style="stop-color:#000000" offset="0"/>
|
||||||
|
<stop id="stop4384" style="stop-color:#000000;stop-opacity:0" offset="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="linearGradient3694">
|
||||||
|
<stop id="stop3702" style="stop-color:#b7b7b7" offset="0"/>
|
||||||
|
<stop id="stop3698" style="stop-color:#000000" offset="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="linearGradient3743">
|
||||||
|
<stop id="stop3745" style="stop-color:#ffffff" offset="0"/>
|
||||||
|
<stop id="stop3751" style="stop-color:#7fd4ee" offset="0.5"/>
|
||||||
|
<stop id="stop3747" style="stop-color:#00aade" offset="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="linearGradient3652">
|
||||||
|
<stop id="stop3654" style="stop-color:#ffffff" offset="0"/>
|
||||||
|
<stop id="stop3662" style="stop-color:#d5e2ec" offset="0.49903482"/>
|
||||||
|
<stop id="stop3656" style="stop-color:#d5e2ec" offset="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<filter color-interpolation-filters="sRGB" id="filter3729">
|
||||||
|
<feGaussianBlur stdDeviation="6.4154088" id="feGaussianBlur3731"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient x1="965.02625" y1="17.489901" x2="1557.8665" y2="663.80927" id="linearGradient3737" xlink:href="#linearGradient3652" gradientUnits="userSpaceOnUse" gradientTransform="translate(-1581.6355,147.78233)"/>
|
||||||
|
<linearGradient x1="33.526711" y1="441.98093" x2="159.24117" y2="581.41302" id="linearGradient3966" xlink:href="#linearGradient3743" gradientUnits="userSpaceOnUse"/>
|
||||||
|
<linearGradient x1="245.51741" y1="426.95151" x2="305.39166" y2="531.48969" id="linearGradient3968" xlink:href="#linearGradient3743" gradientUnits="userSpaceOnUse" gradientTransform="translate(-0.38239847,2.6767893)"/>
|
||||||
|
<linearGradient x1="601.53467" y1="434.94836" x2="668.81775" y2="543.35834" id="linearGradient3970" xlink:href="#linearGradient3743" gradientUnits="userSpaceOnUse"/>
|
||||||
|
<linearGradient x1="440.52084" y1="455.93307" x2="486.95523" y2="535.30469" id="linearGradient3972" xlink:href="#linearGradient3743" gradientUnits="userSpaceOnUse" gradientTransform="translate(-0.50986462,6.1183754)"/>
|
||||||
|
<linearGradient x1="118.25153" y1="254.24648" x2="291.87143" y2="254.24648" id="linearGradient4386" xlink:href="#linearGradient4380" gradientUnits="userSpaceOnUse"/>
|
||||||
|
<filter color-interpolation-filters="sRGB" id="filter4631">
|
||||||
|
<feGaussianBlur id="feGaussianBlur4633" stdDeviation="2.3336003"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient x1="174.28104" y1="164.16707" x2="236.40213" y2="253.70575" id="linearGradient4162" xlink:href="#linearGradient3694" gradientUnits="userSpaceOnUse" gradientTransform="matrix(3.7524926,0,0,3.7524926,-424.92228,-686.03042)"/>
|
||||||
|
<filter color-interpolation-filters="sRGB" id="filter4223">
|
||||||
|
<feGaussianBlur id="feGaussianBlur4225" stdDeviation="4.6672006"/>
|
||||||
|
</filter>
|
||||||
|
<filter color-interpolation-filters="sRGB" id="filter4266">
|
||||||
|
<feGaussianBlur id="feGaussianBlur4268" stdDeviation="1.308628"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<g transform="translate(754.35152,-70.104999)" id="g3733">
|
||||||
|
<path d="M -688.36669,231.76247 -533.89627,88.530764 -359.32377,303.30745 -170.75037,88.081185 -25.665766,221.31553 -234.39997,435.40777 -20.542666,663.49077 -175.64877,807.89088 -372.21127,579.61811 -569.06737,810.83624 -709.17754,672.17796 -494.29357,442.77473 -688.36669,231.76247 z" id="path2870-8" style="filter:url(#filter3729)"/>
|
||||||
|
<path d="M -692.09302,232.01783 -537.6226,88.786116 -363.0501,303.5628 -174.4767,88.336537 -29.392112,221.57089 -238.1263,435.66312 -24.269012,663.74612 -179.3751,808.14623 -375.9376,579.87346 -572.7937,811.09159 -712.90387,672.43331 -498.0199,443.03008 -692.09302,232.01783 z" id="path2870" style="fill:url(#linearGradient3737)"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(20.600025,32.751608)" id="g3915">
|
||||||
|
<g transform="translate(6,4.5)" id="g3803-6" style="opacity:0.8;filter:url(#filter4223)">
|
||||||
|
<path d="m 58.345585,417.63579 56.432265,69.38376 56.45069,-66.66449 40.49963,40.30749 -61.78078,65.91662 65.45855,68.01565 -42.74601,42.50269 L 108.5111,564.35998 48.268494,638.64971 7.1899623,597.32826 74.708237,531.54185 13.029005,462.45552 z" id="path3912" style="stroke:#000000;stroke-width:3"/>
|
||||||
|
<path d="m 245.32235,432.76045 c 0,0 117.20479,-1.21951 117.20479,-0.74369 0,0.47308 -0.46199,46.74243 -0.46199,46.74243 l -56.15161,2.88037 -0.17056,36.17 53.92068,0.57225 0.17335,44.40991 -53.06171,1.1226 -0.0416,75.28312 -62.11058,-0.88382 c 0,0 -0.24879,-205.55317 0.69921,-205.55317 z" id="path3914" style="stroke:#000000;stroke-width:3"/>
|
||||||
|
<path d="m 542.33906,431.37943 c 3.7919,18.02364 7.581,36.04727 11.373,54.07091 -36.2734,12.22845 -88.71272,15.00605 -91.24882,68.09506 6.6716,52.01081 70.35438,34.37103 98.68398,36.76942 0.4726,17.23426 0.43804,33.70097 0.91074,50.93523 C 435.29236,657.93973 400.36214,596.11929 398.22454,573.7307 385.72484,472.89955 486.80926,443.52538 542.33906,431.37943 z" id="path3916" style="stroke:#000000;stroke-width:3"/>
|
||||||
|
<path d="m 597.26819,428.94061 116.39026,-0.42207 -1.34547,46.26995 -56.4993,0.0539 0.79926,34.59126 48.82809,-0.68549 -0.56651,45.70916 -48.37488,0.80727 0.4754,32.7275 57.10463,0.0964 0.11331,50.46219 -118.30011,-3.34717 z" id="path3918" style="stroke:#000000;stroke-width:3"/>
|
||||||
|
</g>
|
||||||
|
<g id="g4621">
|
||||||
|
<path d="m 58.345585,417.63579 56.432265,69.38376 56.45069,-66.66449 40.49963,40.30749 -61.78078,65.91662 65.45855,68.01565 -42.74601,42.50269 L 108.5111,564.35998 48.268494,638.64971 7.1899623,597.32826 74.708237,531.54185 13.029005,462.45552 z" id="path4623" style="fill:url(#linearGradient3966);stroke:#00aade;stroke-width:3"/>
|
||||||
|
<path d="m 245.32235,432.76045 c 0,0 117.20479,-1.21951 117.20479,-0.74369 0,0.47308 -0.46199,46.74243 -0.46199,46.74243 l -56.15161,2.88037 -0.17056,36.17 53.92068,0.57225 0.17335,44.40991 -53.06171,1.1226 -0.0416,75.28312 -62.11058,-0.88382 c 0,0 -0.24879,-205.55317 0.69921,-205.55317 z" id="path4625" style="fill:url(#linearGradient3968);stroke:#00aade;stroke-width:3"/>
|
||||||
|
<path d="m 542.33906,431.37943 c 3.7919,18.02364 7.581,36.04727 11.373,54.07091 -36.2734,12.22845 -88.71272,15.00605 -91.24882,68.09506 6.6716,52.01081 70.35438,34.37103 98.68398,36.76942 0.4726,17.23426 0.43804,33.70097 0.91074,50.93523 C 435.29236,657.93973 400.36214,596.11929 398.22454,573.7307 385.72484,472.89955 486.80926,443.52538 542.33906,431.37943 z" id="path4627" style="fill:url(#linearGradient3972);stroke:#00aade;stroke-width:3"/>
|
||||||
|
<path d="m 597.26819,428.94061 116.39026,-0.42207 -1.34547,46.26995 -56.4993,0.0539 0.79926,34.59126 48.82809,-0.68549 -0.56651,45.70916 -48.37488,0.80727 0.4754,32.7275 57.10463,0.0964 0.11331,50.46219 -118.30011,-3.34717 z" id="path4629" style="fill:url(#linearGradient3970);stroke:#00aade;stroke-width:3"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(3.7524926,0,0,3.7524926,-424.92228,-686.03042)" id="g23" style="fill:url(#linearGradient4386);stroke:#000000;stroke-width:0.26814505">
|
||||||
|
<use transform="translate(1.8654516,1.1992028)" id="use4246" style="opacity:0.8;filter:url(#filter4266)" x="0" y="0" width="760" height="760" xlink:href="#path14"/>
|
||||||
|
<path d="m 118.94557,209.56947 c -0.915,0.501 7.2975,5.88275 22.6875,12.21875 15.391,6.336 36.667,22.37675 35,24.96875 -4.999,7.041 -7.94125,15.48225 -5.90625,29.90625 0.367,2.539 -7.54275,6.95025 -6.96875,10.78125 0.48,2.915 8.838,-2.896 9.75,-1.75 0.558,2.057 -4.4665,12.47475 -2.4375,13.21875 1.484,0.441 14.52325,-12.4015 15.28125,-12.1875 21.514,6.504 49.5205,1.61225 50.0625,1.40625 -0.051,-0.113 20.12,9.49175 21.875,8.34375 4.427,-4.794 -8.387,-11.551 -6.25,-14.25 0.937,-1.413 15.62075,5.30275 16.34375,3.46875 1.052,-3.175 -6.817,-9.8375 -8,-11.8125 -1.029,-1.821 -1.23275,-4.75375 1.40625,-6.59375 2.64,-1.84 29.632,-12.16625 28.5,-20.15625 -1.957,-14.518 -34.26025,-15.4655 -34.78125,-16.6875 -0.245,-0.574 1.44825,-20.76025 -7.96875,-17.03125 -5.798,2.158 -1.09375,15.0245 -0.71875,16.6875 0.346,1.531 -2.574,1.3595 -3.125,0.6875 -1.23,-1.502 -5.2725,-14.92525 -10.0625,-12.53125 -6.718,3.704 -0.41,15.28125 -1.75,15.65625 -2.489,1.391 -2.34875,4.7945 -4.34375,7.5625 -3.29,4.715 -27.917,-2.2055 -35.875,-2.6875 -5.826,-0.344 -11.38025,4.4205 -11.90625,3.8125 -7.094,-7.104 -21.76475,-19.48475 -37.84375,-23.59375 -9.017,-2.447 -22.43075,-9.5275 -22.96875,-9.4375 z m 164.21875,13 c -0.53716,0.23183 -2.70444,11.39669 -1.65625,11.90625 0.929,0.695 2.5355,-11.257 1.6875,-11.875 -0.0138,-0.016 -0.0139,-0.0387 -0.0313,-0.0313 z m 7.96875,1.53125 c -1.21841,-0.0224 -7.71547,10.3605 -6.21875,11.46875 0.929,0.695 6.7105,-11.0445 6.3125,-11.4375 -0.0278,-0.023 -0.0544,-0.0305 -0.0937,-0.0313 z m -31.5,12.65625 c 1.53924,0 2.78125,1.40374 2.78125,3.15625 0,1.75251 -1.24201,3.1875 -2.78125,3.1875 -1.53924,0 -2.78125,-1.43499 -2.78125,-3.1875 0,-1.75251 1.24201,-3.15625 2.78125,-3.15625 z m -6.25,1.90625 c 1.184,5.111 3.0165,6.7255 8.5625,6.5625 -7.059,6.473 -11.3335,-4.6035 -8.5625,-6.5625 z m 14.9375,13.375 c 0.43934,-0.0356 0.77562,0.0333 0.96875,0.21875 2.781,1.008 -11.93325,6.3115 -12.03125,5.5625 -0.14787,-0.51625 7.98709,-5.53182 11.0625,-5.78125 z m 1.8125,3.375 c 0.15222,-0.0118 0.29388,0.0358 0.40625,0.0937 2.625,2.608 -8.3105,8.3905 -8.1875,8.1875 -0.88125,-0.5325 5.498,-8.1041 7.78125,-8.28125 z" id="path14" style="fill:#000000;fill-rule:evenodd;stroke:#000000;stroke-width:0.26814505"/>
|
||||||
|
<path d="m 21.40625,100.375 c -3.433531,1.88 27.405389,22.06796 85.15625,45.84375 57.75461,23.77579 137.59916,83.99229 131.34375,93.71875 -18.75871,26.4213 -29.82382,58.0928 -22.1875,112.21875 1.37716,9.52758 -28.27893,26.0617 -26.125,40.4375 1.62157,9.84768 27.16679,-6.83583 34.75,-7.125 C 211.59347,288.40086 380.71993,233.48854 524.375,211.125 c 0.42249,-0.61852 0.9417,-1.14571 1.53125,-1.5625 0.10346,0.44662 0.20625,0.84878 0.3125,1.28125 4.64119,-0.71462 9.26483,-1.41562 13.84375,-2.0625 1.73622,-3.79693 5.24284,-6.375 9.28125,-6.375 3.18263,0 6.02524,1.58871 7.9375,4.125 23.87521,-2.99523 46.50573,-5.06548 66.84375,-6.21875 -37.95486,-16.11541 -88.98683,-18.63101 -90.25,-21.59375 -0.91936,-2.15393 5.43097,-77.89929 -29.90625,-63.90625 -21.75695,8.09788 -4.09468,56.35335 -2.6875,62.59375 1.29836,5.74507 -9.68238,5.11543 -11.75,2.59375 -4.61557,-5.63624 -19.77556,-56.01472 -37.75,-47.03125 -25.20925,13.89923 -1.53416,57.34282 -6.5625,58.75 -9.33995,5.21972 -8.82628,17.9881 -16.3125,28.375 -12.3457,17.693 -104.76266,-8.2538 -134.625,-10.0625 -21.86202,-1.29086 -42.68244,16.56277 -44.65625,14.28125 -26.62018,-26.65771 -81.66367,-73.11226 -142,-88.53125 C 73.788774,126.5989 23.425091,100.03728 21.40625,100.375 z" transform="matrix(0.26648953,0,0,0.26648953,113.23734,182.81993)" id="path4083" style="fill:url(#linearGradient4162)"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 12 KiB |
@ -2,6 +2,17 @@
|
|||||||
|
|
||||||
## master branch
|
## master branch
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
- Added `m1k1o/neko:vivaldi` tag (thanks @Xeddius).
|
||||||
|
- Added `m1k1o/neko:opera` tag (thanks @prophetofxenu).
|
||||||
|
- Added `NEKO_PATH_PREFIX`.
|
||||||
|
- Added screenshot function `/screenshot.jpg?pwd=<admin>`, works only for unlocked rooms.
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
- Server: Split `remote` to `desktop` and `capture`.
|
||||||
|
- Server: Refactored `xorg` - added `xevent` and clipboard is handled as event (no looped polling anymore).
|
||||||
|
- Introduced `NEKO_AUDIO_CODEC=` and `NEKO_VIDEO_CODEC=` as a new way of setting codecs.
|
||||||
|
|
||||||
## [n.eko v2.6](https://github.com/m1k1o/neko/releases/tag/v2.6)
|
## [n.eko v2.6](https://github.com/m1k1o/neko/releases/tag/v2.6)
|
||||||
|
|
||||||
### Bugs
|
### Bugs
|
||||||
|
@ -1,5 +1,19 @@
|
|||||||
# Getting started & FAQ
|
# Getting started & FAQ
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="../_media/icons/firefox.svg" title="m1k1o/neko:firefox" width="60" height="auto"/>
|
||||||
|
<img src="../_media/icons/google-chrome.svg" title="m1k1o/neko:google-chrome" width="60" height="auto"/>
|
||||||
|
<img src="../_media/icons/chromium.svg" title="m1k1o/neko:chromium" width="60" height="auto"/>
|
||||||
|
<img src="../_media/icons/microsoft-edge.svg" title="m1k1o/neko:microsoft-edge" width="60" height="auto"/>
|
||||||
|
<img src="../_media/icons/brave.svg" title="m1k1o/neko:brave" width="60" height="auto"/>
|
||||||
|
<img src="../_media/icons/vivaldi.svg" title="m1k1o/neko:vivaldi" width="60" height="auto"/>
|
||||||
|
<img src="../_media/icons/opera.svg" title="m1k1o/neko:opera" width="60" height="auto"/>
|
||||||
|
<img src="../_media/icons/tor-browser.svg" title="m1k1o/neko:tor-browser" width="60" height="auto"/>
|
||||||
|
<img src="../_media/icons/remmina.png" title="m1k1o/neko:remmina" width="60" height="auto"/>
|
||||||
|
<img src="../_media/icons/vlc.svg" title="m1k1o/neko:vlc" width="60" height="auto"/>
|
||||||
|
<img src="../_media/icons/xfce.svg" title="m1k1o/neko:xfce" width="60" height="auto"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
Use the following docker images:
|
Use the following docker images:
|
||||||
- `m1k1o/neko:latest` or `m1k1o/neko:firefox` - for Firefox.
|
- `m1k1o/neko:latest` or `m1k1o/neko:firefox` - for Firefox.
|
||||||
- `m1k1o/neko:chromium` - for Chromium (needs `--cap-add=SYS_ADMIN`, see the [security implications](https://www.redhat.com/en/blog/container-tidbits-adding-capabilities-container)).
|
- `m1k1o/neko:chromium` - for Chromium (needs `--cap-add=SYS_ADMIN`, see the [security implications](https://www.redhat.com/en/blog/container-tidbits-adding-capabilities-container)).
|
||||||
@ -7,6 +21,8 @@ Use the following docker images:
|
|||||||
- `m1k1o/neko:ungoogled-chromium` - for [Ungoogled Chromium](https://github.com/Eloston/ungoogled-chromium) (needs `--cap-add=SYS_ADMIN`, see the [security implications](https://www.redhat.com/en/blog/container-tidbits-adding-capabilities-container)) (by @whalehub).
|
- `m1k1o/neko:ungoogled-chromium` - for [Ungoogled Chromium](https://github.com/Eloston/ungoogled-chromium) (needs `--cap-add=SYS_ADMIN`, see the [security implications](https://www.redhat.com/en/blog/container-tidbits-adding-capabilities-container)) (by @whalehub).
|
||||||
- `m1k1o/neko:microsoft-edge` - for Microsoft Edge (needs `--cap-add=SYS_ADMIN`, see the [security implications](https://www.redhat.com/en/blog/container-tidbits-adding-capabilities-container)).
|
- `m1k1o/neko:microsoft-edge` - for Microsoft Edge (needs `--cap-add=SYS_ADMIN`, see the [security implications](https://www.redhat.com/en/blog/container-tidbits-adding-capabilities-container)).
|
||||||
- `m1k1o/neko:brave` - for [Brave Browser](https://brave.com) (needs `--cap-add=SYS_ADMIN`, see the [security implications](https://www.redhat.com/en/blog/container-tidbits-adding-capabilities-container)).
|
- `m1k1o/neko:brave` - for [Brave Browser](https://brave.com) (needs `--cap-add=SYS_ADMIN`, see the [security implications](https://www.redhat.com/en/blog/container-tidbits-adding-capabilities-container)).
|
||||||
|
- `m1k1o/neko:vivaldi` - for [Vivaldi Browser](https://vivaldi.com) (needs `--cap-add=SYS_ADMIN`, see the [security implications](https://www.redhat.com/en/blog/container-tidbits-adding-capabilities-container)) (by @Xeddius).
|
||||||
|
- `m1k1o/neko:opera` for [Opera Browser](https://opera.com) (requires extra steps to enable DRM, see instructions [here](https://www.reddit.com/r/operabrowser/wiki/opera/linux_widevine_config/). libffmpeg is already configured.) (by @prophetofxenu)
|
||||||
- `m1k1o/neko:tor-browser` - for Tor Browser.
|
- `m1k1o/neko:tor-browser` - for Tor Browser.
|
||||||
- `m1k1o/neko:remmina` - for remote desktop connection (by @lowne).
|
- `m1k1o/neko:remmina` - for remote desktop connection (by @lowne).
|
||||||
- Pass env var `REMMINA_URL=<proto>://[<username>[:<password>]@]server[:port]` (proto being `vnc`, `rdp` or `spice`).
|
- Pass env var `REMMINA_URL=<proto>://[<username>[:<password>]@]server[:port]` (proto being `vnc`, `rdp` or `spice`).
|
||||||
@ -37,10 +53,9 @@ Images (except `arm-`) are built using GitHub actions on every push and on weekl
|
|||||||
- You can change API port (8080).
|
- You can change API port (8080).
|
||||||
- This **WILL** work: `3000:8080`
|
- This **WILL** work: `3000:8080`
|
||||||
|
|
||||||
#### But there is a hope!
|
### Using mux instead of epr
|
||||||
There has been an attempt to implement [single port ice using tcp and udp mux](https://github.com/m1k1o/neko/commit/c97b1fc4541caabf6b00331d081b02d2f9c58751) ([#106](https://github.com/m1k1o/neko/pull/106)), that allows using one port instead (each for TCP and/or UDP). This feature is not properly tested yet and only experimental.
|
|
||||||
|
|
||||||
We can use TCP mux and/or UDP mux, example:
|
When using a mux, not so many ports are needed.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: "3.4"
|
version: "3.4"
|
||||||
@ -62,6 +77,13 @@ services:
|
|||||||
NEKO_ICELITE: 1
|
NEKO_ICELITE: 1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- When using mux, `NEKO_EPR` is ignored.
|
||||||
|
- You only need to expose maximum two ports for WebRTC on your router/firewall and have many users connected.
|
||||||
|
- It can even be the same port number, so e.g. `NEKO_TCPMUX: 8081` and `NEKO_UDPMUX: 8081`.
|
||||||
|
- You can use them alone (either TCP or UDP) when needed.
|
||||||
|
- UDP is generally better for latency. But some networks block UDP so it is good to have TCP available as fallback.
|
||||||
|
- Still, using `NEKO_ICELITE=true` is recommended.
|
||||||
|
|
||||||
### Want to customize and install own add-ons, set custom bookmarks?
|
### Want to customize and install own add-ons, set custom bookmarks?
|
||||||
- You would need to modify the existing policy file and mount it to your container.
|
- You would need to modify the existing policy file and mount it to your container.
|
||||||
- For Firefox, copy [this](https://github.com/m1k1o/neko/blob/master/.docker/firefox/policies.json) file, modify and mount it as: ` -v '${PWD}/policies.json:/usr/lib/firefox/distribution/policies.json'`
|
- For Firefox, copy [this](https://github.com/m1k1o/neko/blob/master/.docker/firefox/policies.json) file, modify and mount it as: ` -v '${PWD}/policies.json:/usr/lib/firefox/distribution/policies.json'`
|
||||||
|
@ -64,15 +64,10 @@ nat1to1: <ip>
|
|||||||
|
|
||||||
### Video
|
### Video
|
||||||
|
|
||||||
#### `NEKO_VP8`:
|
#### `NEKO_VIDEO_CODEC`:
|
||||||
- If vp8 should be used as video encoder for the stream *(default encoder)*.
|
- vp8 *(default encoder)*
|
||||||
- e.g. `true`
|
- vp9 *(parameter not optimized yet)*
|
||||||
#### `NEKO_VP9`:
|
- h264 *(second best option)*
|
||||||
- If vp9 should be used as video encoder for the stream *(parameter not optimized yet)*.
|
|
||||||
- e.g. `false`
|
|
||||||
#### `NEKO_H264`:
|
|
||||||
- If h264 should be used as video encoder for the stream *(second best option)*.
|
|
||||||
- e.g. `false`
|
|
||||||
#### `NEKO_VIDEO_BITRATE`:
|
#### `NEKO_VIDEO_BITRATE`:
|
||||||
- Bitrate of the video stream in kb/s.
|
- Bitrate of the video stream in kb/s.
|
||||||
- e.g. 3500
|
- e.g. 3500
|
||||||
@ -93,18 +88,11 @@ nat1to1: <ip>
|
|||||||
|
|
||||||
### Audio
|
### Audio
|
||||||
|
|
||||||
#### `NEKO_OPUS`:
|
#### `NEKO_AUDIO_CODEC`:
|
||||||
- If opus should be used as audio encoder for the stream *(default encoder)*.
|
- opus *(default encoder)*
|
||||||
- e.g. `true`
|
- g722
|
||||||
#### `NEKO_G722`:
|
- pcmu
|
||||||
- If g722 should be used as audio encoder for the stream.
|
- pcma
|
||||||
- e.g. `false`
|
|
||||||
#### `NEKO_PCMU`:
|
|
||||||
- If pcmu should be used as audio encoder for the stream.
|
|
||||||
- e.g. `false`
|
|
||||||
#### `NEKO_PCMA`:
|
|
||||||
- If pcma should be used as audio encoder for the stream.
|
|
||||||
- e.g. `false`
|
|
||||||
#### `NEKO_AUDIO_BITRATE`:
|
#### `NEKO_AUDIO_BITRATE`:
|
||||||
- Bitrate of the audio stream in kb/s.
|
- Bitrate of the audio stream in kb/s.
|
||||||
- e.g. `196`
|
- e.g. `196`
|
||||||
@ -134,6 +122,9 @@ nat1to1: <ip>
|
|||||||
#### `NEKO_PROXY`:
|
#### `NEKO_PROXY`:
|
||||||
- Enable reverse proxy mode, so that neko trusts `X-Forwarded-For` headers.
|
- Enable reverse proxy mode, so that neko trusts `X-Forwarded-For` headers.
|
||||||
- e.g. `false`
|
- e.g. `false`
|
||||||
|
#### `NEKO_PATH_PREFIX`:
|
||||||
|
- Path prefix for HTTP requests.
|
||||||
|
- e.g. `/neko/`
|
||||||
|
|
||||||
### Expert settings
|
### Expert settings
|
||||||
|
|
||||||
@ -144,7 +135,7 @@ nat1to1: <ip>
|
|||||||
#### `NEKO_STATIC`:
|
#### `NEKO_STATIC`:
|
||||||
- Path to neko client files to serve.
|
- Path to neko client files to serve.
|
||||||
|
|
||||||
## Agruments
|
## Arguments
|
||||||
|
|
||||||
You can execute `neko serve --help` to see available arguments.
|
You can execute `neko serve --help` to see available arguments.
|
||||||
|
|
||||||
@ -155,6 +146,7 @@ Usage:
|
|||||||
Flags:
|
Flags:
|
||||||
--audio string audio codec parameters to use for streaming
|
--audio string audio codec parameters to use for streaming
|
||||||
--audio_bitrate int audio bitrate in kbit/s (default 128)
|
--audio_bitrate int audio bitrate in kbit/s (default 128)
|
||||||
|
--audio_codec string audio codec to be used (default "opus")
|
||||||
--bind string address/port/socket to serve neko (default "127.0.0.1:8080")
|
--bind string address/port/socket to serve neko (default "127.0.0.1:8080")
|
||||||
--broadcast_pipeline string custom gst pipeline used for broadcasting, strings {url} {device} {display} will be replaced
|
--broadcast_pipeline string custom gst pipeline used for broadcasting, strings {url} {device} {display} will be replaced
|
||||||
--broadcast_url string URL for broadcasting, setting this value will automatically enable broadcasting
|
--broadcast_url string URL for broadcasting, setting this value will automatically enable broadcasting
|
||||||
@ -163,8 +155,8 @@ Flags:
|
|||||||
--device string audio device to capture (default "auto_null.monitor")
|
--device string audio device to capture (default "auto_null.monitor")
|
||||||
--display string XDisplay to capture (default ":99.0")
|
--display string XDisplay to capture (default ":99.0")
|
||||||
--epr string limits the pool of ephemeral ports that ICE UDP connections can allocate from (default "59000-59100")
|
--epr string limits the pool of ephemeral ports that ICE UDP connections can allocate from (default "59000-59100")
|
||||||
--g722 use G722 audio codec
|
--g722 DEPRECATED: use audio_codec
|
||||||
--h264 use H264 video codec
|
--h264 DEPRECATED: use video_codec
|
||||||
-h, --help help for serve
|
-h, --help help for serve
|
||||||
--hwenc string use hardware accelerated encoding
|
--hwenc string use hardware accelerated encoding
|
||||||
--icelite configures whether or not the ice agent should be a lite agent
|
--icelite configures whether or not the ice agent should be a lite agent
|
||||||
@ -176,11 +168,12 @@ Flags:
|
|||||||
--locks strings resources, that will be locked when starting (control, login)
|
--locks strings resources, that will be locked when starting (control, login)
|
||||||
--max_fps int maximum fps delivered via WebRTC, 0 is for no maximum (default 25)
|
--max_fps int maximum fps delivered via WebRTC, 0 is for no maximum (default 25)
|
||||||
--nat1to1 strings sets a list of external IP addresses of 1:1 (D)NAT and a candidate type for which the external IP address is used
|
--nat1to1 strings sets a list of external IP addresses of 1:1 (D)NAT and a candidate type for which the external IP address is used
|
||||||
--opus use Opus audio codec
|
--opus DEPRECATED: use audio_codec
|
||||||
--password string password for connecting to stream (default "neko")
|
--password string password for connecting to stream (default "neko")
|
||||||
--password_admin string admin password for connecting to stream (default "admin")
|
--password_admin string admin password for connecting to stream (default "admin")
|
||||||
--pcma use PCMA audio codec
|
--path_prefix string path prefix for HTTP requests (default "/")
|
||||||
--pcmu use PCMU audio codec
|
--pcma DEPRECATED: use audio_codec
|
||||||
|
--pcmu DEPRECATED: use audio_codec
|
||||||
--proxy enable reverse proxy mode
|
--proxy enable reverse proxy mode
|
||||||
--screen string default screen resolution and framerate (default "1280x720@30")
|
--screen string default screen resolution and framerate (default "1280x720@30")
|
||||||
--static string path to neko client files to serve (default "./www")
|
--static string path to neko client files to serve (default "./www")
|
||||||
@ -188,8 +181,9 @@ Flags:
|
|||||||
--udpmux int single UDP mux port for all peers
|
--udpmux int single UDP mux port for all peers
|
||||||
--video string video codec parameters to use for streaming
|
--video string video codec parameters to use for streaming
|
||||||
--video_bitrate int video bitrate in kbit/s (default 3072)
|
--video_bitrate int video bitrate in kbit/s (default 3072)
|
||||||
--vp8 use VP8 video codec
|
--video_codec string video codec to be used (default "vp8")
|
||||||
--vp9 use VP9 video codec
|
--vp8 DEPRECATED: use video_codec
|
||||||
|
--vp9 DEPRECATED: use video_codec
|
||||||
|
|
||||||
Global Flags:
|
Global Flags:
|
||||||
--config string configuration file path
|
--config string configuration file path
|
||||||
|
@ -98,7 +98,7 @@ services:
|
|||||||
! v4l2h264enc extra-controls="controls,h264_profile=0,video_bitrate=1250000;"
|
! v4l2h264enc extra-controls="controls,h264_profile=0,video_bitrate=1250000;"
|
||||||
! h264parse config-interval=3
|
! h264parse config-interval=3
|
||||||
! video/x-h264,profile=baseline,stream-format=byte-stream
|
! video/x-h264,profile=baseline,stream-format=byte-stream
|
||||||
NEKO_H264: 1
|
NEKO_VIDEO_CODEC: h264
|
||||||
```
|
```
|
||||||
|
|
||||||
## Not using docker?
|
## Not using docker?
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"m1k1o/neko"
|
"m1k1o/neko"
|
||||||
"m1k1o/neko/internal/types/config"
|
"m1k1o/neko/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -19,8 +19,8 @@ func init() {
|
|||||||
configs := []config.Config{
|
configs := []config.Config{
|
||||||
neko.Service.Server,
|
neko.Service.Server,
|
||||||
neko.Service.WebRTC,
|
neko.Service.WebRTC,
|
||||||
neko.Service.Remote,
|
neko.Service.Capture,
|
||||||
neko.Service.Broadcast,
|
neko.Service.Desktop,
|
||||||
neko.Service.WebSocket,
|
neko.Service.WebSocket,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,20 +7,20 @@ require (
|
|||||||
github.com/go-chi/chi v4.1.2+incompatible
|
github.com/go-chi/chi v4.1.2+incompatible
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/kataras/go-events v0.0.3
|
github.com/kataras/go-events v0.0.3
|
||||||
github.com/pion/ice/v2 v2.2.6 // indirect
|
github.com/pion/ice/v2 v2.2.7 // indirect
|
||||||
github.com/pion/interceptor v0.1.11
|
github.com/pion/interceptor v0.1.12
|
||||||
github.com/pion/logging v0.2.2
|
github.com/pion/logging v0.2.2
|
||||||
github.com/pion/rtp v1.7.13 // indirect
|
github.com/pion/rtp v1.7.13 // indirect
|
||||||
github.com/pion/srtp/v2 v2.0.9 // indirect
|
github.com/pion/srtp/v2 v2.0.10 // indirect
|
||||||
github.com/pion/webrtc/v3 v3.1.41
|
github.com/pion/webrtc/v3 v3.1.43
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/rs/zerolog v1.26.1
|
github.com/rs/zerolog v1.27.0
|
||||||
github.com/spf13/cast v1.5.0 // indirect
|
github.com/spf13/cast v1.5.0 // indirect
|
||||||
github.com/spf13/cobra v1.4.0
|
github.com/spf13/cobra v1.5.0
|
||||||
github.com/spf13/viper v1.12.0
|
github.com/spf13/viper v1.12.0
|
||||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
|
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
|
||||||
golang.org/x/net v0.0.0-20220526153639-5463443f8c37 // indirect
|
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
golang.org/x/sys v0.0.0-20220730100132-1609e554cd39 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
)
|
)
|
||||||
@ -30,26 +30,28 @@ require (
|
|||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.6 // indirect
|
github.com/magiconair/properties v1.8.6 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
|
||||||
github.com/pion/datachannel v1.5.2 // indirect
|
github.com/pion/datachannel v1.5.2 // indirect
|
||||||
github.com/pion/dtls/v2 v2.1.5 // indirect
|
github.com/pion/dtls/v2 v2.1.5 // indirect
|
||||||
github.com/pion/mdns v0.0.5 // indirect
|
github.com/pion/mdns v0.0.5 // indirect
|
||||||
github.com/pion/randutil v0.1.0 // indirect
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
github.com/pion/rtcp v1.2.9 // indirect
|
github.com/pion/rtcp v1.2.10 // indirect
|
||||||
github.com/pion/sctp v1.8.2 // indirect
|
github.com/pion/sctp v1.8.2 // indirect
|
||||||
github.com/pion/sdp/v3 v3.0.5 // indirect
|
github.com/pion/sdp/v3 v3.0.5 // indirect
|
||||||
github.com/pion/stun v0.3.5 // indirect
|
github.com/pion/stun v0.3.5 // indirect
|
||||||
github.com/pion/transport v0.13.0 // indirect
|
github.com/pion/transport v0.13.1 // indirect
|
||||||
github.com/pion/turn/v2 v2.0.8 // indirect
|
github.com/pion/turn/v2 v2.0.8 // indirect
|
||||||
github.com/pion/udp v0.1.1 // indirect
|
github.com/pion/udp v0.1.1 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.8.1 // indirect
|
github.com/rogpeppe/go-internal v1.8.1 // indirect
|
||||||
github.com/spf13/afero v1.8.2 // indirect
|
github.com/spf13/afero v1.9.2 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/subosito/gotenv v1.3.0 // indirect
|
github.com/subosito/gotenv v1.4.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.66.4 // indirect
|
gopkg.in/ini.v1 v1.66.6 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
438
server/go.sum
@ -17,32 +17,14 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb
|
|||||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
|
||||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
|
||||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
|
||||||
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
|
|
||||||
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
|
|
||||||
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
|
|
||||||
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
|
|
||||||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
|
||||||
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
|
|
||||||
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
|
|
||||||
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
|
|
||||||
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
|
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||||
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
|
|
||||||
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
|
|
||||||
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
|
|
||||||
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
|
|
||||||
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
|
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||||
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
|
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||||
@ -56,90 +38,41 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
|
|||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
|
||||||
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
|
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
|
||||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
|
||||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
|
||||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
|
||||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||||
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
|
||||||
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
||||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
|
||||||
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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
|
||||||
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/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
@ -147,8 +80,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
|||||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
@ -164,9 +95,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
|
|||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
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/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
@ -177,18 +106,12 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
|
||||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
@ -199,119 +122,44 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
|
|||||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
|
|
||||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
|
||||||
github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=
|
|
||||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
|
||||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
|
||||||
github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
|
||||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
|
||||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
|
||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
|
||||||
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
|
||||||
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
|
||||||
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
|
||||||
github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
|
||||||
github.com/kataras/go-events v0.0.3 h1:o5YK53uURXtrlg7qE/vovxd/yKOJcLuFtPQbf1rYMC4=
|
github.com/kataras/go-events v0.0.3 h1:o5YK53uURXtrlg7qE/vovxd/yKOJcLuFtPQbf1rYMC4=
|
||||||
github.com/kataras/go-events v0.0.3/go.mod h1:bFBgtzwwzrag7kQmGuU1ZaVxhK2qseYPQomXoVEMsj4=
|
github.com/kataras/go-events v0.0.3/go.mod h1:bFBgtzwwzrag7kQmGuU1ZaVxhK2qseYPQomXoVEMsj4=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
|
||||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
|
||||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
|
||||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
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/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
@ -321,29 +169,30 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042
|
|||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.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.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
|
||||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
|
github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
|
||||||
github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
|
github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
|
||||||
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
|
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
|
||||||
github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
|
github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
|
||||||
github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c=
|
github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c=
|
||||||
github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
|
github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
|
||||||
github.com/pion/ice/v2 v2.2.6 h1:R/vaLlI1J2gCx141L5PEwtuGAGcyS6e7E0hDeJFq5Ig=
|
|
||||||
github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE=
|
github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE=
|
||||||
github.com/pion/interceptor v0.1.11 h1:00U6OlqxA3FFB50HSg25J/8cWi7P6FbSzw4eFn24Bvs=
|
github.com/pion/ice/v2 v2.2.7 h1:kG9tux3WdYUSqqqnf+O5zKlpy41PdlvLUBlYJeV2emQ=
|
||||||
|
github.com/pion/ice/v2 v2.2.7/go.mod h1:Ckj7cWZ717rtU01YoDQA9ntGWCk95D42uVZ8sI0EL+8=
|
||||||
github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8=
|
github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8=
|
||||||
|
github.com/pion/interceptor v0.1.12 h1:CslaNriCFUItiXS5o+hh5lpL0t0ytQkFnUcbbCs2Zq8=
|
||||||
|
github.com/pion/interceptor v0.1.12/go.mod h1:bDtgAD9dRkBZpWHGKaoKb42FhDHTG2rX8Ii9LRALLVA=
|
||||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||||
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
||||||
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
|
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
|
||||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
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/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||||
github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U=
|
|
||||||
github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
|
github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
|
||||||
|
github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
|
||||||
|
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
||||||
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
||||||
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||||
github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||||
@ -351,71 +200,42 @@ github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA=
|
|||||||
github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||||
github.com/pion/sdp/v3 v3.0.5 h1:ouvI7IgGl+V4CrqskVtr3AaTrPvPisEOxwgpdktctkU=
|
github.com/pion/sdp/v3 v3.0.5 h1:ouvI7IgGl+V4CrqskVtr3AaTrPvPisEOxwgpdktctkU=
|
||||||
github.com/pion/sdp/v3 v3.0.5/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
github.com/pion/sdp/v3 v3.0.5/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||||
github.com/pion/srtp/v2 v2.0.9 h1:JJq3jClmDFBPX/F5roEb0U19jSU7eUhyDqR/NZ34EKQ=
|
github.com/pion/srtp/v2 v2.0.10 h1:b8ZvEuI+mrL8hbr/f1YiJFB34UMrOac3R3N1yq2UN0w=
|
||||||
github.com/pion/srtp/v2 v2.0.9/go.mod h1:5TtM9yw6lsH0ppNCehB/EjEUli7VkUgKSPJqWVqbhQ4=
|
github.com/pion/srtp/v2 v2.0.10/go.mod h1:XEeSWaK9PfuMs7zxXyiN252AHPbH12NX5q/CFDWtUuA=
|
||||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||||
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
|
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
|
||||||
github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY=
|
|
||||||
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
||||||
|
github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA=
|
||||||
|
github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg=
|
||||||
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
|
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
|
||||||
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
||||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
||||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||||
github.com/pion/webrtc/v3 v3.1.41 h1:QogLjtriu+OwerRp4r6emTg4+zDWUy5R6EqthDBy7c0=
|
github.com/pion/webrtc/v3 v3.1.43 h1:YT3ZTO94UT4kSBvZnRAH82+0jJPUruiKr9CEstdlQzk=
|
||||||
github.com/pion/webrtc/v3 v3.1.41/go.mod h1:sUcW9SFPEWerDqGOBmdYEMfRvbdd7rgwo4bNzfsXww4=
|
github.com/pion/webrtc/v3 v3.1.43/go.mod h1:G/J8k0+grVsjC/rjCZ24AKoCCxcFFODgh7zThNZGs0M=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
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=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
|
||||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
|
||||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
|
||||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
|
||||||
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
|
||||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
|
||||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
|
||||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
|
||||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
|
||||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
|
||||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
|
||||||
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
||||||
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
|
github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
|
||||||
github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
|
github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
|
||||||
github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8=
|
|
||||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
|
||||||
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
|
|
||||||
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
|
|
||||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||||
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
|
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
|
||||||
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
@ -423,55 +243,38 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
|||||||
github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=
|
github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=
|
||||||
github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=
|
github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI=
|
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
|
||||||
github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs=
|
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs=
|
||||||
|
github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
|
||||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
|
||||||
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
|
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
|
||||||
go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU=
|
|
||||||
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
|
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
|
||||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
|
||||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
|
||||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
|
||||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@ -495,7 +298,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu
|
|||||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
@ -506,11 +308,9 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
@ -518,11 +318,9 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@ -540,29 +338,21 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
|
|||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/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-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-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-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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
|
||||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8=
|
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 h1:UreQrH7DbFXSi9ZFox6FNT3WBooWmdANpU+IfkT1T4I=
|
||||||
|
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@ -572,17 +362,6 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
|
|||||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@ -593,38 +372,24 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/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-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -635,8 +400,6 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -645,41 +408,19 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/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-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220730100132-1609e554cd39 h1:aNCnH+Fiqs7ZDTFH6oEFjIfbX2HvgQXJ6uQuUbTobjk=
|
||||||
|
golang.org/x/sys v0.0.0-20220730100132-1609e554cd39/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@ -688,7 +429,6 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
|||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
@ -708,7 +448,6 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw
|
|||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@ -733,7 +472,6 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY
|
|||||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
@ -743,21 +481,12 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f
|
|||||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
@ -777,26 +506,6 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513
|
|||||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
|
||||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
|
||||||
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
|
|
||||||
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
|
|
||||||
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
|
|
||||||
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
|
|
||||||
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
|
|
||||||
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
|
||||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
|
||||||
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
|
|
||||||
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
|
|
||||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
|
||||||
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
|
|
||||||
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
|
|
||||||
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
|
|
||||||
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
|
|
||||||
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
|
|
||||||
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
|
|
||||||
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
|
|
||||||
google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
@ -827,7 +536,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
|
|||||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||||
@ -840,48 +548,7 @@ google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6D
|
|||||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
|
||||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
|
||||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
|
||||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
|
||||||
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
|
||||||
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
|
||||||
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
|
||||||
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
|
||||||
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
|
||||||
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
|
||||||
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
|
|
||||||
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
|
||||||
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
|
||||||
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
|
||||||
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
|
||||||
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
|
||||||
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
|
||||||
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
|
||||||
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
|
||||||
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
|
||||||
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
|
|
||||||
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
|
||||||
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
|
||||||
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
|
||||||
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
|
||||||
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
|
||||||
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
|
||||||
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
@ -895,24 +562,9 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
|
|||||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
|
||||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
|
||||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
|
||||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
|
||||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
|
||||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
|
||||||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
|
||||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
|
||||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
|
||||||
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
|
||||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
|
||||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
|
||||||
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@ -925,9 +577,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
|||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@ -935,21 +584,15 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
|||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
|
gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=
|
||||||
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
@ -962,4 +605,3 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
|||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
|
||||||
|
@ -1,104 +0,0 @@
|
|||||||
package broadcast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
|
|
||||||
"m1k1o/neko/internal/gst"
|
|
||||||
"m1k1o/neko/internal/types/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BroadcastManager struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
logger zerolog.Logger
|
|
||||||
pipeline *gst.Pipeline
|
|
||||||
remote *config.Remote
|
|
||||||
config *config.Broadcast
|
|
||||||
enabled bool
|
|
||||||
url string
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(remote *config.Remote, config *config.Broadcast) *BroadcastManager {
|
|
||||||
return &BroadcastManager{
|
|
||||||
logger: log.With().Str("module", "remote").Logger(),
|
|
||||||
remote: remote,
|
|
||||||
config: config,
|
|
||||||
enabled: config.Enabled,
|
|
||||||
url: config.URL,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *BroadcastManager) Shutdown() error {
|
|
||||||
manager.Destroy()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *BroadcastManager) Start() error {
|
|
||||||
if !manager.enabled || manager.IsActive() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
manager.pipeline, err = gst.CreateRTMPPipeline(
|
|
||||||
manager.remote.Device,
|
|
||||||
manager.remote.Display,
|
|
||||||
manager.config.Pipeline,
|
|
||||||
manager.url,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
manager.pipeline = nil
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.logger.Info().
|
|
||||||
Str("audio_device", manager.remote.Device).
|
|
||||||
Str("video_display", manager.remote.Display).
|
|
||||||
Str("rtmp_pipeline_src", manager.pipeline.Src).
|
|
||||||
Msgf("RTMP pipeline is starting...")
|
|
||||||
|
|
||||||
manager.pipeline.Play()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *BroadcastManager) Stop() {
|
|
||||||
if !manager.IsActive() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.pipeline.Stop()
|
|
||||||
manager.pipeline = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *BroadcastManager) IsActive() bool {
|
|
||||||
return manager.pipeline != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *BroadcastManager) Create(url string) error {
|
|
||||||
manager.mu.Lock()
|
|
||||||
defer manager.mu.Unlock()
|
|
||||||
|
|
||||||
manager.url = url
|
|
||||||
manager.enabled = true
|
|
||||||
|
|
||||||
err := manager.Start()
|
|
||||||
if err != nil {
|
|
||||||
manager.enabled = false
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *BroadcastManager) Destroy() {
|
|
||||||
manager.mu.Lock()
|
|
||||||
defer manager.mu.Unlock()
|
|
||||||
|
|
||||||
manager.Stop()
|
|
||||||
manager.enabled = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *BroadcastManager) GetUrl() string {
|
|
||||||
return manager.url
|
|
||||||
}
|
|
121
server/internal/capture/broadcast.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package capture
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"m1k1o/neko/internal/capture/gst"
|
||||||
|
"m1k1o/neko/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BroacastManagerCtx struct {
|
||||||
|
logger zerolog.Logger
|
||||||
|
mu sync.Mutex
|
||||||
|
|
||||||
|
pipeline *gst.Pipeline
|
||||||
|
pipelineMu sync.Mutex
|
||||||
|
pipelineFn func(url string) (string, error)
|
||||||
|
|
||||||
|
url string
|
||||||
|
started bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func broadcastNew(pipelineFn func(url string) (string, error), defaultUrl string) *BroacastManagerCtx {
|
||||||
|
logger := log.With().
|
||||||
|
Str("module", "capture").
|
||||||
|
Str("submodule", "broadcast").
|
||||||
|
Logger()
|
||||||
|
|
||||||
|
return &BroacastManagerCtx{
|
||||||
|
logger: logger,
|
||||||
|
pipelineFn: pipelineFn,
|
||||||
|
url: defaultUrl,
|
||||||
|
started: defaultUrl != "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *BroacastManagerCtx) shutdown() {
|
||||||
|
manager.logger.Info().Msgf("shutdown")
|
||||||
|
|
||||||
|
manager.destroyPipeline()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *BroacastManagerCtx) Start(url string) error {
|
||||||
|
manager.mu.Lock()
|
||||||
|
defer manager.mu.Unlock()
|
||||||
|
|
||||||
|
err := manager.createPipeline()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.url = url
|
||||||
|
manager.started = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *BroacastManagerCtx) Stop() {
|
||||||
|
manager.mu.Lock()
|
||||||
|
defer manager.mu.Unlock()
|
||||||
|
|
||||||
|
manager.started = false
|
||||||
|
manager.destroyPipeline()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *BroacastManagerCtx) Started() bool {
|
||||||
|
manager.mu.Lock()
|
||||||
|
defer manager.mu.Unlock()
|
||||||
|
|
||||||
|
return manager.started
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *BroacastManagerCtx) Url() string {
|
||||||
|
manager.mu.Lock()
|
||||||
|
defer manager.mu.Unlock()
|
||||||
|
|
||||||
|
return manager.url
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *BroacastManagerCtx) createPipeline() error {
|
||||||
|
manager.pipelineMu.Lock()
|
||||||
|
defer manager.pipelineMu.Unlock()
|
||||||
|
|
||||||
|
if manager.pipeline != nil {
|
||||||
|
return types.ErrCapturePipelineAlreadyExists
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
pipelineStr, err := manager.pipelineFn(manager.url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.logger.Info().
|
||||||
|
Str("url", manager.url).
|
||||||
|
Str("src", pipelineStr).
|
||||||
|
Msgf("starting pipeline")
|
||||||
|
|
||||||
|
manager.pipeline, err = gst.CreatePipeline(pipelineStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.pipeline.Play()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *BroacastManagerCtx) destroyPipeline() {
|
||||||
|
manager.pipelineMu.Lock()
|
||||||
|
defer manager.pipelineMu.Unlock()
|
||||||
|
|
||||||
|
if manager.pipeline == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.pipeline.Destroy()
|
||||||
|
manager.logger.Info().Msgf("destroying pipeline")
|
||||||
|
manager.pipeline = nil
|
||||||
|
}
|
202
server/internal/capture/gst/gst.c
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
#include "gst.h"
|
||||||
|
|
||||||
|
static void gstreamer_pipeline_log(GstPipelineCtx *ctx, char* level, const char* format, ...) {
|
||||||
|
va_list argptr;
|
||||||
|
va_start(argptr, format);
|
||||||
|
char buffer[100];
|
||||||
|
vsprintf(buffer, format, argptr);
|
||||||
|
va_end(argptr);
|
||||||
|
goPipelineLog(level, buffer, ctx->pipelineId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean gstreamer_bus_call(GstBus *bus, GstMessage *msg, gpointer user_data) {
|
||||||
|
GstPipelineCtx *ctx = (GstPipelineCtx *)user_data;
|
||||||
|
|
||||||
|
switch (GST_MESSAGE_TYPE(msg)) {
|
||||||
|
case GST_MESSAGE_EOS: {
|
||||||
|
gstreamer_pipeline_log(ctx, "fatal", "end of stream");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case GST_MESSAGE_STATE_CHANGED: {
|
||||||
|
GstState old_state, new_state;
|
||||||
|
gst_message_parse_state_changed(msg, &old_state, &new_state, NULL);
|
||||||
|
|
||||||
|
gstreamer_pipeline_log(ctx, "debug",
|
||||||
|
"element %s changed state from %s to %s",
|
||||||
|
GST_OBJECT_NAME(msg->src),
|
||||||
|
gst_element_state_get_name(old_state),
|
||||||
|
gst_element_state_get_name(new_state));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case GST_MESSAGE_TAG: {
|
||||||
|
GstTagList *tags = NULL;
|
||||||
|
gst_message_parse_tag(msg, &tags);
|
||||||
|
|
||||||
|
gstreamer_pipeline_log(ctx, "debug",
|
||||||
|
"got tags from element %s",
|
||||||
|
GST_OBJECT_NAME(msg->src));
|
||||||
|
|
||||||
|
gst_tag_list_unref(tags);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case GST_MESSAGE_ERROR: {
|
||||||
|
GError *err = NULL;
|
||||||
|
gchar *dbg_info = NULL;
|
||||||
|
gst_message_parse_error(msg, &err, &dbg_info);
|
||||||
|
|
||||||
|
gstreamer_pipeline_log(ctx, "error",
|
||||||
|
"error from element %s: %s",
|
||||||
|
GST_OBJECT_NAME(msg->src), err->message);
|
||||||
|
gstreamer_pipeline_log(ctx, "warn",
|
||||||
|
"debugging info: %s",
|
||||||
|
(dbg_info) ? dbg_info : "none");
|
||||||
|
|
||||||
|
g_error_free(err);
|
||||||
|
g_free(dbg_info);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
gstreamer_pipeline_log(ctx, "trace", "unknown message");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
GstPipelineCtx *gstreamer_pipeline_create(char *pipelineStr, int pipelineId, GError **error) {
|
||||||
|
GstElement *pipeline = gst_parse_launch(pipelineStr, error);
|
||||||
|
if (pipeline == NULL) return NULL;
|
||||||
|
|
||||||
|
// create gstreamer pipeline context
|
||||||
|
GstPipelineCtx *ctx = calloc(1, sizeof(GstPipelineCtx));
|
||||||
|
ctx->pipelineId = pipelineId;
|
||||||
|
ctx->pipeline = pipeline;
|
||||||
|
|
||||||
|
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
|
||||||
|
gst_bus_add_watch(bus, gstreamer_bus_call, ctx);
|
||||||
|
gst_object_unref(bus);
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GstFlowReturn gstreamer_send_new_sample_handler(GstElement *object, gpointer user_data) {
|
||||||
|
GstPipelineCtx *ctx = (GstPipelineCtx *)user_data;
|
||||||
|
GstSample *sample = NULL;
|
||||||
|
GstBuffer *buffer = NULL;
|
||||||
|
gpointer copy = NULL;
|
||||||
|
gsize copy_size = 0;
|
||||||
|
|
||||||
|
g_signal_emit_by_name(object, "pull-sample", &sample);
|
||||||
|
if (sample) {
|
||||||
|
buffer = gst_sample_get_buffer(sample);
|
||||||
|
if (buffer) {
|
||||||
|
gst_buffer_extract_dup(buffer, 0, gst_buffer_get_size(buffer), ©, ©_size);
|
||||||
|
goHandlePipelineBuffer(copy, copy_size, GST_BUFFER_DURATION(buffer), ctx->pipelineId);
|
||||||
|
}
|
||||||
|
gst_sample_unref(sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GST_FLOW_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gstreamer_pipeline_attach_appsink(GstPipelineCtx *ctx, char *sinkName) {
|
||||||
|
ctx->appsink = gst_bin_get_by_name(GST_BIN(ctx->pipeline), sinkName);
|
||||||
|
g_object_set(ctx->appsink, "emit-signals", TRUE, NULL);
|
||||||
|
g_signal_connect(ctx->appsink, "new-sample", G_CALLBACK(gstreamer_send_new_sample_handler), ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gstreamer_pipeline_attach_appsrc(GstPipelineCtx *ctx, char *srcName) {
|
||||||
|
ctx->appsrc = gst_bin_get_by_name(GST_BIN(ctx->pipeline), srcName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gstreamer_pipeline_play(GstPipelineCtx *ctx) {
|
||||||
|
gst_element_set_state(GST_ELEMENT(ctx->pipeline), GST_STATE_PLAYING);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gstreamer_pipeline_pause(GstPipelineCtx *ctx) {
|
||||||
|
gst_element_set_state(GST_ELEMENT(ctx->pipeline), GST_STATE_PAUSED);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gstreamer_pipeline_destory(GstPipelineCtx *ctx) {
|
||||||
|
// end appsrc, if exists
|
||||||
|
if (ctx->appsrc) {
|
||||||
|
gst_app_src_end_of_stream(GST_APP_SRC(ctx->appsrc));
|
||||||
|
}
|
||||||
|
|
||||||
|
// send pipeline eos
|
||||||
|
gst_element_send_event(GST_ELEMENT(ctx->pipeline), gst_event_new_eos());
|
||||||
|
|
||||||
|
// set null state
|
||||||
|
gst_element_set_state(GST_ELEMENT(ctx->pipeline), GST_STATE_NULL);
|
||||||
|
|
||||||
|
if (ctx->appsink) {
|
||||||
|
gst_object_unref(ctx->appsink);
|
||||||
|
ctx->appsink = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->appsrc) {
|
||||||
|
gst_object_unref(ctx->appsrc);
|
||||||
|
ctx->appsrc = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
gst_object_unref(ctx->pipeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gstreamer_pipeline_push(GstPipelineCtx *ctx, void *buffer, int bufferLen) {
|
||||||
|
if (ctx->appsrc != NULL) {
|
||||||
|
gpointer p = g_memdup(buffer, bufferLen);
|
||||||
|
GstBuffer *buffer = gst_buffer_new_wrapped(p, bufferLen);
|
||||||
|
gst_app_src_push_buffer(GST_APP_SRC(ctx->appsrc), buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean gstreamer_pipeline_set_prop_int(GstPipelineCtx *ctx, char *binName, char *prop, gint value) {
|
||||||
|
GstElement *el = gst_bin_get_by_name(GST_BIN(ctx->pipeline), binName);
|
||||||
|
if (el == NULL) return FALSE;
|
||||||
|
|
||||||
|
g_object_set(G_OBJECT(el),
|
||||||
|
prop, value,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
gst_object_unref(el);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean gstreamer_pipeline_set_caps_framerate(GstPipelineCtx *ctx, const gchar* binName, gint numerator, gint denominator) {
|
||||||
|
GstElement *el = gst_bin_get_by_name(GST_BIN(ctx->pipeline), binName);
|
||||||
|
if (el == NULL) return FALSE;
|
||||||
|
|
||||||
|
GstCaps *caps = gst_caps_new_simple("video/x-raw",
|
||||||
|
"framerate", GST_TYPE_FRACTION, numerator, denominator,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
g_object_set(G_OBJECT(el),
|
||||||
|
"caps", caps,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
gst_caps_unref(caps);
|
||||||
|
gst_object_unref(el);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean gstreamer_pipeline_set_caps_resolution(GstPipelineCtx *ctx, const gchar* binName, gint width, gint height) {
|
||||||
|
GstElement *el = gst_bin_get_by_name(GST_BIN(ctx->pipeline), binName);
|
||||||
|
if (el == NULL) return FALSE;
|
||||||
|
|
||||||
|
GstCaps *caps = gst_caps_new_simple("video/x-raw",
|
||||||
|
"width", G_TYPE_INT, width,
|
||||||
|
"height", G_TYPE_INT, height,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
g_object_set(G_OBJECT(el),
|
||||||
|
"caps", caps,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
gst_caps_unref(caps);
|
||||||
|
gst_object_unref(el);
|
||||||
|
return TRUE;
|
||||||
|
}
|
202
server/internal/capture/gst/gst.go
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
package gst
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo pkg-config: gstreamer-1.0 gstreamer-app-1.0
|
||||||
|
|
||||||
|
#include "gst.h"
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"m1k1o/neko/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pipeline struct {
|
||||||
|
id int
|
||||||
|
logger zerolog.Logger
|
||||||
|
Src string
|
||||||
|
Ctx *C.GstPipelineCtx
|
||||||
|
Sample chan types.Sample
|
||||||
|
}
|
||||||
|
|
||||||
|
var pSerial int32
|
||||||
|
var pipelines = make(map[int]*Pipeline)
|
||||||
|
var pipelinesLock sync.Mutex
|
||||||
|
var registry *C.GstRegistry
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
C.gst_init(nil, nil)
|
||||||
|
registry = C.gst_registry_get()
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreatePipeline(pipelineStr string) (*Pipeline, error) {
|
||||||
|
id := atomic.AddInt32(&pSerial, 1)
|
||||||
|
|
||||||
|
pipelineStrUnsafe := C.CString(pipelineStr)
|
||||||
|
defer C.free(unsafe.Pointer(pipelineStrUnsafe))
|
||||||
|
|
||||||
|
pipelinesLock.Lock()
|
||||||
|
defer pipelinesLock.Unlock()
|
||||||
|
|
||||||
|
var gstError *C.GError
|
||||||
|
ctx := C.gstreamer_pipeline_create(pipelineStrUnsafe, C.int(id), &gstError)
|
||||||
|
|
||||||
|
if gstError != nil {
|
||||||
|
defer C.g_error_free(gstError)
|
||||||
|
return nil, fmt.Errorf("(pipeline error) %s", C.GoString(gstError.message))
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &Pipeline{
|
||||||
|
id: int(id),
|
||||||
|
logger: log.With().
|
||||||
|
Str("module", "capture").
|
||||||
|
Str("submodule", "gstreamer").
|
||||||
|
Int("pipeline_id", int(id)).Logger(),
|
||||||
|
Src: pipelineStr,
|
||||||
|
Ctx: ctx,
|
||||||
|
Sample: make(chan types.Sample),
|
||||||
|
}
|
||||||
|
|
||||||
|
pipelines[p.id] = p
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pipeline) AttachAppsink(sinkName string) {
|
||||||
|
sinkNameUnsafe := C.CString(sinkName)
|
||||||
|
defer C.free(unsafe.Pointer(sinkNameUnsafe))
|
||||||
|
|
||||||
|
C.gstreamer_pipeline_attach_appsink(p.Ctx, sinkNameUnsafe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pipeline) AttachAppsrc(srcName string) {
|
||||||
|
srcNameUnsafe := C.CString(srcName)
|
||||||
|
defer C.free(unsafe.Pointer(srcNameUnsafe))
|
||||||
|
|
||||||
|
C.gstreamer_pipeline_attach_appsrc(p.Ctx, srcNameUnsafe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pipeline) Play() {
|
||||||
|
C.gstreamer_pipeline_play(p.Ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pipeline) Pause() {
|
||||||
|
C.gstreamer_pipeline_pause(p.Ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pipeline) Destroy() {
|
||||||
|
C.gstreamer_pipeline_destory(p.Ctx)
|
||||||
|
|
||||||
|
pipelinesLock.Lock()
|
||||||
|
delete(pipelines, p.id)
|
||||||
|
pipelinesLock.Unlock()
|
||||||
|
|
||||||
|
close(p.Sample)
|
||||||
|
C.free(unsafe.Pointer(p.Ctx))
|
||||||
|
p = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pipeline) Push(buffer []byte) {
|
||||||
|
bytes := C.CBytes(buffer)
|
||||||
|
defer C.free(bytes)
|
||||||
|
|
||||||
|
C.gstreamer_pipeline_push(p.Ctx, bytes, C.int(len(buffer)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pipeline) SetPropInt(binName string, prop string, value int) bool {
|
||||||
|
cBinName := C.CString(binName)
|
||||||
|
defer C.free(unsafe.Pointer(cBinName))
|
||||||
|
|
||||||
|
cProp := C.CString(prop)
|
||||||
|
defer C.free(unsafe.Pointer(cProp))
|
||||||
|
|
||||||
|
cValue := C.int(value)
|
||||||
|
|
||||||
|
p.logger.Debug().Msgf("setting prop %s of %s to %d", prop, binName, value)
|
||||||
|
|
||||||
|
ok := C.gstreamer_pipeline_set_prop_int(p.Ctx, cBinName, cProp, cValue)
|
||||||
|
return ok == C.TRUE
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pipeline) SetCapsFramerate(binName string, numerator, denominator int) bool {
|
||||||
|
cBinName := C.CString(binName)
|
||||||
|
cNumerator := C.int(numerator)
|
||||||
|
cDenominator := C.int(denominator)
|
||||||
|
|
||||||
|
defer C.free(unsafe.Pointer(cBinName))
|
||||||
|
|
||||||
|
p.logger.Debug().Msgf("setting caps framerate of %s to %d/%d", binName, numerator, denominator)
|
||||||
|
|
||||||
|
ok := C.gstreamer_pipeline_set_caps_framerate(p.Ctx, cBinName, cNumerator, cDenominator)
|
||||||
|
return ok == C.TRUE
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pipeline) SetCapsResolution(binName string, width, height int) bool {
|
||||||
|
cBinName := C.CString(binName)
|
||||||
|
cWidth := C.int(width)
|
||||||
|
cHeight := C.int(height)
|
||||||
|
|
||||||
|
defer C.free(unsafe.Pointer(cBinName))
|
||||||
|
|
||||||
|
p.logger.Debug().Msgf("setting caps resolution of %s to %dx%d", binName, width, height)
|
||||||
|
|
||||||
|
ok := C.gstreamer_pipeline_set_caps_resolution(p.Ctx, cBinName, cWidth, cHeight)
|
||||||
|
return ok == C.TRUE
|
||||||
|
}
|
||||||
|
|
||||||
|
// gst-inspect-1.0
|
||||||
|
func CheckPlugins(plugins []string) error {
|
||||||
|
var plugin *C.GstPlugin
|
||||||
|
for _, pluginstr := range plugins {
|
||||||
|
plugincstr := C.CString(pluginstr)
|
||||||
|
plugin = C.gst_registry_find_plugin(registry, plugincstr)
|
||||||
|
C.free(unsafe.Pointer(plugincstr))
|
||||||
|
if plugin == nil {
|
||||||
|
return fmt.Errorf("required gstreamer plugin %s not found", pluginstr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//export goHandlePipelineBuffer
|
||||||
|
func goHandlePipelineBuffer(buffer unsafe.Pointer, bufferLen C.int, duration C.int, pipelineID C.int) {
|
||||||
|
defer C.free(buffer)
|
||||||
|
|
||||||
|
pipelinesLock.Lock()
|
||||||
|
pipeline, ok := pipelines[int(pipelineID)]
|
||||||
|
pipelinesLock.Unlock()
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
pipeline.Sample <- types.Sample{
|
||||||
|
Data: C.GoBytes(buffer, bufferLen),
|
||||||
|
Duration: time.Duration(duration),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Warn().
|
||||||
|
Str("module", "capture").
|
||||||
|
Str("submodule", "gstreamer").
|
||||||
|
Int("pipeline_id", int(pipelineID)).
|
||||||
|
Msgf("discarding sample, pipeline not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//export goPipelineLog
|
||||||
|
func goPipelineLog(levelUnsafe *C.char, msgUnsafe *C.char, pipelineID C.int) {
|
||||||
|
levelStr := C.GoString(levelUnsafe)
|
||||||
|
msg := C.GoString(msgUnsafe)
|
||||||
|
|
||||||
|
level, _ := zerolog.ParseLevel(levelStr)
|
||||||
|
log.WithLevel(level).
|
||||||
|
Str("module", "capture").
|
||||||
|
Str("submodule", "gstreamer").
|
||||||
|
Int("pipeline_id", int(pipelineID)).
|
||||||
|
Msg(msg)
|
||||||
|
}
|
27
server/internal/capture/gst/gst.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <gst/gst.h>
|
||||||
|
#include <gst/app/gstappsrc.h>
|
||||||
|
|
||||||
|
typedef struct GstPipelineCtx {
|
||||||
|
int pipelineId;
|
||||||
|
GstElement *pipeline;
|
||||||
|
GstElement *appsink;
|
||||||
|
GstElement *appsrc;
|
||||||
|
} GstPipelineCtx;
|
||||||
|
|
||||||
|
extern void goHandlePipelineBuffer(void *buffer, int bufferLen, int samples, int pipelineId);
|
||||||
|
extern void goPipelineLog(char *level, char *msg, int pipelineId);
|
||||||
|
|
||||||
|
GstPipelineCtx *gstreamer_pipeline_create(char *pipelineStr, int pipelineId, GError **error);
|
||||||
|
void gstreamer_pipeline_attach_appsink(GstPipelineCtx *ctx, char *sinkName);
|
||||||
|
void gstreamer_pipeline_attach_appsrc(GstPipelineCtx *ctx, char *srcName);
|
||||||
|
void gstreamer_pipeline_play(GstPipelineCtx *ctx);
|
||||||
|
void gstreamer_pipeline_pause(GstPipelineCtx *ctx);
|
||||||
|
void gstreamer_pipeline_destory(GstPipelineCtx *ctx);
|
||||||
|
void gstreamer_pipeline_push(GstPipelineCtx *ctx, void *buffer, int bufferLen);
|
||||||
|
|
||||||
|
gboolean gstreamer_pipeline_set_prop_int(GstPipelineCtx *ctx, char *binName, char *prop, gint value);
|
||||||
|
gboolean gstreamer_pipeline_set_caps_framerate(GstPipelineCtx *ctx, const gchar* binName, gint numerator, gint denominator);
|
||||||
|
gboolean gstreamer_pipeline_set_caps_resolution(GstPipelineCtx *ctx, const gchar* binName, gint width, gint height);
|
98
server/internal/capture/manager.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package capture
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"m1k1o/neko/internal/config"
|
||||||
|
"m1k1o/neko/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CaptureManagerCtx struct {
|
||||||
|
logger zerolog.Logger
|
||||||
|
desktop types.DesktopManager
|
||||||
|
|
||||||
|
// sinks
|
||||||
|
broadcast *BroacastManagerCtx
|
||||||
|
audio *StreamSinkManagerCtx
|
||||||
|
video *StreamSinkManagerCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCtx {
|
||||||
|
logger := log.With().Str("module", "capture").Logger()
|
||||||
|
|
||||||
|
return &CaptureManagerCtx{
|
||||||
|
logger: logger,
|
||||||
|
desktop: desktop,
|
||||||
|
|
||||||
|
// sinks
|
||||||
|
broadcast: broadcastNew(func(url string) (string, error) {
|
||||||
|
return NewBroadcastPipeline(config.AudioDevice, config.Display, config.BroadcastPipeline, url)
|
||||||
|
}, config.BroadcastUrl),
|
||||||
|
audio: streamSinkNew(config.AudioCodec, func() (string, error) {
|
||||||
|
return NewAudioPipeline(config.AudioCodec, config.AudioDevice, config.AudioPipeline, config.AudioBitrate)
|
||||||
|
}, "audio"),
|
||||||
|
video: streamSinkNew(config.VideoCodec, func() (string, error) {
|
||||||
|
return NewVideoPipeline(config.VideoCodec, config.Display, config.VideoPipeline, config.VideoMaxFPS, config.VideoBitrate, config.VideoHWEnc)
|
||||||
|
}, "video"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *CaptureManagerCtx) Start() {
|
||||||
|
if manager.broadcast.Started() {
|
||||||
|
if err := manager.broadcast.createPipeline(); err != nil {
|
||||||
|
manager.logger.Panic().Err(err).Msg("unable to create broadcast pipeline")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.desktop.OnBeforeScreenSizeChange(func() {
|
||||||
|
if manager.video.Started() {
|
||||||
|
manager.video.destroyPipeline()
|
||||||
|
}
|
||||||
|
|
||||||
|
if manager.broadcast.Started() {
|
||||||
|
manager.broadcast.destroyPipeline()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
manager.desktop.OnAfterScreenSizeChange(func() {
|
||||||
|
if manager.video.Started() {
|
||||||
|
err := manager.video.createPipeline()
|
||||||
|
if err != nil && !errors.Is(err, types.ErrCapturePipelineAlreadyExists) {
|
||||||
|
manager.logger.Panic().Err(err).Msg("unable to recreate video pipeline")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if manager.broadcast.Started() {
|
||||||
|
err := manager.broadcast.createPipeline()
|
||||||
|
if err != nil && !errors.Is(err, types.ErrCapturePipelineAlreadyExists) {
|
||||||
|
manager.logger.Panic().Err(err).Msg("unable to recreate broadcast pipeline")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *CaptureManagerCtx) Shutdown() error {
|
||||||
|
manager.logger.Info().Msgf("shutdown")
|
||||||
|
|
||||||
|
manager.broadcast.shutdown()
|
||||||
|
|
||||||
|
manager.audio.shutdown()
|
||||||
|
manager.video.shutdown()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *CaptureManagerCtx) Broadcast() types.BroadcastManager {
|
||||||
|
return manager.broadcast
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *CaptureManagerCtx) Audio() types.StreamSinkManager {
|
||||||
|
return manager.audio
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *CaptureManagerCtx) Video() types.StreamSinkManager {
|
||||||
|
return manager.video
|
||||||
|
}
|
202
server/internal/capture/pipelines.go
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
package capture
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"m1k1o/neko/internal/capture/gst"
|
||||||
|
"m1k1o/neko/internal/types/codec"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
apt-get install \
|
||||||
|
libgstreamer1.0-0 \
|
||||||
|
gstreamer1.0-plugins-base \
|
||||||
|
gstreamer1.0-plugins-good \
|
||||||
|
gstreamer1.0-plugins-bad \
|
||||||
|
gstreamer1.0-plugins-ugly\
|
||||||
|
gstreamer1.0-libav \
|
||||||
|
gstreamer1.0-doc \
|
||||||
|
gstreamer1.0-tools \
|
||||||
|
gstreamer1.0-x \
|
||||||
|
gstreamer1.0-alsa \
|
||||||
|
gstreamer1.0-pulseaudio
|
||||||
|
|
||||||
|
gst-inspect-1.0 --version
|
||||||
|
gst-inspect-1.0 plugin
|
||||||
|
gst-launch-1.0 ximagesrc show-pointer=true use-damage=false ! video/x-raw,framerate=30/1 ! videoconvert ! queue ! vp8enc error-resilient=partitions keyframe-max-dist=10 auto-alt-ref=true cpu-used=5 deadline=1 ! autovideosink
|
||||||
|
gst-launch-1.0 pulsesrc ! audioconvert ! opusenc ! autoaudiosink
|
||||||
|
*/
|
||||||
|
|
||||||
|
const (
|
||||||
|
videoSrc = "ximagesrc display-name=%s show-pointer=true use-damage=false ! video/x-raw,framerate=%d/1 ! videoconvert ! queue ! "
|
||||||
|
audioSrc = "pulsesrc device=%s ! audio/x-raw,channels=2 ! audioconvert ! "
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewBroadcastPipeline(device string, display string, pipelineSrc string, url string) (string, error) {
|
||||||
|
video := fmt.Sprintf(videoSrc, display, 25)
|
||||||
|
audio := fmt.Sprintf(audioSrc, device)
|
||||||
|
|
||||||
|
var pipelineStr string
|
||||||
|
if pipelineSrc != "" {
|
||||||
|
// replace RTMP url
|
||||||
|
pipelineStr = strings.Replace(pipelineSrc, "{url}", url, -1)
|
||||||
|
// replace audio device
|
||||||
|
pipelineStr = strings.Replace(pipelineStr, "{device}", device, -1)
|
||||||
|
// replace display
|
||||||
|
pipelineStr = strings.Replace(pipelineStr, "{display}", display, -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.", url, audio, video)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pipelineStr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc string, fps int16, bitrate uint, hwenc string) (string, error) {
|
||||||
|
pipelineStr := " ! appsink name=appsink"
|
||||||
|
|
||||||
|
// if using custom pipeline
|
||||||
|
if pipelineSrc != "" {
|
||||||
|
pipelineStr = fmt.Sprintf(pipelineSrc+pipelineStr, display)
|
||||||
|
return pipelineStr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch rtpCodec.Name {
|
||||||
|
case codec.VP8().Name:
|
||||||
|
if hwenc == "VAAPI" {
|
||||||
|
if err := gst.CheckPlugins([]string{"ximagesrc", "vaapi"}); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// vp8 encode is missing from gstreamer.freedesktop.org/documentation
|
||||||
|
// note that it was removed from some recent intel CPUs: https://trac.ffmpeg.org/wiki/Hardware/QuickSync
|
||||||
|
// https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer-vaapi-plugins/html/gstreamer-vaapi-plugins-vaapivp8enc.html
|
||||||
|
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! vaapivp8enc rate-control=vbr bitrate=%d keyframe-period=180"+pipelineStr, display, fps, bitrate)
|
||||||
|
} else {
|
||||||
|
// https://gstreamer.freedesktop.org/documentation/vpx/vp8enc.html?gi-language=c
|
||||||
|
// gstreamer1.0-plugins-good
|
||||||
|
// vp8enc error-resilient=partitions keyframe-max-dist=10 auto-alt-ref=true cpu-used=5 deadline=1
|
||||||
|
if err := gst.CheckPlugins([]string{"ximagesrc", "vpx"}); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
pipelineStr = strings.Join([]string{
|
||||||
|
fmt.Sprintf(videoSrc, display, fps),
|
||||||
|
"vp8enc",
|
||||||
|
fmt.Sprintf("target-bitrate=%d", bitrate*650),
|
||||||
|
"cpu-used=4",
|
||||||
|
"end-usage=cbr",
|
||||||
|
"threads=4",
|
||||||
|
"deadline=1",
|
||||||
|
"undershoot=95",
|
||||||
|
fmt.Sprintf("buffer-size=%d", bitrate*4),
|
||||||
|
fmt.Sprintf("buffer-initial-size=%d", bitrate*2),
|
||||||
|
fmt.Sprintf("buffer-optimal-size=%d", bitrate*3),
|
||||||
|
"keyframe-max-dist=25",
|
||||||
|
"min-quantizer=4",
|
||||||
|
"max-quantizer=20",
|
||||||
|
pipelineStr,
|
||||||
|
}, " ")
|
||||||
|
}
|
||||||
|
case codec.VP9().Name:
|
||||||
|
// https://gstreamer.freedesktop.org/documentation/vpx/vp9enc.html?gi-language=c
|
||||||
|
// gstreamer1.0-plugins-good
|
||||||
|
// vp9enc
|
||||||
|
if err := gst.CheckPlugins([]string{"ximagesrc", "vpx"}); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
pipelineStr = fmt.Sprintf(videoSrc+"vp9enc target-bitrate=%d cpu-used=-5 threads=4 deadline=1 keyframe-max-dist=30 auto-alt-ref=true"+pipelineStr, display, fps, bitrate*1000)
|
||||||
|
case codec.H264().Name:
|
||||||
|
if err := gst.CheckPlugins([]string{"ximagesrc"}); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if hwenc == "VAAPI" {
|
||||||
|
if err := gst.CheckPlugins([]string{"vaapi"}); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! vaapih264enc rate-control=vbr bitrate=%d keyframe-period=180 quality-level=7 ! video/x-h264,stream-format=byte-stream"+pipelineStr, display, fps, bitrate)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// https://gstreamer.freedesktop.org/documentation/openh264/openh264enc.html?gi-language=c#openh264enc
|
||||||
|
// gstreamer1.0-plugins-bad
|
||||||
|
// openh264enc multi-thread=4 complexity=high bitrate=3072000 max-bitrate=4096000
|
||||||
|
if err := gst.CheckPlugins([]string{"openh264"}); err == nil {
|
||||||
|
pipelineStr = fmt.Sprintf(videoSrc+"openh264enc multi-thread=4 complexity=high bitrate=%d max-bitrate=%d ! video/x-h264,stream-format=byte-stream"+pipelineStr, display, fps, bitrate*1000, (bitrate+1024)*1000)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c
|
||||||
|
// gstreamer1.0-plugins-ugly
|
||||||
|
// video/x-raw,format=I420 ! x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream
|
||||||
|
if err := gst.CheckPlugins([]string{"x264"}); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
vbvbuf := uint(1000)
|
||||||
|
if bitrate > 1000 {
|
||||||
|
vbvbuf = bitrate
|
||||||
|
}
|
||||||
|
|
||||||
|
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! x264enc threads=4 bitrate=%d key-int-max=60 vbv-buf-capacity=%d byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream"+pipelineStr, display, fps, bitrate, vbvbuf)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unknown codec %s", rtpCodec.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pipelineStr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAudioPipeline(rtpCodec codec.RTPCodec, device string, pipelineSrc string, bitrate uint) (string, error) {
|
||||||
|
pipelineStr := " ! appsink name=appsink"
|
||||||
|
|
||||||
|
// if using custom pipeline
|
||||||
|
if pipelineSrc != "" {
|
||||||
|
pipelineStr = fmt.Sprintf(pipelineSrc+pipelineStr, device)
|
||||||
|
return pipelineStr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch rtpCodec.Name {
|
||||||
|
case codec.Opus().Name:
|
||||||
|
// https://gstreamer.freedesktop.org/documentation/opus/opusenc.html
|
||||||
|
// gstreamer1.0-plugins-base
|
||||||
|
// opusenc
|
||||||
|
if err := gst.CheckPlugins([]string{"pulseaudio", "opus"}); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
pipelineStr = fmt.Sprintf(audioSrc+"opusenc inband-fec=true bitrate=%d"+pipelineStr, device, bitrate*1000)
|
||||||
|
case codec.G722().Name:
|
||||||
|
// https://gstreamer.freedesktop.org/documentation/libav/avenc_g722.html?gi-language=c
|
||||||
|
// gstreamer1.0-libav
|
||||||
|
// avenc_g722
|
||||||
|
if err := gst.CheckPlugins([]string{"pulseaudio", "libav"}); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
pipelineStr = fmt.Sprintf(audioSrc+"avenc_g722 bitrate=%d"+pipelineStr, device, bitrate*1000)
|
||||||
|
case codec.PCMU().Name:
|
||||||
|
// https://gstreamer.freedesktop.org/documentation/mulaw/mulawenc.html?gi-language=c
|
||||||
|
// gstreamer1.0-plugins-good
|
||||||
|
// audio/x-raw, rate=8000 ! mulawenc
|
||||||
|
if err := gst.CheckPlugins([]string{"pulseaudio", "mulaw"}); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
pipelineStr = fmt.Sprintf(audioSrc+"audio/x-raw, rate=8000 ! mulawenc"+pipelineStr, device)
|
||||||
|
case codec.PCMA().Name:
|
||||||
|
// https://gstreamer.freedesktop.org/documentation/alaw/alawenc.html?gi-language=c
|
||||||
|
// gstreamer1.0-plugins-good
|
||||||
|
// audio/x-raw, rate=8000 ! alawenc
|
||||||
|
if err := gst.CheckPlugins([]string{"pulseaudio", "alaw"}); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
pipelineStr = fmt.Sprintf(audioSrc+"audio/x-raw, rate=8000 ! alawenc"+pipelineStr, device)
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unknown codec %s", rtpCodec.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pipelineStr, nil
|
||||||
|
}
|
190
server/internal/capture/streamsink.go
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
package capture
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"m1k1o/neko/internal/capture/gst"
|
||||||
|
"m1k1o/neko/internal/types"
|
||||||
|
"m1k1o/neko/internal/types/codec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StreamSinkManagerCtx struct {
|
||||||
|
logger zerolog.Logger
|
||||||
|
mu sync.Mutex
|
||||||
|
wg sync.WaitGroup
|
||||||
|
|
||||||
|
codec codec.RTPCodec
|
||||||
|
pipeline *gst.Pipeline
|
||||||
|
pipelineMu sync.Mutex
|
||||||
|
pipelineFn func() (string, error)
|
||||||
|
|
||||||
|
listeners int
|
||||||
|
listenersMu sync.Mutex
|
||||||
|
|
||||||
|
sampleFn func(sample types.Sample)
|
||||||
|
}
|
||||||
|
|
||||||
|
func streamSinkNew(codec codec.RTPCodec, pipelineFn func() (string, error), video_id string) *StreamSinkManagerCtx {
|
||||||
|
logger := log.With().
|
||||||
|
Str("module", "capture").
|
||||||
|
Str("submodule", "stream-sink").
|
||||||
|
Str("video_id", video_id).Logger()
|
||||||
|
|
||||||
|
manager := &StreamSinkManagerCtx{
|
||||||
|
logger: logger,
|
||||||
|
codec: codec,
|
||||||
|
pipelineFn: pipelineFn,
|
||||||
|
}
|
||||||
|
|
||||||
|
return manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *StreamSinkManagerCtx) shutdown() {
|
||||||
|
manager.logger.Info().Msgf("shutdown")
|
||||||
|
|
||||||
|
manager.destroyPipeline()
|
||||||
|
manager.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *StreamSinkManagerCtx) OnSample(listener func(sample types.Sample)) {
|
||||||
|
manager.sampleFn = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *StreamSinkManagerCtx) Codec() codec.RTPCodec {
|
||||||
|
return manager.codec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *StreamSinkManagerCtx) start() error {
|
||||||
|
if manager.listeners == 0 {
|
||||||
|
err := manager.createPipeline()
|
||||||
|
if err != nil && !errors.Is(err, types.ErrCapturePipelineAlreadyExists) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.logger.Info().Msgf("first listener, starting")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *StreamSinkManagerCtx) stop() {
|
||||||
|
if manager.listeners == 0 {
|
||||||
|
manager.destroyPipeline()
|
||||||
|
manager.logger.Info().Msgf("last listener, stopping")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *StreamSinkManagerCtx) addListener() {
|
||||||
|
manager.listenersMu.Lock()
|
||||||
|
manager.listeners++
|
||||||
|
manager.listenersMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *StreamSinkManagerCtx) removeListener() {
|
||||||
|
manager.listenersMu.Lock()
|
||||||
|
manager.listeners--
|
||||||
|
manager.listenersMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *StreamSinkManagerCtx) AddListener() error {
|
||||||
|
manager.mu.Lock()
|
||||||
|
defer manager.mu.Unlock()
|
||||||
|
|
||||||
|
// start if stopped
|
||||||
|
if err := manager.start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add listener
|
||||||
|
manager.addListener()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *StreamSinkManagerCtx) RemoveListener() error {
|
||||||
|
manager.mu.Lock()
|
||||||
|
defer manager.mu.Unlock()
|
||||||
|
|
||||||
|
// remove listener
|
||||||
|
manager.removeListener()
|
||||||
|
|
||||||
|
// stop if started
|
||||||
|
manager.stop()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *StreamSinkManagerCtx) ListenersCount() int {
|
||||||
|
manager.listenersMu.Lock()
|
||||||
|
defer manager.listenersMu.Unlock()
|
||||||
|
|
||||||
|
return manager.listeners
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *StreamSinkManagerCtx) Started() bool {
|
||||||
|
return manager.ListenersCount() > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *StreamSinkManagerCtx) createPipeline() error {
|
||||||
|
manager.pipelineMu.Lock()
|
||||||
|
defer manager.pipelineMu.Unlock()
|
||||||
|
|
||||||
|
if manager.pipeline != nil {
|
||||||
|
return types.ErrCapturePipelineAlreadyExists
|
||||||
|
}
|
||||||
|
|
||||||
|
pipelineStr, err := manager.pipelineFn()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.logger.Info().
|
||||||
|
Str("codec", manager.codec.Name).
|
||||||
|
Str("src", pipelineStr).
|
||||||
|
Msgf("creating pipeline")
|
||||||
|
|
||||||
|
manager.pipeline, err = gst.CreatePipeline(pipelineStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.pipeline.AttachAppsink("appsink")
|
||||||
|
manager.pipeline.Play()
|
||||||
|
|
||||||
|
manager.wg.Add(1)
|
||||||
|
pipeline := manager.pipeline
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
manager.logger.Debug().Msg("started emitting samples")
|
||||||
|
defer manager.wg.Done()
|
||||||
|
|
||||||
|
for {
|
||||||
|
sample, ok := <-pipeline.Sample
|
||||||
|
if !ok {
|
||||||
|
manager.logger.Debug().Msg("stopped emitting samples")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.sampleFn(sample)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *StreamSinkManagerCtx) destroyPipeline() {
|
||||||
|
manager.pipelineMu.Lock()
|
||||||
|
defer manager.pipelineMu.Unlock()
|
||||||
|
|
||||||
|
if manager.pipeline == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.pipeline.Destroy()
|
||||||
|
manager.logger.Info().Msgf("destroying pipeline")
|
||||||
|
manager.pipeline = nil
|
||||||
|
}
|
224
server/internal/config/capture.go
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"m1k1o/neko/internal/types/codec"
|
||||||
|
|
||||||
|
"github.com/pion/webrtc/v3"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Capture struct {
|
||||||
|
// video
|
||||||
|
Display string
|
||||||
|
VideoCodec codec.RTPCodec
|
||||||
|
VideoHWEnc string // TODO: Pipeline builder.
|
||||||
|
VideoBitrate uint // TODO: Pipeline builder.
|
||||||
|
VideoMaxFPS int16 // TODO: Pipeline builder.
|
||||||
|
VideoPipeline string
|
||||||
|
|
||||||
|
// audio
|
||||||
|
AudioDevice string
|
||||||
|
AudioCodec codec.RTPCodec
|
||||||
|
AudioBitrate uint // TODO: Pipeline builder.
|
||||||
|
AudioPipeline string
|
||||||
|
|
||||||
|
// broadcast
|
||||||
|
BroadcastPipeline string
|
||||||
|
BroadcastUrl string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Capture) Init(cmd *cobra.Command) error {
|
||||||
|
//
|
||||||
|
// video
|
||||||
|
//
|
||||||
|
|
||||||
|
cmd.PersistentFlags().String("display", ":99.0", "XDisplay to capture")
|
||||||
|
if err := viper.BindPFlag("display", cmd.PersistentFlags().Lookup("display")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.PersistentFlags().String("video_codec", "vp8", "video codec to be used")
|
||||||
|
if err := viper.BindPFlag("video_codec", cmd.PersistentFlags().Lookup("video_codec")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: video codec
|
||||||
|
cmd.PersistentFlags().Bool("vp8", false, "DEPRECATED: use video_codec")
|
||||||
|
if err := viper.BindPFlag("vp8", cmd.PersistentFlags().Lookup("vp8")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: video codec
|
||||||
|
cmd.PersistentFlags().Bool("vp9", false, "DEPRECATED: use video_codec")
|
||||||
|
if err := viper.BindPFlag("vp9", cmd.PersistentFlags().Lookup("vp9")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: video codec
|
||||||
|
cmd.PersistentFlags().Bool("h264", false, "DEPRECATED: use video_codec")
|
||||||
|
if err := viper.BindPFlag("h264", cmd.PersistentFlags().Lookup("h264")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.PersistentFlags().String("hwenc", "", "use hardware accelerated encoding")
|
||||||
|
if err := viper.BindPFlag("hwenc", cmd.PersistentFlags().Lookup("hwenc")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.PersistentFlags().Int("video_bitrate", 3072, "video bitrate in kbit/s")
|
||||||
|
if err := viper.BindPFlag("video_bitrate", cmd.PersistentFlags().Lookup("video_bitrate")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.PersistentFlags().Int("max_fps", 25, "maximum fps delivered via WebRTC, 0 is for no maximum")
|
||||||
|
if err := viper.BindPFlag("max_fps", cmd.PersistentFlags().Lookup("max_fps")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.PersistentFlags().String("video", "", "video codec parameters to use for streaming")
|
||||||
|
if err := viper.BindPFlag("video", cmd.PersistentFlags().Lookup("video")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// audio
|
||||||
|
//
|
||||||
|
|
||||||
|
cmd.PersistentFlags().String("device", "auto_null.monitor", "audio device to capture")
|
||||||
|
if err := viper.BindPFlag("device", cmd.PersistentFlags().Lookup("device")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.PersistentFlags().String("audio_codec", "opus", "audio codec to be used")
|
||||||
|
if err := viper.BindPFlag("audio_codec", cmd.PersistentFlags().Lookup("audio_codec")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: audio codec
|
||||||
|
cmd.PersistentFlags().Bool("opus", false, "DEPRECATED: use audio_codec")
|
||||||
|
if err := viper.BindPFlag("opus", cmd.PersistentFlags().Lookup("opus")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: audio codec
|
||||||
|
cmd.PersistentFlags().Bool("g722", false, "DEPRECATED: use audio_codec")
|
||||||
|
if err := viper.BindPFlag("g722", cmd.PersistentFlags().Lookup("g722")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: audio codec
|
||||||
|
cmd.PersistentFlags().Bool("pcmu", false, "DEPRECATED: use audio_codec")
|
||||||
|
if err := viper.BindPFlag("pcmu", cmd.PersistentFlags().Lookup("pcmu")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: audio codec
|
||||||
|
cmd.PersistentFlags().Bool("pcma", false, "DEPRECATED: use audio_codec")
|
||||||
|
if err := viper.BindPFlag("pcma", cmd.PersistentFlags().Lookup("pcma")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// audio codecs
|
||||||
|
|
||||||
|
cmd.PersistentFlags().Int("audio_bitrate", 128, "audio bitrate in kbit/s")
|
||||||
|
if err := viper.BindPFlag("audio_bitrate", cmd.PersistentFlags().Lookup("audio_bitrate")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.PersistentFlags().String("audio", "", "audio codec parameters to use for streaming")
|
||||||
|
if err := viper.BindPFlag("audio", cmd.PersistentFlags().Lookup("audio")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// broadcast
|
||||||
|
//
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.PersistentFlags().String("broadcast_url", "", "URL for broadcasting, setting this value will automatically enable broadcasting")
|
||||||
|
if err := viper.BindPFlag("broadcast_url", cmd.PersistentFlags().Lookup("broadcast_url")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Capture) Set() {
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
//
|
||||||
|
// video
|
||||||
|
//
|
||||||
|
|
||||||
|
s.Display = viper.GetString("display")
|
||||||
|
|
||||||
|
videoCodec := viper.GetString("video_codec")
|
||||||
|
s.VideoCodec, ok = codec.ParseStr(videoCodec)
|
||||||
|
if !ok || s.VideoCodec.Type != webrtc.RTPCodecTypeVideo {
|
||||||
|
log.Warn().Str("codec", videoCodec).Msgf("unknown video codec, using Vp8")
|
||||||
|
s.VideoCodec = codec.VP8()
|
||||||
|
}
|
||||||
|
|
||||||
|
if viper.GetBool("vp8") {
|
||||||
|
s.VideoCodec = codec.VP8()
|
||||||
|
log.Warn().Msg("you are using deprecated config setting 'NEKO_VP8=true', use 'NEKO_VIDEO_CODEC=vp8' instead")
|
||||||
|
} else if viper.GetBool("vp9") {
|
||||||
|
s.VideoCodec = codec.VP9()
|
||||||
|
log.Warn().Msg("you are using deprecated config setting 'NEKO_VP9=true', use 'NEKO_VIDEO_CODEC=vp9' instead")
|
||||||
|
} else if viper.GetBool("h264") {
|
||||||
|
s.VideoCodec = codec.H264()
|
||||||
|
log.Warn().Msg("you are using deprecated config setting 'NEKO_H264=true', use 'NEKO_VIDEO_CODEC=h264' instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
videoHWEnc := ""
|
||||||
|
if viper.GetString("hwenc") == "VAAPI" {
|
||||||
|
videoHWEnc = "VAAPI"
|
||||||
|
}
|
||||||
|
s.VideoHWEnc = videoHWEnc
|
||||||
|
|
||||||
|
s.VideoBitrate = viper.GetUint("video_bitrate")
|
||||||
|
s.VideoMaxFPS = int16(viper.GetInt("max_fps"))
|
||||||
|
s.VideoPipeline = viper.GetString("video")
|
||||||
|
|
||||||
|
//
|
||||||
|
// audio
|
||||||
|
//
|
||||||
|
|
||||||
|
s.AudioDevice = viper.GetString("device")
|
||||||
|
|
||||||
|
audioCodec := viper.GetString("audio_codec")
|
||||||
|
s.AudioCodec, ok = codec.ParseStr(audioCodec)
|
||||||
|
if !ok || s.AudioCodec.Type != webrtc.RTPCodecTypeAudio {
|
||||||
|
log.Warn().Str("codec", audioCodec).Msgf("unknown audio codec, using Opus")
|
||||||
|
s.AudioCodec = codec.Opus()
|
||||||
|
}
|
||||||
|
|
||||||
|
if viper.GetBool("opus") {
|
||||||
|
s.AudioCodec = codec.Opus()
|
||||||
|
log.Warn().Msg("you are using deprecated config setting 'NEKO_OPUS=true', use 'NEKO_VIDEO_CODEC=opus' instead")
|
||||||
|
} else if viper.GetBool("g722") {
|
||||||
|
s.AudioCodec = codec.G722()
|
||||||
|
log.Warn().Msg("you are using deprecated config setting 'NEKO_G722=true', use 'NEKO_VIDEO_CODEC=g722' instead")
|
||||||
|
} else if viper.GetBool("pcmu") {
|
||||||
|
s.AudioCodec = codec.PCMU()
|
||||||
|
log.Warn().Msg("you are using deprecated config setting 'NEKO_PCMU=true', use 'NEKO_VIDEO_CODEC=pcmu' instead")
|
||||||
|
} else if viper.GetBool("pcma") {
|
||||||
|
s.AudioCodec = codec.PCMA()
|
||||||
|
log.Warn().Msg("you are using deprecated config setting 'NEKO_PCMA=true', use 'NEKO_VIDEO_CODEC=pcma' instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.AudioBitrate = viper.GetUint("audio_bitrate")
|
||||||
|
s.AudioPipeline = viper.GetString("audio")
|
||||||
|
|
||||||
|
//
|
||||||
|
// broadcast
|
||||||
|
//
|
||||||
|
|
||||||
|
s.BroadcastPipeline = viper.GetString("broadcast_pipeline")
|
||||||
|
s.BroadcastUrl = viper.GetString("broadcast_url")
|
||||||
|
}
|
51
server/internal/config/desktop.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Desktop struct {
|
||||||
|
Display string
|
||||||
|
|
||||||
|
ScreenWidth int
|
||||||
|
ScreenHeight int
|
||||||
|
ScreenRate int16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Desktop) Init(cmd *cobra.Command) error {
|
||||||
|
cmd.PersistentFlags().String("screen", "1280x720@30", "default screen resolution and framerate")
|
||||||
|
if err := viper.BindPFlag("screen", cmd.PersistentFlags().Lookup("screen")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Desktop) Set() {
|
||||||
|
// Display is provided by env variable
|
||||||
|
s.Display = os.Getenv("DISPLAY")
|
||||||
|
|
||||||
|
s.ScreenWidth = 1280
|
||||||
|
s.ScreenHeight = 720
|
||||||
|
s.ScreenRate = 30
|
||||||
|
|
||||||
|
r := regexp.MustCompile(`([0-9]{1,4})x([0-9]{1,4})@([0-9]{1,3})`)
|
||||||
|
res := r.FindStringSubmatch(viper.GetString("screen"))
|
||||||
|
|
||||||
|
if len(res) > 0 {
|
||||||
|
width, err1 := strconv.ParseInt(res[1], 10, 64)
|
||||||
|
height, err2 := strconv.ParseInt(res[2], 10, 64)
|
||||||
|
rate, err3 := strconv.ParseInt(res[3], 10, 64)
|
||||||
|
|
||||||
|
if err1 == nil && err2 == nil && err3 == nil {
|
||||||
|
s.ScreenWidth = int(width)
|
||||||
|
s.ScreenHeight = int(height)
|
||||||
|
s.ScreenRate = int16(rate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,18 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Cert string
|
Cert string
|
||||||
Key string
|
Key string
|
||||||
Bind string
|
Bind string
|
||||||
Static string
|
Static string
|
||||||
|
PathPrefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Server) Init(cmd *cobra.Command) error {
|
func (Server) Init(cmd *cobra.Command) error {
|
||||||
@ -33,6 +36,11 @@ func (Server) Init(cmd *cobra.Command) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.PersistentFlags().String("path_prefix", "/", "path prefix for HTTP requests")
|
||||||
|
if err := viper.BindPFlag("path_prefix", cmd.PersistentFlags().Lookup("path_prefix")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,4 +49,5 @@ func (s *Server) Set() {
|
|||||||
s.Key = viper.GetString("key")
|
s.Key = viper.GetString("key")
|
||||||
s.Bind = viper.GetString("bind")
|
s.Bind = viper.GetString("bind")
|
||||||
s.Static = viper.GetString("static")
|
s.Static = viper.GetString("static")
|
||||||
|
s.PathPrefix = path.Join("/", path.Clean(viper.GetString("path_prefix")))
|
||||||
}
|
}
|
11
server/internal/desktop/clipboard.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package desktop
|
||||||
|
|
||||||
|
import "m1k1o/neko/internal/desktop/clipboard"
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) ReadClipboard() string {
|
||||||
|
return clipboard.Read()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) WriteClipboard(data string) {
|
||||||
|
clipboard.Write(data)
|
||||||
|
}
|
21
server/internal/desktop/clipboard/clipboard.c
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#include "clipboard.h"
|
||||||
|
|
||||||
|
static clipboard_c *CLIPBOARD = NULL;
|
||||||
|
|
||||||
|
clipboard_c *getClipboard(void) {
|
||||||
|
if (CLIPBOARD == NULL) {
|
||||||
|
CLIPBOARD = clipboard_new(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CLIPBOARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipboardSet(char *src) {
|
||||||
|
clipboard_c *cb = getClipboard();
|
||||||
|
clipboard_set_text_ex(cb, src, strlen(src), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *ClipboardGet() {
|
||||||
|
clipboard_c *cb = getClipboard();
|
||||||
|
return clipboard_text_ex(cb, NULL, 0);
|
||||||
|
}
|
35
server/internal/desktop/clipboard/clipboard.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package clipboard
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo linux LDFLAGS: /usr/local/lib/libclipboard.a -lxcb
|
||||||
|
|
||||||
|
#include "clipboard.h"
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mu = sync.Mutex{}
|
||||||
|
|
||||||
|
func Read() string {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
clipboardUnsafe := C.ClipboardGet()
|
||||||
|
defer C.free(unsafe.Pointer(clipboardUnsafe))
|
||||||
|
|
||||||
|
return C.GoString(clipboardUnsafe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Write(data string) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
clipboardUnsafe := C.CString(data)
|
||||||
|
defer C.free(unsafe.Pointer(clipboardUnsafe))
|
||||||
|
|
||||||
|
C.ClipboardSet(clipboardUnsafe)
|
||||||
|
}
|
9
server/internal/desktop/clipboard/clipboard.h
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <libclipboard.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
clipboard_c *getClipboard(void);
|
||||||
|
|
||||||
|
void ClipboardSet(char *src);
|
||||||
|
char *ClipboardGet();
|
98
server/internal/desktop/manager.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package desktop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"m1k1o/neko/internal/config"
|
||||||
|
"m1k1o/neko/internal/desktop/xevent"
|
||||||
|
"m1k1o/neko/internal/desktop/xorg"
|
||||||
|
|
||||||
|
"github.com/kataras/go-events"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mu = sync.Mutex{}
|
||||||
|
|
||||||
|
type DesktopManagerCtx struct {
|
||||||
|
logger zerolog.Logger
|
||||||
|
wg sync.WaitGroup
|
||||||
|
shutdown chan struct{}
|
||||||
|
emmiter events.EventEmmiter
|
||||||
|
config *config.Desktop
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config *config.Desktop) *DesktopManagerCtx {
|
||||||
|
return &DesktopManagerCtx{
|
||||||
|
logger: log.With().Str("module", "desktop").Logger(),
|
||||||
|
shutdown: make(chan struct{}),
|
||||||
|
emmiter: events.New(),
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) Start() {
|
||||||
|
if xorg.DisplayOpen(manager.config.Display) {
|
||||||
|
manager.logger.Panic().Str("display", manager.config.Display).Msg("unable to open display")
|
||||||
|
}
|
||||||
|
|
||||||
|
xorg.GetScreenConfigurations()
|
||||||
|
|
||||||
|
err := xorg.ChangeScreenSize(manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate)
|
||||||
|
manager.logger.Err(err).
|
||||||
|
Str("screen_size", fmt.Sprintf("%dx%d@%d", manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate)).
|
||||||
|
Msgf("setting initial screen size")
|
||||||
|
|
||||||
|
go xevent.EventLoop(manager.config.Display)
|
||||||
|
|
||||||
|
manager.OnEventError(func(error_code uint8, message string, request_code uint8, minor_code uint8) {
|
||||||
|
manager.logger.Warn().
|
||||||
|
Uint8("error_code", error_code).
|
||||||
|
Str("message", message).
|
||||||
|
Uint8("request_code", request_code).
|
||||||
|
Uint8("minor_code", minor_code).
|
||||||
|
Msg("X event error occurred")
|
||||||
|
})
|
||||||
|
|
||||||
|
manager.wg.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer manager.wg.Done()
|
||||||
|
|
||||||
|
ticker := time.NewTicker(1 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-manager.shutdown:
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
xorg.CheckKeys(time.Second * 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) OnBeforeScreenSizeChange(listener func()) {
|
||||||
|
manager.emmiter.On("before_screen_size_change", func(payload ...any) {
|
||||||
|
listener()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) OnAfterScreenSizeChange(listener func()) {
|
||||||
|
manager.emmiter.On("after_screen_size_change", func(payload ...any) {
|
||||||
|
listener()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) Shutdown() error {
|
||||||
|
manager.logger.Info().Msgf("desktop shutting down")
|
||||||
|
|
||||||
|
close(manager.shutdown)
|
||||||
|
manager.wg.Wait()
|
||||||
|
|
||||||
|
xorg.DisplayClose()
|
||||||
|
return nil
|
||||||
|
}
|
33
server/internal/desktop/xevent.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package desktop
|
||||||
|
|
||||||
|
import "m1k1o/neko/internal/desktop/xevent"
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) OnCursorChanged(listener func(serial uint64)) {
|
||||||
|
xevent.Emmiter.On("cursor-changed", func(payload ...any) {
|
||||||
|
listener(payload[0].(uint64))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) OnClipboardUpdated(listener func()) {
|
||||||
|
xevent.Emmiter.On("clipboard-updated", func(payload ...any) {
|
||||||
|
listener()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) OnFileChooserDialogOpened(listener func()) {
|
||||||
|
xevent.Emmiter.On("file-chooser-dialog-opened", func(payload ...any) {
|
||||||
|
listener()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) OnFileChooserDialogClosed(listener func()) {
|
||||||
|
xevent.Emmiter.On("file-chooser-dialog-closed", func(payload ...any) {
|
||||||
|
listener()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) OnEventError(listener func(error_code uint8, message string, request_code uint8, minor_code uint8)) {
|
||||||
|
xevent.Emmiter.On("event-error", func(payload ...any) {
|
||||||
|
listener(payload[0].(uint8), payload[1].(string), payload[2].(uint8), payload[3].(uint8))
|
||||||
|
})
|
||||||
|
}
|
123
server/internal/desktop/xevent/xevent.c
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#include "xevent.h"
|
||||||
|
|
||||||
|
static int XEventError(Display *display, XErrorEvent *event) {
|
||||||
|
char message[100];
|
||||||
|
|
||||||
|
int error;
|
||||||
|
error = XGetErrorText(display, event->error_code, message, sizeof(message));
|
||||||
|
if (error) {
|
||||||
|
goXEventError(event, "Could not get error message.");
|
||||||
|
} else {
|
||||||
|
goXEventError(event, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XEventLoop(char *name) {
|
||||||
|
Display *display = XOpenDisplay(name);
|
||||||
|
Window root = RootWindow(display, 0);
|
||||||
|
|
||||||
|
int xfixes_event_base, xfixes_error_base;
|
||||||
|
if (!XFixesQueryExtension(display, &xfixes_event_base, &xfixes_error_base)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Atom WM_WINDOW_ROLE = XInternAtom(display, "WM_WINDOW_ROLE", 1);
|
||||||
|
Atom XA_CLIPBOARD = XInternAtom(display, "CLIPBOARD", 0);
|
||||||
|
XFixesSelectSelectionInput(display, root, XA_CLIPBOARD, XFixesSetSelectionOwnerNotifyMask);
|
||||||
|
XFixesSelectCursorInput(display, root, XFixesDisplayCursorNotifyMask);
|
||||||
|
XSelectInput(display, root, SubstructureNotifyMask);
|
||||||
|
|
||||||
|
XSync(display, 0);
|
||||||
|
XSetErrorHandler(XEventError);
|
||||||
|
|
||||||
|
while (goXEventActive()) {
|
||||||
|
XEvent event;
|
||||||
|
XNextEvent(display, &event);
|
||||||
|
|
||||||
|
// XFixesDisplayCursorNotify
|
||||||
|
if (event.type == xfixes_event_base + 1) {
|
||||||
|
XFixesCursorNotifyEvent notifyEvent = *((XFixesCursorNotifyEvent *) &event);
|
||||||
|
if (notifyEvent.subtype == XFixesDisplayCursorNotify) {
|
||||||
|
goXEventCursorChanged(notifyEvent);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XFixesSelectionNotifyEvent
|
||||||
|
if (event.type == xfixes_event_base + XFixesSelectionNotify) {
|
||||||
|
XFixesSelectionNotifyEvent notifyEvent = *((XFixesSelectionNotifyEvent *) &event);
|
||||||
|
if (notifyEvent.subtype == XFixesSetSelectionOwnerNotify && notifyEvent.selection == XA_CLIPBOARD) {
|
||||||
|
goXEventClipboardUpdated();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigureNotify
|
||||||
|
if (event.type == ConfigureNotify) {
|
||||||
|
Window window = event.xconfigure.window;
|
||||||
|
|
||||||
|
char *name;
|
||||||
|
XFetchName(display, window, &name);
|
||||||
|
|
||||||
|
XTextProperty role;
|
||||||
|
XGetTextProperty(display, window, &role, WM_WINDOW_ROLE);
|
||||||
|
|
||||||
|
goXEventConfigureNotify(display, window, name, role.value);
|
||||||
|
XFree(name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmapNotify
|
||||||
|
if (event.type == UnmapNotify) {
|
||||||
|
Window window = event.xunmap.window;
|
||||||
|
goXEventUnmapNotify(window);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XCloseDisplay(display);
|
||||||
|
}
|
||||||
|
|
||||||
|
void XFileChooserHide(Display *display, Window window) {
|
||||||
|
Window root = RootWindow(display, 0);
|
||||||
|
|
||||||
|
// The WM_TRANSIENT_FOR property is defined by the [ICCCM] for managed windows.
|
||||||
|
// This specification extends the use of the property to override-redirect windows.
|
||||||
|
// If an override-redirect is a pop-up on behalf of another window, then the Client
|
||||||
|
// SHOULD set WM_TRANSIENT_FOR on the override-redirect to this other window.
|
||||||
|
//
|
||||||
|
// As an example, a Client should set WM_TRANSIENT_FOR on dropdown menus to the
|
||||||
|
// toplevel application window that contains the menubar.
|
||||||
|
|
||||||
|
// Remove WM_TRANSIENT_FOR
|
||||||
|
Atom WM_TRANSIENT_FOR = XInternAtom(display, "WM_TRANSIENT_FOR", 0);
|
||||||
|
XDeleteProperty(display, window, WM_TRANSIENT_FOR);
|
||||||
|
|
||||||
|
// Add _NET_WM_STATE_BELOW
|
||||||
|
XClientMessageEvent clientMessageEvent;
|
||||||
|
memset(&clientMessageEvent, 0, sizeof(clientMessageEvent));
|
||||||
|
|
||||||
|
// window = the respective client window
|
||||||
|
// message_type = _NET_WM_STATE
|
||||||
|
// format = 32
|
||||||
|
// data.l[0] = the action, as listed below
|
||||||
|
// _NET_WM_STATE_REMOVE 0 // remove/unset property
|
||||||
|
// _NET_WM_STATE_ADD 1 // add/set property
|
||||||
|
// _NET_WM_STATE_TOGGLE 2 // toggle property
|
||||||
|
// data.l[1] = first property to alter
|
||||||
|
// data.l[2] = second property to alter
|
||||||
|
// data.l[3] = source indication
|
||||||
|
// other data.l[] elements = 0
|
||||||
|
|
||||||
|
clientMessageEvent.type = ClientMessage;
|
||||||
|
clientMessageEvent.window = window;
|
||||||
|
clientMessageEvent.message_type = XInternAtom(display, "_NET_WM_STATE", 0);
|
||||||
|
clientMessageEvent.format = 32;
|
||||||
|
clientMessageEvent.data.l[0] = 1;
|
||||||
|
clientMessageEvent.data.l[1] = XInternAtom(display, "_NET_WM_STATE_BELOW", 0);
|
||||||
|
clientMessageEvent.data.l[3] = 1;
|
||||||
|
|
||||||
|
XSendEvent(display, root, 0, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&clientMessageEvent);
|
||||||
|
}
|
84
server/internal/desktop/xevent/xevent.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package xevent
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo LDFLAGS: -lX11 -lXfixes
|
||||||
|
|
||||||
|
#include "xevent.h"
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/kataras/go-events"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Emmiter events.EventEmmiter
|
||||||
|
var file_chooser_dialog_window uint32 = 0
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Emmiter = events.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
func EventLoop(display string) {
|
||||||
|
displayUnsafe := C.CString(display)
|
||||||
|
defer C.free(unsafe.Pointer(displayUnsafe))
|
||||||
|
|
||||||
|
C.XEventLoop(displayUnsafe)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export goXEventCursorChanged
|
||||||
|
func goXEventCursorChanged(event C.XFixesCursorNotifyEvent) {
|
||||||
|
Emmiter.Emit("cursor-changed", uint64(event.cursor_serial))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export goXEventClipboardUpdated
|
||||||
|
func goXEventClipboardUpdated() {
|
||||||
|
Emmiter.Emit("clipboard-updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
//export goXEventConfigureNotify
|
||||||
|
func goXEventConfigureNotify(display *C.Display, window C.Window, name *C.char, role *C.char) {
|
||||||
|
if C.GoString(role) != "GtkFileChooserDialog" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Refactor. Right now processing of this dialog relies on identifying
|
||||||
|
// via its name. When that changes to role, this condition should be removed.
|
||||||
|
if !strings.HasPrefix(C.GoString(name), "Open File") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
C.XFileChooserHide(display, window)
|
||||||
|
|
||||||
|
// Because first dialog is not put properly to background
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
C.XFileChooserHide(display, window)
|
||||||
|
|
||||||
|
if file_chooser_dialog_window == 0 {
|
||||||
|
file_chooser_dialog_window = uint32(window)
|
||||||
|
Emmiter.Emit("file-chooser-dialog-opened")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//export goXEventUnmapNotify
|
||||||
|
func goXEventUnmapNotify(window C.Window) {
|
||||||
|
if uint32(window) != file_chooser_dialog_window {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file_chooser_dialog_window = 0
|
||||||
|
Emmiter.Emit("file-chooser-dialog-closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
//export goXEventError
|
||||||
|
func goXEventError(event *C.XErrorEvent, message *C.char) {
|
||||||
|
Emmiter.Emit("event-error", uint8(event.error_code), C.GoString(message), uint8(event.request_code), uint8(event.minor_code))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export goXEventActive
|
||||||
|
func goXEventActive() C.int {
|
||||||
|
return C.int(1)
|
||||||
|
}
|
20
server/internal/desktop/xevent/xevent.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <X11/Xutil.h>
|
||||||
|
#include <X11/Xatom.h>
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/extensions/Xfixes.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
extern void goXEventCursorChanged(XFixesCursorNotifyEvent event);
|
||||||
|
extern void goXEventClipboardUpdated();
|
||||||
|
extern void goXEventConfigureNotify(Display *display, Window window, char *name, char *role);
|
||||||
|
extern void goXEventUnmapNotify(Window window);
|
||||||
|
extern void goXEventError(XErrorEvent *event, char *message);
|
||||||
|
extern int goXEventActive();
|
||||||
|
|
||||||
|
static int XEventError(Display *display, XErrorEvent *event);
|
||||||
|
void XEventLoop(char *display);
|
||||||
|
|
||||||
|
void XFileChooserHide(Display *display, Window window);
|
149
server/internal/desktop/xorg.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package desktop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"m1k1o/neko/internal/desktop/xorg"
|
||||||
|
"m1k1o/neko/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) Move(x, y int) {
|
||||||
|
xorg.Move(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) GetCursorPosition() (int, int) {
|
||||||
|
return xorg.GetCursorPosition()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) Scroll(x, y int) {
|
||||||
|
xorg.Scroll(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) ButtonDown(code uint32) error {
|
||||||
|
return xorg.ButtonDown(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) KeyDown(code uint32) error {
|
||||||
|
return xorg.KeyDown(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) ButtonUp(code uint32) error {
|
||||||
|
return xorg.ButtonUp(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) KeyUp(code uint32) error {
|
||||||
|
return xorg.KeyUp(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) ButtonPress(code uint32) error {
|
||||||
|
xorg.ResetKeys()
|
||||||
|
defer xorg.ResetKeys()
|
||||||
|
|
||||||
|
return xorg.ButtonDown(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) KeyPress(codes ...uint32) error {
|
||||||
|
xorg.ResetKeys()
|
||||||
|
defer xorg.ResetKeys()
|
||||||
|
|
||||||
|
for _, code := range codes {
|
||||||
|
if err := xorg.KeyDown(code); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(codes) > 1 {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) ResetKeys() {
|
||||||
|
xorg.ResetKeys()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) ScreenConfigurations() map[int]types.ScreenConfiguration {
|
||||||
|
return xorg.ScreenConfigurations
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) SetScreenSize(size types.ScreenSize) error {
|
||||||
|
mu.Lock()
|
||||||
|
manager.emmiter.Emit("before_screen_size_change")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
manager.emmiter.Emit("after_screen_size_change")
|
||||||
|
mu.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return xorg.ChangeScreenSize(size.Width, size.Height, size.Rate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) GetScreenSize() *types.ScreenSize {
|
||||||
|
return xorg.GetScreenSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) SetKeyboardMap(kbd types.KeyboardMap) error {
|
||||||
|
// TOOD: Use native API.
|
||||||
|
cmd := exec.Command("setxkbmap", "-layout", kbd.Layout, "-variant", kbd.Variant)
|
||||||
|
_, err := cmd.Output()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) GetKeyboardMap() (*types.KeyboardMap, error) {
|
||||||
|
// TOOD: Use native API.
|
||||||
|
cmd := exec.Command("setxkbmap", "-query")
|
||||||
|
res, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
kbd := types.KeyboardMap{}
|
||||||
|
|
||||||
|
re := regexp.MustCompile(`layout:\s+(.*)\n`)
|
||||||
|
arr := re.FindStringSubmatch(string(res))
|
||||||
|
if len(arr) > 1 {
|
||||||
|
kbd.Layout = arr[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
re = regexp.MustCompile(`variant:\s+(.*)\n`)
|
||||||
|
arr = re.FindStringSubmatch(string(res))
|
||||||
|
if len(arr) > 1 {
|
||||||
|
kbd.Variant = arr[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &kbd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) SetKeyboardModifiers(mod types.KeyboardModifiers) {
|
||||||
|
if mod.NumLock != nil {
|
||||||
|
xorg.SetKeyboardModifier(xorg.KbdModNumLock, *mod.NumLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mod.CapsLock != nil {
|
||||||
|
xorg.SetKeyboardModifier(xorg.KbdModCapsLock, *mod.CapsLock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) GetKeyboardModifiers() types.KeyboardModifiers {
|
||||||
|
modifiers := xorg.GetKeyboardModifiers()
|
||||||
|
|
||||||
|
NumLock := (modifiers & xorg.KbdModNumLock) != 0
|
||||||
|
CapsLock := (modifiers & xorg.KbdModCapsLock) != 0
|
||||||
|
|
||||||
|
return types.KeyboardModifiers{
|
||||||
|
NumLock: &NumLock,
|
||||||
|
CapsLock: &CapsLock,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) GetCursorImage() *types.CursorImage {
|
||||||
|
return xorg.GetCursorImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) GetScreenshotImage() *image.RGBA {
|
||||||
|
return xorg.GetScreenshotImage()
|
||||||
|
}
|
266
server/internal/desktop/xorg/xorg.c
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
#include "xorg.h"
|
||||||
|
|
||||||
|
static Display *DISPLAY = NULL;
|
||||||
|
|
||||||
|
Display *getXDisplay(void) {
|
||||||
|
return DISPLAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
int XDisplayOpen(char *name) {
|
||||||
|
DISPLAY = XOpenDisplay(name);
|
||||||
|
return DISPLAY == NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XDisplayClose(void) {
|
||||||
|
XCloseDisplay(DISPLAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
void XMove(int x, int y) {
|
||||||
|
Display *display = getXDisplay();
|
||||||
|
XWarpPointer(display, None, DefaultRootWindow(display), 0, 0, 0, 0, x, y);
|
||||||
|
XSync(display, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void XCursorPosition(int *x, int *y) {
|
||||||
|
Display *display = getXDisplay();
|
||||||
|
Window root = DefaultRootWindow(display);
|
||||||
|
Window window;
|
||||||
|
int i;
|
||||||
|
unsigned mask;
|
||||||
|
XQueryPointer(display, root, &root, &window, x, y, &i, &i, &mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
void XScroll(int x, int y) {
|
||||||
|
int ydir = 4; /* Button 4 is up, 5 is down. */
|
||||||
|
int xdir = 6;
|
||||||
|
|
||||||
|
Display *display = getXDisplay();
|
||||||
|
|
||||||
|
if (y < 0) {
|
||||||
|
ydir = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x < 0) {
|
||||||
|
xdir = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
int xi;
|
||||||
|
int yi;
|
||||||
|
|
||||||
|
for (xi = 0; xi < abs(x); xi++) {
|
||||||
|
XTestFakeButtonEvent(display, xdir, 1, CurrentTime);
|
||||||
|
XTestFakeButtonEvent(display, xdir, 0, CurrentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (yi = 0; yi < abs(y); yi++) {
|
||||||
|
XTestFakeButtonEvent(display, ydir, 1, CurrentTime);
|
||||||
|
XTestFakeButtonEvent(display, ydir, 0, CurrentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
XSync(display, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void XButton(unsigned int button, int down) {
|
||||||
|
if (button == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Display *display = getXDisplay();
|
||||||
|
XTestFakeButtonEvent(display, button, down, CurrentTime);
|
||||||
|
XSync(display, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static xkeyentry_t *xKeysHead = NULL;
|
||||||
|
|
||||||
|
void XKeyEntryAdd(KeySym keysym, KeyCode keycode) {
|
||||||
|
xkeyentry_t *entry = (xkeyentry_t *) malloc(sizeof(xkeyentry_t));
|
||||||
|
if (entry == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
entry->keysym = keysym;
|
||||||
|
entry->keycode = keycode;
|
||||||
|
entry->next = xKeysHead;
|
||||||
|
xKeysHead = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyCode XKeyEntryGet(KeySym keysym) {
|
||||||
|
xkeyentry_t *prev = NULL;
|
||||||
|
xkeyentry_t *curr = xKeysHead;
|
||||||
|
|
||||||
|
KeyCode keycode = 0;
|
||||||
|
while (curr != NULL) {
|
||||||
|
if (curr->keysym == keysym) {
|
||||||
|
keycode = curr->keycode;
|
||||||
|
|
||||||
|
if (prev == NULL) {
|
||||||
|
xKeysHead = curr->next;
|
||||||
|
} else {
|
||||||
|
prev->next = curr->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(curr);
|
||||||
|
return keycode;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = curr;
|
||||||
|
curr = curr->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
xkb = XkbGetMap(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
|
||||||
|
if (!xkb)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 keysym, int down) {
|
||||||
|
if (keysym == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Display *display = getXDisplay();
|
||||||
|
KeyCode keycode = 0;
|
||||||
|
|
||||||
|
if (!down)
|
||||||
|
keycode = XKeyEntryGet(keysym);
|
||||||
|
|
||||||
|
if (keycode == 0)
|
||||||
|
keycode = XkbKeysymToKeycode(display, keysym);
|
||||||
|
|
||||||
|
// Map non-existing keysyms to new keycodes
|
||||||
|
if (keycode == 0) {
|
||||||
|
int min, max, numcodes;
|
||||||
|
XDisplayKeycodes(display, &min, &max);
|
||||||
|
XGetKeyboardMapping(display, min, max-min, &numcodes);
|
||||||
|
|
||||||
|
keycode = (max-min+1)*numcodes;
|
||||||
|
KeySym keysym_list[numcodes];
|
||||||
|
for(int i=0;i<numcodes;i++) keysym_list[i] = keysym;
|
||||||
|
XChangeKeyboardMapping(display, keycode, numcodes, keysym_list, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (down)
|
||||||
|
XKeyEntryAdd(keysym, keycode);
|
||||||
|
|
||||||
|
XTestFakeKeyEvent(display, keycode, down, CurrentTime);
|
||||||
|
XSync(display, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void XGetScreenConfigurations() {
|
||||||
|
Display *display = getXDisplay();
|
||||||
|
Window root = RootWindow(display, 0);
|
||||||
|
XRRScreenSize *xrrs;
|
||||||
|
int num_sizes;
|
||||||
|
|
||||||
|
xrrs = XRRSizes(display, 0, &num_sizes);
|
||||||
|
for (int i = 0; i < num_sizes; i++) {
|
||||||
|
short *rates;
|
||||||
|
int num_rates;
|
||||||
|
|
||||||
|
goCreateScreenSize(i, xrrs[i].width, xrrs[i].height, xrrs[i].mwidth, xrrs[i].mheight);
|
||||||
|
rates = XRRRates(display, 0, i, &num_rates);
|
||||||
|
for (int j = 0; j < num_rates; j++) {
|
||||||
|
goSetScreenRates(i, j, rates[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void XSetScreenConfiguration(int index, short rate) {
|
||||||
|
Display *display = getXDisplay();
|
||||||
|
Window root = RootWindow(display, 0);
|
||||||
|
XRRSetScreenConfigAndRate(display, XRRGetScreenInfo(display, root), root, index, RR_Rotate_0, rate, CurrentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
int XGetScreenSize() {
|
||||||
|
Display *display = getXDisplay();
|
||||||
|
XRRScreenConfiguration *conf = XRRGetScreenInfo(display, RootWindow(display, 0));
|
||||||
|
Rotation original_rotation;
|
||||||
|
return XRRConfigCurrentConfiguration(conf, &original_rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
short XGetScreenRate() {
|
||||||
|
Display *display = getXDisplay();
|
||||||
|
XRRScreenConfiguration *conf = XRRGetScreenInfo(display, RootWindow(display, 0));
|
||||||
|
return XRRConfigCurrentRate(conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void XSetKeyboardModifier(int mod, int on) {
|
||||||
|
Display *display = getXDisplay();
|
||||||
|
XkbLockModifiers(display, XkbUseCoreKbd, mod, on ? mod : 0);
|
||||||
|
XFlush(display);
|
||||||
|
}
|
||||||
|
|
||||||
|
char XGetKeyboardModifiers() {
|
||||||
|
XkbStateRec xkbState;
|
||||||
|
Display *display = getXDisplay();
|
||||||
|
XkbGetState(display, XkbUseCoreKbd, &xkbState);
|
||||||
|
return xkbState.locked_mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
XFixesCursorImage *XGetCursorImage(void) {
|
||||||
|
Display *display = getXDisplay();
|
||||||
|
return XFixesGetCursorImage(display);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *XGetScreenshot(int *w, int *h) {
|
||||||
|
Display *display = getXDisplay();
|
||||||
|
Window root = DefaultRootWindow(display);
|
||||||
|
|
||||||
|
XWindowAttributes attr;
|
||||||
|
XGetWindowAttributes(display, root, &attr);
|
||||||
|
int width = attr.width;
|
||||||
|
int height = attr.height;
|
||||||
|
|
||||||
|
XImage *ximage = XGetImage(display, root, 0, 0, width, height, AllPlanes, ZPixmap);
|
||||||
|
|
||||||
|
*w = width;
|
||||||
|
*h = height;
|
||||||
|
char *pixels = (char *)malloc(width * height * 3);
|
||||||
|
|
||||||
|
for (int row = 0; row < height; row++) {
|
||||||
|
for (int col = 0; col < width; col++) {
|
||||||
|
int pos = ((row * width) + col) * 3;
|
||||||
|
unsigned long pixel = XGetPixel(ximage, col, row);
|
||||||
|
|
||||||
|
pixels[pos] = (pixel & ximage->red_mask) >> 16;
|
||||||
|
pixels[pos+1] = (pixel & ximage->green_mask) >> 8;
|
||||||
|
pixels[pos+2] = pixel & ximage->blue_mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XDestroyImage(ximage);
|
||||||
|
return pixels;
|
||||||
|
}
|
320
server/internal/desktop/xorg/xorg.go
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
package xorg
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo LDFLAGS: -lX11 -lXrandr -lXtst -lXfixes
|
||||||
|
|
||||||
|
#include "xorg.h"
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"m1k1o/neko/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate ./keysymdef.sh
|
||||||
|
|
||||||
|
type KbdMod uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
KbdModCapsLock KbdMod = 2
|
||||||
|
KbdModNumLock KbdMod = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
var ScreenConfigurations = make(map[int]types.ScreenConfiguration)
|
||||||
|
|
||||||
|
var debounce_button = make(map[uint32]time.Time)
|
||||||
|
var debounce_key = make(map[uint32]time.Time)
|
||||||
|
var mu = sync.Mutex{}
|
||||||
|
|
||||||
|
func GetScreenConfigurations() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
C.XGetScreenConfigurations()
|
||||||
|
}
|
||||||
|
|
||||||
|
func DisplayOpen(display string) bool {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
displayUnsafe := C.CString(display)
|
||||||
|
defer C.free(unsafe.Pointer(displayUnsafe))
|
||||||
|
|
||||||
|
ok := C.XDisplayOpen(displayUnsafe)
|
||||||
|
return int(ok) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func DisplayClose() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
C.XDisplayClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Move(x, y int) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
C.XMove(C.int(x), C.int(y))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCursorPosition() (int, int) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
var x C.int
|
||||||
|
var y C.int
|
||||||
|
C.XCursorPosition(&x, &y)
|
||||||
|
|
||||||
|
return int(x), int(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Scroll(x, y int) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
C.XScroll(C.int(x), C.int(y))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ButtonDown(code uint32) error {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
if _, ok := debounce_button[code]; ok {
|
||||||
|
return fmt.Errorf("debounced button %v", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
debounce_button[code] = time.Now()
|
||||||
|
|
||||||
|
C.XButton(C.uint(code), C.int(1))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func KeyDown(code uint32) error {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
if _, ok := debounce_key[code]; ok {
|
||||||
|
return fmt.Errorf("debounced key %v", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
debounce_key[code] = time.Now()
|
||||||
|
|
||||||
|
C.XKey(C.KeySym(code), C.int(1))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ButtonUp(code uint32) error {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
if _, ok := debounce_button[code]; !ok {
|
||||||
|
return fmt.Errorf("debounced button %v", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(debounce_button, code)
|
||||||
|
|
||||||
|
C.XButton(C.uint(code), C.int(0))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func KeyUp(code uint32) error {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
if _, ok := debounce_key[code]; !ok {
|
||||||
|
return fmt.Errorf("debounced key %v", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(debounce_key, code)
|
||||||
|
|
||||||
|
C.XKey(C.KeySym(code), C.int(0))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResetKeys() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
for code := range debounce_button {
|
||||||
|
C.XButton(C.uint(code), C.int(0))
|
||||||
|
delete(debounce_button, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
for code := range debounce_key {
|
||||||
|
C.XKey(C.KeySym(code), C.int(0))
|
||||||
|
delete(debounce_key, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckKeys(duration time.Duration) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
t := time.Now()
|
||||||
|
for code, start := range debounce_button {
|
||||||
|
if t.Sub(start) < duration {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
C.XButton(C.uint(code), C.int(0))
|
||||||
|
delete(debounce_button, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
for code, start := range debounce_key {
|
||||||
|
if t.Sub(start) < duration {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
C.XKey(C.KeySym(code), C.int(0))
|
||||||
|
delete(debounce_key, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeScreenSize(width int, height int, rate int16) error {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
for index, size := range ScreenConfigurations {
|
||||||
|
if size.Width == width && size.Height == height {
|
||||||
|
for _, fps := range size.Rates {
|
||||||
|
if rate == fps {
|
||||||
|
C.XSetScreenConfiguration(C.int(index), C.short(fps))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("unknown screen configuration %dx%d@%d", width, height, rate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetScreenSize() *types.ScreenSize {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
index := int(C.XGetScreenSize())
|
||||||
|
rate := int16(C.XGetScreenRate())
|
||||||
|
|
||||||
|
if conf, ok := ScreenConfigurations[index]; ok {
|
||||||
|
return &types.ScreenSize{
|
||||||
|
Width: conf.Width,
|
||||||
|
Height: conf.Height,
|
||||||
|
Rate: rate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetKeyboardModifier(mod KbdMod, active bool) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
num := C.int(0)
|
||||||
|
if active {
|
||||||
|
num = C.int(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
C.XSetKeyboardModifier(C.int(mod), num)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetKeyboardModifiers() KbdMod {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
return KbdMod(C.XGetKeyboardModifiers())
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCursorImage() *types.CursorImage {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
cur := C.XGetCursorImage()
|
||||||
|
defer C.XFree(unsafe.Pointer(cur))
|
||||||
|
|
||||||
|
width := int(cur.width)
|
||||||
|
height := int(cur.height)
|
||||||
|
|
||||||
|
// Xlib stores 32-bit data in longs, even if longs are 64-bits long.
|
||||||
|
pixels := C.GoBytes(unsafe.Pointer(cur.pixels), C.int(width*height*8))
|
||||||
|
|
||||||
|
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||||
|
for y := 0; y < height; y++ {
|
||||||
|
for x := 0; x < width; x++ {
|
||||||
|
pos := ((y * width) + x) * 8
|
||||||
|
|
||||||
|
img.SetRGBA(x, y, color.RGBA{
|
||||||
|
A: pixels[pos+3],
|
||||||
|
R: pixels[pos+2],
|
||||||
|
G: pixels[pos+1],
|
||||||
|
B: pixels[pos+0],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.CursorImage{
|
||||||
|
Width: uint16(width),
|
||||||
|
Height: uint16(height),
|
||||||
|
Xhot: uint16(cur.xhot),
|
||||||
|
Yhot: uint16(cur.yhot),
|
||||||
|
Serial: uint64(cur.cursor_serial),
|
||||||
|
Image: img,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetScreenshotImage() *image.RGBA {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
var w, h C.int
|
||||||
|
pixelsUnsafe := C.XGetScreenshot(&w, &h)
|
||||||
|
pixels := C.GoBytes(unsafe.Pointer(pixelsUnsafe), w*h*3)
|
||||||
|
defer C.free(unsafe.Pointer(pixelsUnsafe))
|
||||||
|
|
||||||
|
width := int(w)
|
||||||
|
height := int(h)
|
||||||
|
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||||
|
for row := 0; row < height; row++ {
|
||||||
|
for col := 0; col < width; col++ {
|
||||||
|
pos := ((row * width) + col) * 3
|
||||||
|
|
||||||
|
img.SetRGBA(col, row, color.RGBA{
|
||||||
|
R: uint8(pixels[pos]),
|
||||||
|
G: uint8(pixels[pos+1]),
|
||||||
|
B: uint8(pixels[pos+2]),
|
||||||
|
A: 0xFF,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
|
||||||
|
//export goCreateScreenSize
|
||||||
|
func goCreateScreenSize(index C.int, width C.int, height C.int, mwidth C.int, mheight C.int) {
|
||||||
|
ScreenConfigurations[int(index)] = types.ScreenConfiguration{
|
||||||
|
Width: int(width),
|
||||||
|
Height: int(height),
|
||||||
|
Rates: make(map[int]int16),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//export goSetScreenRates
|
||||||
|
func goSetScreenRates(index C.int, rate_index C.int, rateC C.short) {
|
||||||
|
rate := int16(rateC)
|
||||||
|
|
||||||
|
// filter out all irrelevant rates
|
||||||
|
if rate > 60 || (rate > 30 && rate%10 != 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ScreenConfigurations[int(index)].Rates[int(rate_index)] = rate
|
||||||
|
}
|
43
server/internal/desktop/xorg/xorg.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/XKBlib.h>
|
||||||
|
#include <X11/Xutil.h>
|
||||||
|
#include <X11/extensions/Xrandr.h>
|
||||||
|
#include <X11/extensions/XTest.h>
|
||||||
|
#include <X11/extensions/Xfixes.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
extern void goCreateScreenSize(int index, int width, int height, int mwidth, int mheight);
|
||||||
|
extern void goSetScreenRates(int index, int rate_index, short rate);
|
||||||
|
|
||||||
|
Display *getXDisplay(void);
|
||||||
|
int XDisplayOpen(char *input);
|
||||||
|
void XDisplayClose(void);
|
||||||
|
|
||||||
|
void XMove(int x, int y);
|
||||||
|
void XCursorPosition(int *x, int *y);
|
||||||
|
void XScroll(int x, int y);
|
||||||
|
void XButton(unsigned int button, int down);
|
||||||
|
|
||||||
|
typedef struct xkeyentry_t {
|
||||||
|
KeySym keysym;
|
||||||
|
KeyCode keycode;
|
||||||
|
struct xkeyentry_t *next;
|
||||||
|
} xkeyentry_t;
|
||||||
|
|
||||||
|
static void XKeyEntryAdd(KeySym keysym, KeyCode keycode);
|
||||||
|
static KeyCode XKeyEntryGet(KeySym keysym);
|
||||||
|
static KeyCode XkbKeysymToKeycode(Display *dpy, KeySym keysym);
|
||||||
|
void XKey(KeySym keysym, int down);
|
||||||
|
|
||||||
|
void XGetScreenConfigurations();
|
||||||
|
void XSetScreenConfiguration(int index, short rate);
|
||||||
|
int XGetScreenSize();
|
||||||
|
short XGetScreenRate();
|
||||||
|
|
||||||
|
void XSetKeyboardModifier(int mod, int on);
|
||||||
|
char XGetKeyboardModifiers();
|
||||||
|
XFixesCursorImage *XGetCursorImage(void);
|
||||||
|
|
||||||
|
char *XGetScreenshot(int *w, int *h);
|
@ -1,83 +0,0 @@
|
|||||||
#include "gst.h"
|
|
||||||
|
|
||||||
typedef struct SampleHandlerUserData {
|
|
||||||
int pipelineId;
|
|
||||||
} SampleHandlerUserData;
|
|
||||||
|
|
||||||
void gstreamer_init(void) {
|
|
||||||
gst_init(NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean gstreamer_send_bus_call(GstBus *bus, GstMessage *msg, gpointer data) {
|
|
||||||
switch (GST_MESSAGE_TYPE(msg)) {
|
|
||||||
|
|
||||||
case GST_MESSAGE_EOS:
|
|
||||||
g_print("End of stream\n");
|
|
||||||
exit(1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GST_MESSAGE_ERROR: {
|
|
||||||
gchar *debug;
|
|
||||||
GError *error;
|
|
||||||
|
|
||||||
gst_message_parse_error(msg, &error, &debug);
|
|
||||||
g_free(debug);
|
|
||||||
|
|
||||||
g_printerr("Error: %s\n", error->message);
|
|
||||||
g_error_free(error);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
GstFlowReturn gstreamer_send_new_sample_handler(GstElement *object, gpointer user_data) {
|
|
||||||
GstSample *sample = NULL;
|
|
||||||
GstBuffer *buffer = NULL;
|
|
||||||
gpointer copy = NULL;
|
|
||||||
gsize copy_size = 0;
|
|
||||||
SampleHandlerUserData *s = (SampleHandlerUserData *)user_data;
|
|
||||||
|
|
||||||
g_signal_emit_by_name (object, "pull-sample", &sample);
|
|
||||||
if (sample) {
|
|
||||||
buffer = gst_sample_get_buffer(sample);
|
|
||||||
if (buffer) {
|
|
||||||
gst_buffer_extract_dup(buffer, 0, gst_buffer_get_size(buffer), ©, ©_size);
|
|
||||||
goHandlePipelineBuffer(copy, copy_size, GST_BUFFER_DURATION(buffer), s->pipelineId);
|
|
||||||
}
|
|
||||||
gst_sample_unref (sample);
|
|
||||||
}
|
|
||||||
|
|
||||||
return GST_FLOW_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
GstElement *gstreamer_send_create_pipeline(char *pipeline, GError **error) {
|
|
||||||
return gst_parse_launch(pipeline, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
void gstreamer_send_start_pipeline(GstElement *pipeline, int pipelineId) {
|
|
||||||
SampleHandlerUserData *s = calloc(1, sizeof(SampleHandlerUserData));
|
|
||||||
s->pipelineId = pipelineId;
|
|
||||||
|
|
||||||
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
|
|
||||||
gst_bus_add_watch(bus, gstreamer_send_bus_call, NULL);
|
|
||||||
gst_object_unref(bus);
|
|
||||||
|
|
||||||
GstElement *appsink = gst_bin_get_by_name(GST_BIN(pipeline), "appsink");
|
|
||||||
g_object_set(appsink, "emit-signals", TRUE, NULL);
|
|
||||||
g_signal_connect(appsink, "new-sample", G_CALLBACK(gstreamer_send_new_sample_handler), s);
|
|
||||||
gst_object_unref(appsink);
|
|
||||||
|
|
||||||
gst_element_set_state(pipeline, GST_STATE_PLAYING);
|
|
||||||
}
|
|
||||||
|
|
||||||
void gstreamer_send_play_pipeline(GstElement *pipeline) {
|
|
||||||
gst_element_set_state(pipeline, GST_STATE_PLAYING);
|
|
||||||
}
|
|
||||||
|
|
||||||
void gstreamer_send_stop_pipeline(GstElement *pipeline) {
|
|
||||||
gst_element_set_state(pipeline, GST_STATE_NULL);
|
|
||||||
}
|
|
@ -1,282 +0,0 @@
|
|||||||
package gst
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo pkg-config: gstreamer-1.0 gstreamer-app-1.0
|
|
||||||
|
|
||||||
#include "gst.h"
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"m1k1o/neko/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
apt-get install \
|
|
||||||
libgstreamer1.0-0 \
|
|
||||||
gstreamer1.0-plugins-base \
|
|
||||||
gstreamer1.0-plugins-good \
|
|
||||||
gstreamer1.0-plugins-bad \
|
|
||||||
gstreamer1.0-plugins-ugly\
|
|
||||||
gstreamer1.0-libav \
|
|
||||||
gstreamer1.0-doc \
|
|
||||||
gstreamer1.0-tools \
|
|
||||||
gstreamer1.0-x \
|
|
||||||
gstreamer1.0-alsa \
|
|
||||||
gstreamer1.0-pulseaudio
|
|
||||||
|
|
||||||
gst-inspect-1.0 --version
|
|
||||||
gst-inspect-1.0 plugin
|
|
||||||
gst-launch-1.0 ximagesrc show-pointer=true use-damage=false ! video/x-raw,framerate=30/1 ! videoconvert ! queue ! vp8enc error-resilient=partitions keyframe-max-dist=10 auto-alt-ref=true cpu-used=5 deadline=1 ! autovideosink
|
|
||||||
gst-launch-1.0 pulsesrc ! audioconvert ! opusenc ! autoaudiosink
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Pipeline is a wrapper for a GStreamer Pipeline
|
|
||||||
type Pipeline struct {
|
|
||||||
Pipeline *C.GstElement
|
|
||||||
Sample chan types.Sample
|
|
||||||
Src string
|
|
||||||
id int
|
|
||||||
}
|
|
||||||
|
|
||||||
var pipelines = make(map[int]*Pipeline)
|
|
||||||
var pipelinesLock sync.Mutex
|
|
||||||
var registry *C.GstRegistry
|
|
||||||
|
|
||||||
const (
|
|
||||||
videoSrc = "ximagesrc display-name=%s show-pointer=true use-damage=false ! video/x-raw,framerate=%d/1 ! videoconvert ! queue ! "
|
|
||||||
audioSrc = "pulsesrc device=%s ! audio/x-raw,channels=2 ! audioconvert ! "
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
C.gstreamer_init()
|
|
||||||
registry = C.gst_registry_get()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateRTMPPipeline creates a GStreamer Pipeline
|
|
||||||
func CreateRTMPPipeline(pipelineDevice string, pipelineDisplay string, pipelineSrc string, pipelineRTMP string) (*Pipeline, error) {
|
|
||||||
video := fmt.Sprintf(videoSrc, pipelineDisplay, 25)
|
|
||||||
audio := fmt.Sprintf(audioSrc, pipelineDevice)
|
|
||||||
|
|
||||||
var pipelineStr string
|
|
||||||
if pipelineSrc != "" {
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateAppPipeline creates a GStreamer Pipeline
|
|
||||||
func CreateAppPipeline(codecName string, pipelineDevice string, pipelineSrc string, fps int, bitrate uint, hwenc string) (*Pipeline, error) {
|
|
||||||
pipelineStr := " ! appsink name=appsink"
|
|
||||||
|
|
||||||
// if using custom pipeline
|
|
||||||
if pipelineSrc != "" {
|
|
||||||
pipelineStr = fmt.Sprintf(pipelineSrc+pipelineStr, pipelineDevice)
|
|
||||||
return CreatePipeline(pipelineStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch codecName {
|
|
||||||
case "VP8":
|
|
||||||
if hwenc == "VAAPI" {
|
|
||||||
if err := CheckPlugins([]string{"ximagesrc", "vaapi"}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// vp8 encode is missing from gstreamer.freedesktop.org/documentation
|
|
||||||
// note that it was removed from some recent intel CPUs: https://trac.ffmpeg.org/wiki/Hardware/QuickSync
|
|
||||||
// https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer-vaapi-plugins/html/gstreamer-vaapi-plugins-vaapivp8enc.html
|
|
||||||
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! vaapivp8enc rate-control=vbr bitrate=%d keyframe-period=180"+pipelineStr, pipelineDevice, fps, bitrate)
|
|
||||||
} else {
|
|
||||||
// https://gstreamer.freedesktop.org/documentation/vpx/vp8enc.html?gi-language=c
|
|
||||||
// gstreamer1.0-plugins-good
|
|
||||||
// vp8enc error-resilient=partitions keyframe-max-dist=10 auto-alt-ref=true cpu-used=5 deadline=1
|
|
||||||
if err := CheckPlugins([]string{"ximagesrc", "vpx"}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pipelineStr = strings.Join([]string{
|
|
||||||
fmt.Sprintf(videoSrc, pipelineDevice, fps),
|
|
||||||
"vp8enc",
|
|
||||||
fmt.Sprintf("target-bitrate=%d", bitrate*650),
|
|
||||||
"cpu-used=4",
|
|
||||||
"end-usage=cbr",
|
|
||||||
"threads=4",
|
|
||||||
"deadline=1",
|
|
||||||
"undershoot=95",
|
|
||||||
fmt.Sprintf("buffer-size=%d", bitrate*4),
|
|
||||||
fmt.Sprintf("buffer-initial-size=%d", bitrate*2),
|
|
||||||
fmt.Sprintf("buffer-optimal-size=%d", bitrate*3),
|
|
||||||
"keyframe-max-dist=25",
|
|
||||||
"min-quantizer=4",
|
|
||||||
"max-quantizer=20",
|
|
||||||
pipelineStr,
|
|
||||||
}, " ")
|
|
||||||
}
|
|
||||||
case "VP9":
|
|
||||||
// https://gstreamer.freedesktop.org/documentation/vpx/vp9enc.html?gi-language=c
|
|
||||||
// gstreamer1.0-plugins-good
|
|
||||||
// vp9enc
|
|
||||||
if err := CheckPlugins([]string{"ximagesrc", "vpx"}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pipelineStr = fmt.Sprintf(videoSrc+"vp9enc target-bitrate=%d cpu-used=-5 threads=4 deadline=1 keyframe-max-dist=30 auto-alt-ref=true"+pipelineStr, pipelineDevice, fps, bitrate*1000)
|
|
||||||
case "H264":
|
|
||||||
if err := CheckPlugins([]string{"ximagesrc"}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if hwenc == "VAAPI" {
|
|
||||||
if err := CheckPlugins([]string{"vaapi"}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! vaapih264enc rate-control=vbr bitrate=%d keyframe-period=180 quality-level=7 ! video/x-h264,stream-format=byte-stream"+pipelineStr, pipelineDevice, fps, bitrate)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// https://gstreamer.freedesktop.org/documentation/openh264/openh264enc.html?gi-language=c#openh264enc
|
|
||||||
// gstreamer1.0-plugins-bad
|
|
||||||
// openh264enc multi-thread=4 complexity=high bitrate=3072000 max-bitrate=4096000
|
|
||||||
if err := CheckPlugins([]string{"openh264"}); err == nil {
|
|
||||||
pipelineStr = fmt.Sprintf(videoSrc+"openh264enc multi-thread=4 complexity=high bitrate=%d max-bitrate=%d ! video/x-h264,stream-format=byte-stream"+pipelineStr, pipelineDevice, fps, bitrate*1000, (bitrate+1024)*1000)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c
|
|
||||||
// gstreamer1.0-plugins-ugly
|
|
||||||
// video/x-raw,format=I420 ! x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream
|
|
||||||
if err := CheckPlugins([]string{"x264"}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
vbvbuf := uint(1000)
|
|
||||||
if bitrate > 1000 {
|
|
||||||
vbvbuf = bitrate
|
|
||||||
}
|
|
||||||
|
|
||||||
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! x264enc threads=4 bitrate=%d key-int-max=60 vbv-buf-capacity=%d byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream"+pipelineStr, pipelineDevice, fps, bitrate, vbvbuf)
|
|
||||||
}
|
|
||||||
case "Opus":
|
|
||||||
// https://gstreamer.freedesktop.org/documentation/opus/opusenc.html
|
|
||||||
// gstreamer1.0-plugins-base
|
|
||||||
// opusenc
|
|
||||||
if err := CheckPlugins([]string{"pulseaudio", "opus"}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pipelineStr = fmt.Sprintf(audioSrc+"opusenc inband-fec=true bitrate=%d"+pipelineStr, pipelineDevice, bitrate*1000)
|
|
||||||
case "G722":
|
|
||||||
// https://gstreamer.freedesktop.org/documentation/libav/avenc_g722.html?gi-language=c
|
|
||||||
// gstreamer1.0-libav
|
|
||||||
// avenc_g722
|
|
||||||
if err := CheckPlugins([]string{"pulseaudio", "libav"}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pipelineStr = fmt.Sprintf(audioSrc+"avenc_g722 bitrate=%d"+pipelineStr, pipelineDevice, bitrate*1000)
|
|
||||||
case "PCMU":
|
|
||||||
// https://gstreamer.freedesktop.org/documentation/mulaw/mulawenc.html?gi-language=c
|
|
||||||
// gstreamer1.0-plugins-good
|
|
||||||
// audio/x-raw, rate=8000 ! mulawenc
|
|
||||||
if err := CheckPlugins([]string{"pulseaudio", "mulaw"}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pipelineStr = fmt.Sprintf(audioSrc+"audio/x-raw, rate=8000 ! mulawenc"+pipelineStr, pipelineDevice)
|
|
||||||
case "PCMA":
|
|
||||||
// https://gstreamer.freedesktop.org/documentation/alaw/alawenc.html?gi-language=c
|
|
||||||
// gstreamer1.0-plugins-good
|
|
||||||
// audio/x-raw, rate=8000 ! alawenc
|
|
||||||
if err := CheckPlugins([]string{"pulseaudio", "alaw"}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pipelineStr = fmt.Sprintf(audioSrc+"audio/x-raw, rate=8000 ! alawenc"+pipelineStr, pipelineDevice)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown codec %s", codecName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return CreatePipeline(pipelineStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreatePipeline creates a GStreamer Pipeline
|
|
||||||
func CreatePipeline(pipelineStr string) (*Pipeline, error) {
|
|
||||||
pipelineStrUnsafe := C.CString(pipelineStr)
|
|
||||||
defer C.free(unsafe.Pointer(pipelineStrUnsafe))
|
|
||||||
|
|
||||||
pipelinesLock.Lock()
|
|
||||||
defer pipelinesLock.Unlock()
|
|
||||||
|
|
||||||
var err *C.GError
|
|
||||||
gstPipeline := C.gstreamer_send_create_pipeline(pipelineStrUnsafe, &err)
|
|
||||||
if err != nil {
|
|
||||||
defer C.g_error_free(err)
|
|
||||||
return nil, fmt.Errorf("%s", C.GoString(err.message))
|
|
||||||
}
|
|
||||||
|
|
||||||
p := &Pipeline{
|
|
||||||
Pipeline: gstPipeline,
|
|
||||||
Sample: make(chan types.Sample),
|
|
||||||
Src: pipelineStr,
|
|
||||||
id: len(pipelines),
|
|
||||||
}
|
|
||||||
|
|
||||||
pipelines[p.id] = p
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts the GStreamer Pipeline
|
|
||||||
func (p *Pipeline) Start() {
|
|
||||||
C.gstreamer_send_start_pipeline(p.Pipeline, C.int(p.id))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Play starts the GStreamer Pipeline
|
|
||||||
func (p *Pipeline) Play() {
|
|
||||||
C.gstreamer_send_play_pipeline(p.Pipeline)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops the GStreamer Pipeline
|
|
||||||
func (p *Pipeline) Stop() {
|
|
||||||
C.gstreamer_send_stop_pipeline(p.Pipeline)
|
|
||||||
}
|
|
||||||
|
|
||||||
// gst-inspect-1.0
|
|
||||||
func CheckPlugins(plugins []string) error {
|
|
||||||
var plugin *C.GstPlugin
|
|
||||||
for _, pluginstr := range plugins {
|
|
||||||
plugincstr := C.CString(pluginstr)
|
|
||||||
plugin = C.gst_registry_find_plugin(registry, plugincstr)
|
|
||||||
C.free(unsafe.Pointer(plugincstr))
|
|
||||||
if plugin == nil {
|
|
||||||
return fmt.Errorf("required gstreamer plugin %s not found", pluginstr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goHandlePipelineBuffer
|
|
||||||
func goHandlePipelineBuffer(buffer unsafe.Pointer, bufferLen C.int, duration C.int, pipelineID C.int) {
|
|
||||||
pipelinesLock.Lock()
|
|
||||||
pipeline, ok := pipelines[int(pipelineID)]
|
|
||||||
pipelinesLock.Unlock()
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
pipeline.Sample <- types.Sample{Data: C.GoBytes(buffer, bufferLen), Timestamp: time.Now(), Duration: time.Duration(duration)}
|
|
||||||
} else {
|
|
||||||
fmt.Printf("discarding buffer, no pipeline with id %d", int(pipelineID))
|
|
||||||
}
|
|
||||||
C.free(buffer)
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <gst/gst.h>
|
|
||||||
#include <gst/app/gstappsrc.h>
|
|
||||||
|
|
||||||
extern void goHandlePipelineBuffer(void *buffer, int bufferLen, int samples, int pipelineId);
|
|
||||||
|
|
||||||
GstElement *gstreamer_send_create_pipeline(char *pipeline, GError **error);
|
|
||||||
|
|
||||||
void gstreamer_send_start_pipeline(GstElement *pipeline, int pipelineId);
|
|
||||||
void gstreamer_send_play_pipeline(GstElement *pipeline);
|
|
||||||
void gstreamer_send_stop_pipeline(GstElement *pipeline);
|
|
||||||
void gstreamer_init(void);
|
|
@ -3,16 +3,18 @@ package http
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"image/jpeg"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
"github.com/go-chi/chi/middleware"
|
"github.com/go-chi/chi/middleware"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"m1k1o/neko/internal/config"
|
||||||
"m1k1o/neko/internal/types"
|
"m1k1o/neko/internal/types"
|
||||||
"m1k1o/neko/internal/types/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@ -22,7 +24,7 @@ type Server struct {
|
|||||||
conf *config.Server
|
conf *config.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(conf *config.Server, webSocketHandler types.WebSocketHandler) *Server {
|
func New(conf *config.Server, webSocketHandler types.WebSocketHandler, desktop types.DesktopManager) *Server {
|
||||||
logger := log.With().Str("module", "http").Logger()
|
logger := log.With().Str("module", "http").Logger()
|
||||||
|
|
||||||
router := chi.NewRouter()
|
router := chi.NewRouter()
|
||||||
@ -30,6 +32,12 @@ func New(conf *config.Server, webSocketHandler types.WebSocketHandler) *Server {
|
|||||||
router.Use(middleware.RequestLogger(&logformatter{logger}))
|
router.Use(middleware.RequestLogger(&logformatter{logger}))
|
||||||
router.Use(middleware.Recoverer) // Recover from panics without crashing server
|
router.Use(middleware.Recoverer) // Recover from panics without crashing server
|
||||||
|
|
||||||
|
if conf.PathPrefix != "/" {
|
||||||
|
router.Use(func(h http.Handler) http.Handler {
|
||||||
|
return http.StripPrefix(conf.PathPrefix, h)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
router.Get("/ws", func(w http.ResponseWriter, r *http.Request) {
|
router.Get("/ws", func(w http.ResponseWriter, r *http.Request) {
|
||||||
err := webSocketHandler.Upgrade(w, r)
|
err := webSocketHandler.Upgrade(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -58,6 +66,39 @@ func New(conf *config.Server, webSocketHandler types.WebSocketHandler) *Server {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.Get("/screenshot.jpg", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
password := r.URL.Query().Get("pwd")
|
||||||
|
isAdmin, err := webSocketHandler.IsAdmin(password)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isAdmin {
|
||||||
|
http.Error(w, "bad authorization", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if webSocketHandler.IsLocked("login") {
|
||||||
|
http.Error(w, "room is locked", http.StatusLocked)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
quality, err := strconv.Atoi(r.URL.Query().Get("quality"))
|
||||||
|
if err != nil {
|
||||||
|
quality = 90
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
w.Header().Set("Content-Type", "image/jpeg")
|
||||||
|
|
||||||
|
img := desktop.GetScreenshotImage()
|
||||||
|
if err := jpeg.Encode(w, img, &jpeg.Options{Quality: quality}); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
router.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
router.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||||
_, _ = w.Write([]byte("true"))
|
_, _ = w.Write([]byte("true"))
|
||||||
})
|
})
|
||||||
|
@ -1,261 +0,0 @@
|
|||||||
package remote
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"m1k1o/neko/internal/gst"
|
|
||||||
"m1k1o/neko/internal/types"
|
|
||||||
"m1k1o/neko/internal/types/config"
|
|
||||||
"m1k1o/neko/internal/xorg"
|
|
||||||
|
|
||||||
"github.com/kataras/go-events"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RemoteManager struct {
|
|
||||||
logger zerolog.Logger
|
|
||||||
video *gst.Pipeline
|
|
||||||
audio *gst.Pipeline
|
|
||||||
config *config.Remote
|
|
||||||
broadcast types.BroadcastManager
|
|
||||||
cleanup *time.Ticker
|
|
||||||
shutdown chan bool
|
|
||||||
emmiter events.EventEmmiter
|
|
||||||
streaming bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(config *config.Remote, broadcast types.BroadcastManager) *RemoteManager {
|
|
||||||
return &RemoteManager{
|
|
||||||
logger: log.With().Str("module", "remote").Logger(),
|
|
||||||
cleanup: time.NewTicker(1 * time.Second),
|
|
||||||
shutdown: make(chan bool),
|
|
||||||
emmiter: events.New(),
|
|
||||||
config: config,
|
|
||||||
broadcast: broadcast,
|
|
||||||
streaming: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) VideoCodec() string {
|
|
||||||
return manager.config.VideoCodec
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) AudioCodec() string {
|
|
||||||
return manager.config.AudioCodec
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) Start() {
|
|
||||||
xorg.Display(manager.config.Display)
|
|
||||||
|
|
||||||
if !xorg.ValidScreenSize(manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate) {
|
|
||||||
manager.logger.Warn().Msgf("invalid screen option %dx%d@%d", manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate)
|
|
||||||
} else if err := xorg.ChangeScreenSize(manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate); err != nil {
|
|
||||||
manager.logger.Warn().Err(err).Msg("unable to change screen size")
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.createPipelines()
|
|
||||||
if err := manager.broadcast.Start(); err != nil {
|
|
||||||
manager.logger.Panic().Err(err).Msg("unable to create rtmp pipeline")
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
manager.logger.Info().Msg("shutdown")
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-manager.shutdown:
|
|
||||||
return
|
|
||||||
case sample := <-manager.video.Sample:
|
|
||||||
manager.emmiter.Emit("video", sample)
|
|
||||||
case sample := <-manager.audio.Sample:
|
|
||||||
manager.emmiter.Emit("audio", sample)
|
|
||||||
case <-manager.cleanup.C:
|
|
||||||
xorg.CheckKeys(time.Second * 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) Shutdown() error {
|
|
||||||
manager.logger.Info().Msgf("remote shutting down")
|
|
||||||
manager.video.Stop()
|
|
||||||
manager.audio.Stop()
|
|
||||||
manager.broadcast.Stop()
|
|
||||||
|
|
||||||
manager.cleanup.Stop()
|
|
||||||
manager.shutdown <- true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) OnVideoFrame(listener func(sample types.Sample)) {
|
|
||||||
manager.emmiter.On("video", func(payload ...interface{}) {
|
|
||||||
listener(payload[0].(types.Sample))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) OnAudioFrame(listener func(sample types.Sample)) {
|
|
||||||
manager.emmiter.On("audio", func(payload ...interface{}) {
|
|
||||||
listener(payload[0].(types.Sample))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) StartStream() {
|
|
||||||
manager.createPipelines()
|
|
||||||
|
|
||||||
manager.logger.Info().
|
|
||||||
Str("video_display", manager.config.Display).
|
|
||||||
Str("video_codec", manager.config.VideoCodec).
|
|
||||||
Str("audio_device", manager.config.Device).
|
|
||||||
Str("audio_codec", manager.config.AudioCodec).
|
|
||||||
Str("audio_pipeline_src", manager.audio.Src).
|
|
||||||
Str("video_pipeline_src", manager.video.Src).
|
|
||||||
Str("screen_resolution", fmt.Sprintf("%dx%d@%d", manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate)).
|
|
||||||
Msgf("Pipelines starting...")
|
|
||||||
|
|
||||||
manager.video.Start()
|
|
||||||
manager.audio.Start()
|
|
||||||
manager.streaming = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) StopStream() {
|
|
||||||
manager.logger.Info().Msgf("Pipelines shutting down...")
|
|
||||||
manager.video.Stop()
|
|
||||||
manager.audio.Stop()
|
|
||||||
manager.streaming = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) Streaming() bool {
|
|
||||||
return manager.streaming
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) createPipelines() {
|
|
||||||
// handle maximum fps
|
|
||||||
rate := manager.config.ScreenRate
|
|
||||||
if manager.config.MaxFPS != 0 && manager.config.MaxFPS < manager.config.ScreenRate {
|
|
||||||
rate = manager.config.MaxFPS
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
manager.video, err = gst.CreateAppPipeline(
|
|
||||||
manager.config.VideoCodec,
|
|
||||||
manager.config.Display,
|
|
||||||
manager.config.VideoParams,
|
|
||||||
rate,
|
|
||||||
manager.config.VideoBitrate,
|
|
||||||
manager.config.VideoHWEnc,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
manager.logger.Panic().Err(err).Msg("unable to create video pipeline")
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.audio, err = gst.CreateAppPipeline(
|
|
||||||
manager.config.AudioCodec,
|
|
||||||
manager.config.Device,
|
|
||||||
manager.config.AudioParams,
|
|
||||||
0, // fps: n/a for audio
|
|
||||||
manager.config.AudioBitrate,
|
|
||||||
"", // hwenc: n/a for audio
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
manager.logger.Panic().Err(err).Msg("unable to create audio pipeline")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) ChangeResolution(width int, height int, rate int) error {
|
|
||||||
if !xorg.ValidScreenSize(width, height, rate) {
|
|
||||||
return fmt.Errorf("unknown configuration")
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.video.Stop()
|
|
||||||
manager.broadcast.Stop()
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
manager.video.Start()
|
|
||||||
if err := manager.broadcast.Start(); err != nil {
|
|
||||||
manager.logger.Panic().Err(err).Msg("unable to create rtmp pipeline")
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.logger.Info().Msg("starting video pipeline...")
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := xorg.ChangeScreenSize(width, height, rate); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle maximum fps
|
|
||||||
if manager.config.MaxFPS != 0 && manager.config.MaxFPS < rate {
|
|
||||||
rate = manager.config.MaxFPS
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
manager.video, err = gst.CreateAppPipeline(
|
|
||||||
manager.config.VideoCodec,
|
|
||||||
manager.config.Display,
|
|
||||||
manager.config.VideoParams,
|
|
||||||
rate,
|
|
||||||
manager.config.VideoBitrate,
|
|
||||||
manager.config.VideoHWEnc,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
manager.logger.Panic().Err(err).Msg("unable to create new video pipeline")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) Move(x, y int) {
|
|
||||||
xorg.Move(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) Scroll(x, y int) {
|
|
||||||
xorg.Scroll(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) ButtonDown(code int) error {
|
|
||||||
return xorg.ButtonDown(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) KeyDown(code uint64) error {
|
|
||||||
return xorg.KeyDown(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) ButtonUp(code int) error {
|
|
||||||
return xorg.ButtonUp(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) KeyUp(code uint64) error {
|
|
||||||
return xorg.KeyUp(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) ReadClipboard() string {
|
|
||||||
return xorg.ReadClipboard()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) WriteClipboard(data string) {
|
|
||||||
xorg.WriteClipboard(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) ResetKeys() {
|
|
||||||
xorg.ResetKeys()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) ScreenConfigurations() map[int]types.ScreenConfiguration {
|
|
||||||
return xorg.ScreenConfigurations
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) GetScreenSize() *types.ScreenSize {
|
|
||||||
return xorg.GetScreenSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) SetKeyboardLayout(layout string) {
|
|
||||||
_ = exec.Command("setxkbmap", layout).Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (manager *RemoteManager) SetKeyboardModifiers(NumLock int, CapsLock int, ScrollLock int) {
|
|
||||||
xorg.SetKeyboardModifiers(NumLock, CapsLock, ScrollLock)
|
|
||||||
}
|
|
@ -12,11 +12,11 @@ import (
|
|||||||
"m1k1o/neko/internal/utils"
|
"m1k1o/neko/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(remote types.RemoteManager) *SessionManager {
|
func New(capture types.CaptureManager) *SessionManager {
|
||||||
return &SessionManager{
|
return &SessionManager{
|
||||||
logger: log.With().Str("module", "session").Logger(),
|
logger: log.With().Str("module", "session").Logger(),
|
||||||
host: "",
|
host: "",
|
||||||
remote: remote,
|
capture: capture,
|
||||||
members: make(map[string]*Session),
|
members: make(map[string]*Session),
|
||||||
emmiter: events.New(),
|
emmiter: events.New(),
|
||||||
}
|
}
|
||||||
@ -26,7 +26,7 @@ type SessionManager struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
host string
|
host string
|
||||||
remote types.RemoteManager
|
capture types.CaptureManager
|
||||||
members map[string]*Session
|
members map[string]*Session
|
||||||
emmiter events.EventEmmiter
|
emmiter events.EventEmmiter
|
||||||
// TODO: Handle locks in sessions as flags.
|
// TODO: Handle locks in sessions as flags.
|
||||||
@ -45,9 +45,8 @@ func (manager *SessionManager) New(id string, admin bool, socket types.WebSocket
|
|||||||
|
|
||||||
manager.mu.Lock()
|
manager.mu.Lock()
|
||||||
manager.members[id] = session
|
manager.members[id] = session
|
||||||
if !manager.remote.Streaming() && len(manager.members) > 0 {
|
manager.capture.Audio().AddListener()
|
||||||
manager.remote.StartStream()
|
manager.capture.Video().AddListener()
|
||||||
}
|
|
||||||
manager.mu.Unlock()
|
manager.mu.Unlock()
|
||||||
|
|
||||||
manager.emmiter.Emit("created", id, session)
|
manager.emmiter.Emit("created", id, session)
|
||||||
@ -160,9 +159,8 @@ func (manager *SessionManager) Destroy(id string) {
|
|||||||
err := session.destroy()
|
err := session.destroy()
|
||||||
delete(manager.members, id)
|
delete(manager.members, id)
|
||||||
|
|
||||||
if manager.remote.Streaming() && len(manager.members) <= 0 {
|
manager.capture.Audio().RemoveListener()
|
||||||
manager.remote.StopStream()
|
manager.capture.Video().RemoveListener()
|
||||||
}
|
|
||||||
manager.mu.Unlock()
|
manager.mu.Unlock()
|
||||||
|
|
||||||
manager.emmiter.Emit("destroyed", id, session)
|
manager.emmiter.Emit("destroyed", id, session)
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
type BroadcastManager interface {
|
|
||||||
Shutdown() error
|
|
||||||
Start() error
|
|
||||||
Stop()
|
|
||||||
IsActive() bool
|
|
||||||
Create(url string) error
|
|
||||||
Destroy()
|
|
||||||
GetUrl() string
|
|
||||||
}
|
|
38
server/internal/types/capture.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"m1k1o/neko/internal/types/codec"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrCapturePipelineAlreadyExists = errors.New("capture pipeline already exists")
|
||||||
|
)
|
||||||
|
|
||||||
|
type BroadcastManager interface {
|
||||||
|
Start(url string) error
|
||||||
|
Stop()
|
||||||
|
Started() bool
|
||||||
|
Url() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type StreamSinkManager interface {
|
||||||
|
Codec() codec.RTPCodec
|
||||||
|
OnSample(listener func(sample Sample))
|
||||||
|
|
||||||
|
AddListener() error
|
||||||
|
RemoveListener() error
|
||||||
|
|
||||||
|
ListenersCount() int
|
||||||
|
Started() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type CaptureManager interface {
|
||||||
|
Start()
|
||||||
|
Shutdown() error
|
||||||
|
|
||||||
|
Broadcast() BroadcastManager
|
||||||
|
Audio() StreamSinkManager
|
||||||
|
Video() StreamSinkManager
|
||||||
|
}
|
170
server/internal/types/codec/codecs.go
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
package codec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pion/webrtc/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var RTCPFeedback = []webrtc.RTCPFeedback{
|
||||||
|
{Type: webrtc.TypeRTCPFBTransportCC, Parameter: ""},
|
||||||
|
{Type: webrtc.TypeRTCPFBGoogREMB, Parameter: ""},
|
||||||
|
|
||||||
|
// https://www.iana.org/assignments/sdp-parameters/sdp-parameters.xhtml#sdp-parameters-19
|
||||||
|
{Type: webrtc.TypeRTCPFBCCM, Parameter: "fir"},
|
||||||
|
|
||||||
|
// https://www.iana.org/assignments/sdp-parameters/sdp-parameters.xhtml#sdp-parameters-15
|
||||||
|
{Type: webrtc.TypeRTCPFBNACK, Parameter: "pli"},
|
||||||
|
{Type: webrtc.TypeRTCPFBNACK, Parameter: ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseRTC(codec webrtc.RTPCodecParameters) (RTPCodec, bool) {
|
||||||
|
codecName := strings.Split(codec.RTPCodecCapability.MimeType, "/")[1]
|
||||||
|
return ParseStr(codecName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseStr(codecName string) (codec RTPCodec, ok bool) {
|
||||||
|
ok = true
|
||||||
|
|
||||||
|
switch strings.ToLower(codecName) {
|
||||||
|
case VP8().Name:
|
||||||
|
codec = VP8()
|
||||||
|
case VP9().Name:
|
||||||
|
codec = VP9()
|
||||||
|
case H264().Name:
|
||||||
|
codec = H264()
|
||||||
|
case Opus().Name:
|
||||||
|
codec = Opus()
|
||||||
|
case G722().Name:
|
||||||
|
codec = G722()
|
||||||
|
case PCMU().Name:
|
||||||
|
codec = PCMU()
|
||||||
|
case PCMA().Name:
|
||||||
|
codec = PCMA()
|
||||||
|
default:
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type RTPCodec struct {
|
||||||
|
Name string
|
||||||
|
PayloadType webrtc.PayloadType
|
||||||
|
Type webrtc.RTPCodecType
|
||||||
|
Capability webrtc.RTPCodecCapability
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec RTPCodec) Register(engine *webrtc.MediaEngine) error {
|
||||||
|
return engine.RegisterCodec(webrtc.RTPCodecParameters{
|
||||||
|
RTPCodecCapability: codec.Capability,
|
||||||
|
PayloadType: codec.PayloadType,
|
||||||
|
}, codec.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func VP8() RTPCodec {
|
||||||
|
return RTPCodec{
|
||||||
|
Name: "vp8",
|
||||||
|
PayloadType: 96,
|
||||||
|
Type: webrtc.RTPCodecTypeVideo,
|
||||||
|
Capability: webrtc.RTPCodecCapability{
|
||||||
|
MimeType: webrtc.MimeTypeVP8,
|
||||||
|
ClockRate: 90000,
|
||||||
|
Channels: 0,
|
||||||
|
SDPFmtpLine: "",
|
||||||
|
RTCPFeedback: RTCPFeedback,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Profile ID.
|
||||||
|
func VP9() RTPCodec {
|
||||||
|
return RTPCodec{
|
||||||
|
Name: "vp9",
|
||||||
|
PayloadType: 98,
|
||||||
|
Type: webrtc.RTPCodecTypeVideo,
|
||||||
|
Capability: webrtc.RTPCodecCapability{
|
||||||
|
MimeType: webrtc.MimeTypeVP9,
|
||||||
|
ClockRate: 90000,
|
||||||
|
Channels: 0,
|
||||||
|
SDPFmtpLine: "profile-id=0",
|
||||||
|
RTCPFeedback: RTCPFeedback,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Profile ID.
|
||||||
|
func H264() RTPCodec {
|
||||||
|
return RTPCodec{
|
||||||
|
Name: "h264",
|
||||||
|
PayloadType: 102,
|
||||||
|
Type: webrtc.RTPCodecTypeVideo,
|
||||||
|
Capability: webrtc.RTPCodecCapability{
|
||||||
|
MimeType: webrtc.MimeTypeH264,
|
||||||
|
ClockRate: 90000,
|
||||||
|
Channels: 0,
|
||||||
|
SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f",
|
||||||
|
RTCPFeedback: RTCPFeedback,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Opus() RTPCodec {
|
||||||
|
return RTPCodec{
|
||||||
|
Name: "opus",
|
||||||
|
PayloadType: 111,
|
||||||
|
Type: webrtc.RTPCodecTypeAudio,
|
||||||
|
Capability: webrtc.RTPCodecCapability{
|
||||||
|
MimeType: webrtc.MimeTypeOpus,
|
||||||
|
ClockRate: 48000,
|
||||||
|
Channels: 2,
|
||||||
|
SDPFmtpLine: "useinbandfec=1",
|
||||||
|
RTCPFeedback: []webrtc.RTCPFeedback{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func G722() RTPCodec {
|
||||||
|
return RTPCodec{
|
||||||
|
Name: "g722",
|
||||||
|
PayloadType: 9,
|
||||||
|
Type: webrtc.RTPCodecTypeAudio,
|
||||||
|
Capability: webrtc.RTPCodecCapability{
|
||||||
|
MimeType: webrtc.MimeTypeG722,
|
||||||
|
ClockRate: 8000,
|
||||||
|
Channels: 0,
|
||||||
|
SDPFmtpLine: "",
|
||||||
|
RTCPFeedback: []webrtc.RTCPFeedback{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PCMU() RTPCodec {
|
||||||
|
return RTPCodec{
|
||||||
|
Name: "pcmu",
|
||||||
|
PayloadType: 0,
|
||||||
|
Type: webrtc.RTPCodecTypeAudio,
|
||||||
|
Capability: webrtc.RTPCodecCapability{
|
||||||
|
MimeType: webrtc.MimeTypePCMU,
|
||||||
|
ClockRate: 8000,
|
||||||
|
Channels: 0,
|
||||||
|
SDPFmtpLine: "",
|
||||||
|
RTCPFeedback: []webrtc.RTCPFeedback{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PCMA() RTPCodec {
|
||||||
|
return RTPCodec{
|
||||||
|
Name: "pcma",
|
||||||
|
PayloadType: 8,
|
||||||
|
Type: webrtc.RTPCodecTypeAudio,
|
||||||
|
Capability: webrtc.RTPCodecCapability{
|
||||||
|
MimeType: webrtc.MimeTypePCMA,
|
||||||
|
ClockRate: 8000,
|
||||||
|
Channels: 0,
|
||||||
|
SDPFmtpLine: "",
|
||||||
|
RTCPFeedback: []webrtc.RTCPFeedback{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -1,32 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Broadcast struct {
|
|
||||||
Pipeline string
|
|
||||||
URL string
|
|
||||||
Enabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Broadcast) Init(cmd *cobra.Command) error {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.PersistentFlags().String("broadcast_url", "", "URL for broadcasting, setting this value will automatically enable broadcasting")
|
|
||||||
if err := viper.BindPFlag("broadcast_url", cmd.PersistentFlags().Lookup("broadcast_url")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Broadcast) Set() {
|
|
||||||
s.Pipeline = viper.GetString("broadcast_pipeline")
|
|
||||||
s.URL = viper.GetString("broadcast_url")
|
|
||||||
s.Enabled = s.URL != ""
|
|
||||||
}
|
|
@ -1,169 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Remote struct {
|
|
||||||
Display string
|
|
||||||
Device string
|
|
||||||
AudioCodec string
|
|
||||||
AudioParams string
|
|
||||||
AudioBitrate uint
|
|
||||||
VideoHWEnc string
|
|
||||||
VideoCodec string
|
|
||||||
VideoParams string
|
|
||||||
VideoBitrate uint
|
|
||||||
ScreenWidth int
|
|
||||||
ScreenHeight int
|
|
||||||
ScreenRate int
|
|
||||||
MaxFPS int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Remote) Init(cmd *cobra.Command) error {
|
|
||||||
cmd.PersistentFlags().String("display", ":99.0", "XDisplay to capture")
|
|
||||||
if err := viper.BindPFlag("display", cmd.PersistentFlags().Lookup("display")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.PersistentFlags().String("device", "auto_null.monitor", "audio device to capture")
|
|
||||||
if err := viper.BindPFlag("device", cmd.PersistentFlags().Lookup("device")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.PersistentFlags().String("audio", "", "audio codec parameters to use for streaming")
|
|
||||||
if err := viper.BindPFlag("audio", cmd.PersistentFlags().Lookup("audio")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.PersistentFlags().Int("audio_bitrate", 128, "audio bitrate in kbit/s")
|
|
||||||
if err := viper.BindPFlag("audio_bitrate", cmd.PersistentFlags().Lookup("audio_bitrate")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.PersistentFlags().String("video", "", "video codec parameters to use for streaming")
|
|
||||||
if err := viper.BindPFlag("video", cmd.PersistentFlags().Lookup("video")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.PersistentFlags().Int("video_bitrate", 3072, "video bitrate in kbit/s")
|
|
||||||
if err := viper.BindPFlag("video_bitrate", cmd.PersistentFlags().Lookup("video_bitrate")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.PersistentFlags().String("screen", "1280x720@30", "default screen resolution and framerate")
|
|
||||||
if err := viper.BindPFlag("screen", cmd.PersistentFlags().Lookup("screen")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.PersistentFlags().Int("max_fps", 25, "maximum fps delivered via WebRTC, 0 is for no maximum")
|
|
||||||
if err := viper.BindPFlag("max_fps", cmd.PersistentFlags().Lookup("max_fps")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// hw encoding
|
|
||||||
cmd.PersistentFlags().String("hwenc", "", "use hardware accelerated encoding")
|
|
||||||
if err := viper.BindPFlag("hwenc", cmd.PersistentFlags().Lookup("hwenc")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// video codecs
|
|
||||||
cmd.PersistentFlags().Bool("vp8", false, "use VP8 video codec")
|
|
||||||
if err := viper.BindPFlag("vp8", cmd.PersistentFlags().Lookup("vp8")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.PersistentFlags().Bool("vp9", false, "use VP9 video codec")
|
|
||||||
if err := viper.BindPFlag("vp9", cmd.PersistentFlags().Lookup("vp9")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.PersistentFlags().Bool("h264", false, "use H264 video codec")
|
|
||||||
if err := viper.BindPFlag("h264", cmd.PersistentFlags().Lookup("h264")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// audio codecs
|
|
||||||
cmd.PersistentFlags().Bool("opus", false, "use Opus audio codec")
|
|
||||||
if err := viper.BindPFlag("opus", cmd.PersistentFlags().Lookup("opus")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.PersistentFlags().Bool("g722", false, "use G722 audio codec")
|
|
||||||
if err := viper.BindPFlag("g722", cmd.PersistentFlags().Lookup("g722")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.PersistentFlags().Bool("pcmu", false, "use PCMU audio codec")
|
|
||||||
if err := viper.BindPFlag("pcmu", cmd.PersistentFlags().Lookup("pcmu")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.PersistentFlags().Bool("pcma", false, "use PCMA audio codec")
|
|
||||||
if err := viper.BindPFlag("pcma", cmd.PersistentFlags().Lookup("pcma")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Remote) Set() {
|
|
||||||
audioCodec := "Opus"
|
|
||||||
if viper.GetBool("opus") {
|
|
||||||
audioCodec = "Opus"
|
|
||||||
} else if viper.GetBool("g722") {
|
|
||||||
audioCodec = "G722"
|
|
||||||
} else if viper.GetBool("pcmu") {
|
|
||||||
audioCodec = "PCMU"
|
|
||||||
} else if viper.GetBool("pcma") {
|
|
||||||
audioCodec = "PCMA"
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Device = viper.GetString("device")
|
|
||||||
s.AudioCodec = audioCodec
|
|
||||||
s.AudioParams = viper.GetString("audio")
|
|
||||||
s.AudioBitrate = viper.GetUint("audio_bitrate")
|
|
||||||
|
|
||||||
videoCodec := "VP8"
|
|
||||||
if viper.GetBool("vp8") {
|
|
||||||
videoCodec = "VP8"
|
|
||||||
} else if viper.GetBool("vp9") {
|
|
||||||
videoCodec = "VP9"
|
|
||||||
} else if viper.GetBool("h264") {
|
|
||||||
videoCodec = "H264"
|
|
||||||
}
|
|
||||||
videoHWEnc := ""
|
|
||||||
if viper.GetString("hwenc") == "VAAPI" {
|
|
||||||
videoHWEnc = "VAAPI"
|
|
||||||
}
|
|
||||||
s.Display = viper.GetString("display")
|
|
||||||
s.VideoHWEnc = videoHWEnc
|
|
||||||
s.VideoCodec = videoCodec
|
|
||||||
s.VideoParams = viper.GetString("video")
|
|
||||||
s.VideoBitrate = viper.GetUint("video_bitrate")
|
|
||||||
|
|
||||||
s.ScreenWidth = 1280
|
|
||||||
s.ScreenHeight = 720
|
|
||||||
s.ScreenRate = 30
|
|
||||||
|
|
||||||
r := regexp.MustCompile(`([0-9]{1,4})x([0-9]{1,4})@([0-9]{1,3})`)
|
|
||||||
res := r.FindStringSubmatch(viper.GetString("screen"))
|
|
||||||
|
|
||||||
if len(res) > 0 {
|
|
||||||
width, err1 := strconv.ParseInt(res[1], 10, 64)
|
|
||||||
height, err2 := strconv.ParseInt(res[2], 10, 64)
|
|
||||||
rate, err3 := strconv.ParseInt(res[3], 10, 64)
|
|
||||||
|
|
||||||
if err1 == nil && err2 == nil && err3 == nil {
|
|
||||||
s.ScreenWidth = int(width)
|
|
||||||
s.ScreenHeight = int(height)
|
|
||||||
s.ScreenRate = int(rate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.MaxFPS = viper.GetInt("max_fps")
|
|
||||||
}
|
|
73
server/internal/types/desktop.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "image"
|
||||||
|
|
||||||
|
type CursorImage struct {
|
||||||
|
Width uint16
|
||||||
|
Height uint16
|
||||||
|
Xhot uint16
|
||||||
|
Yhot uint16
|
||||||
|
Serial uint64
|
||||||
|
Image *image.RGBA
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScreenSize struct {
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
Rate int16 `json:"rate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScreenConfiguration struct {
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
Rates map[int]int16 `json:"rates"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyboardModifiers struct {
|
||||||
|
NumLock *bool
|
||||||
|
CapsLock *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyboardMap struct {
|
||||||
|
Layout string
|
||||||
|
Variant string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DesktopManager interface {
|
||||||
|
Start()
|
||||||
|
Shutdown() error
|
||||||
|
OnBeforeScreenSizeChange(listener func())
|
||||||
|
OnAfterScreenSizeChange(listener func())
|
||||||
|
|
||||||
|
// clipboard
|
||||||
|
ReadClipboard() string
|
||||||
|
WriteClipboard(data string)
|
||||||
|
|
||||||
|
// xorg
|
||||||
|
Move(x, y int)
|
||||||
|
GetCursorPosition() (int, int)
|
||||||
|
Scroll(x, y int)
|
||||||
|
ButtonDown(code uint32) error
|
||||||
|
KeyDown(code uint32) error
|
||||||
|
ButtonUp(code uint32) error
|
||||||
|
KeyUp(code uint32) error
|
||||||
|
ButtonPress(code uint32) error
|
||||||
|
KeyPress(codes ...uint32) error
|
||||||
|
ResetKeys()
|
||||||
|
ScreenConfigurations() map[int]ScreenConfiguration
|
||||||
|
SetScreenSize(ScreenSize) error
|
||||||
|
GetScreenSize() *ScreenSize
|
||||||
|
SetKeyboardMap(KeyboardMap) error
|
||||||
|
GetKeyboardMap() (*KeyboardMap, error)
|
||||||
|
SetKeyboardModifiers(mod KeyboardModifiers)
|
||||||
|
GetKeyboardModifiers() KeyboardModifiers
|
||||||
|
GetCursorImage() *CursorImage
|
||||||
|
GetScreenshotImage() *image.RGBA
|
||||||
|
|
||||||
|
// xevent
|
||||||
|
OnCursorChanged(listener func(serial uint64))
|
||||||
|
OnClipboardUpdated(listener func())
|
||||||
|
OnFileChooserDialogOpened(listener func())
|
||||||
|
OnFileChooserDialogClosed(listener func())
|
||||||
|
OnEventError(listener func(error_code uint8, message string, request_code uint8, minor_code uint8))
|
||||||
|
}
|
@ -70,7 +70,7 @@ type Keyboard struct {
|
|||||||
Layout *string `json:"layout,omitempty"`
|
Layout *string `json:"layout,omitempty"`
|
||||||
CapsLock *bool `json:"capsLock,omitempty"`
|
CapsLock *bool `json:"capsLock,omitempty"`
|
||||||
NumLock *bool `json:"numLock,omitempty"`
|
NumLock *bool `json:"numLock,omitempty"`
|
||||||
ScrollLock *bool `json:"scrollLock,omitempty"`
|
ScrollLock *bool `json:"scrollLock,omitempty"` // TODO: ScrollLock is deprecated.
|
||||||
}
|
}
|
||||||
|
|
||||||
type Control struct {
|
type Control struct {
|
||||||
@ -128,7 +128,7 @@ type ScreenResolution struct {
|
|||||||
ID string `json:"id,omitempty"`
|
ID string `json:"id,omitempty"`
|
||||||
Width int `json:"width"`
|
Width int `json:"width"`
|
||||||
Height int `json:"height"`
|
Height int `json:"height"`
|
||||||
Rate int `json:"rate"`
|
Rate int16 `json:"rate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScreenConfigurations struct {
|
type ScreenConfigurations struct {
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
type RemoteManager interface {
|
|
||||||
VideoCodec() string
|
|
||||||
AudioCodec() string
|
|
||||||
Start()
|
|
||||||
Shutdown() error
|
|
||||||
OnVideoFrame(listener func(sample Sample))
|
|
||||||
OnAudioFrame(listener func(sample Sample))
|
|
||||||
StartStream()
|
|
||||||
StopStream()
|
|
||||||
Streaming() bool
|
|
||||||
ChangeResolution(width int, height int, rate int) error
|
|
||||||
GetScreenSize() *ScreenSize
|
|
||||||
ScreenConfigurations() map[int]ScreenConfiguration
|
|
||||||
Move(x, y int)
|
|
||||||
Scroll(x, y int)
|
|
||||||
ButtonDown(code int) error
|
|
||||||
KeyDown(code uint64) error
|
|
||||||
ButtonUp(code int) error
|
|
||||||
KeyUp(code uint64) error
|
|
||||||
ReadClipboard() string
|
|
||||||
WriteClipboard(data string)
|
|
||||||
ResetKeys()
|
|
||||||
SetKeyboardLayout(layout string)
|
|
||||||
SetKeyboardModifiers(NumLock int, CapsLock int, ScrollLock int)
|
|
||||||
}
|
|
@ -32,5 +32,6 @@ type WebSocketHandler interface {
|
|||||||
Shutdown() error
|
Shutdown() error
|
||||||
Upgrade(w http.ResponseWriter, r *http.Request) error
|
Upgrade(w http.ResponseWriter, r *http.Request) error
|
||||||
Stats() Stats
|
Stats() Stats
|
||||||
|
IsLocked(resource string) bool
|
||||||
IsAdmin(password string) (bool, error)
|
IsAdmin(password string) (bool, error)
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
type ScreenSize struct {
|
|
||||||
Width int `json:"width"`
|
|
||||||
Height int `json:"height"`
|
|
||||||
Rate int16 `json:"rate"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScreenConfiguration struct {
|
|
||||||
Width int `json:"width"`
|
|
||||||
Height int `json:"height"`
|
|
||||||
Rates map[int]int16 `json:"rates"`
|
|
||||||
}
|
|
@ -35,7 +35,7 @@ type PayloadScroll struct {
|
|||||||
|
|
||||||
type PayloadKey struct {
|
type PayloadKey struct {
|
||||||
PayloadHeader
|
PayloadHeader
|
||||||
Key uint64
|
Key uint64 // TODO: uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) error {
|
func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) error {
|
||||||
@ -64,7 +64,7 @@ func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.remote.Move(int(payload.X), int(payload.Y))
|
manager.desktop.Move(int(payload.X), int(payload.Y))
|
||||||
case OP_SCROLL:
|
case OP_SCROLL:
|
||||||
payload := &PayloadScroll{}
|
payload := &PayloadScroll{}
|
||||||
if err := binary.Read(buffer, binary.LittleEndian, payload); err != nil {
|
if err := binary.Read(buffer, binary.LittleEndian, payload); err != nil {
|
||||||
@ -77,7 +77,7 @@ func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) e
|
|||||||
Str("y", strconv.Itoa(int(payload.Y))).
|
Str("y", strconv.Itoa(int(payload.Y))).
|
||||||
Msg("scroll")
|
Msg("scroll")
|
||||||
|
|
||||||
manager.remote.Scroll(int(payload.X), int(payload.Y))
|
manager.desktop.Scroll(int(payload.X), int(payload.Y))
|
||||||
case OP_KEY_DOWN:
|
case OP_KEY_DOWN:
|
||||||
payload := &PayloadKey{}
|
payload := &PayloadKey{}
|
||||||
if err := binary.Read(buffer, binary.LittleEndian, payload); err != nil {
|
if err := binary.Read(buffer, binary.LittleEndian, payload); err != nil {
|
||||||
@ -85,7 +85,7 @@ func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
if payload.Key < 8 {
|
if payload.Key < 8 {
|
||||||
err := manager.remote.ButtonDown(int(payload.Key))
|
err := manager.desktop.ButtonDown(uint32(payload.Key))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
manager.logger.Warn().Err(err).Msg("button down failed")
|
manager.logger.Warn().Err(err).Msg("button down failed")
|
||||||
return nil
|
return nil
|
||||||
@ -93,7 +93,7 @@ func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) e
|
|||||||
|
|
||||||
manager.logger.Debug().Msgf("button down %d", payload.Key)
|
manager.logger.Debug().Msgf("button down %d", payload.Key)
|
||||||
} else {
|
} else {
|
||||||
err := manager.remote.KeyDown(uint64(payload.Key))
|
err := manager.desktop.KeyDown(uint32(payload.Key))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
manager.logger.Warn().Err(err).Msg("key down failed")
|
manager.logger.Warn().Err(err).Msg("key down failed")
|
||||||
return nil
|
return nil
|
||||||
@ -109,7 +109,7 @@ func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
if payload.Key < 8 {
|
if payload.Key < 8 {
|
||||||
err := manager.remote.ButtonUp(int(payload.Key))
|
err := manager.desktop.ButtonUp(uint32(payload.Key))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
manager.logger.Warn().Err(err).Msg("button up failed")
|
manager.logger.Warn().Err(err).Msg("button up failed")
|
||||||
return nil
|
return nil
|
||||||
@ -117,7 +117,7 @@ func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) e
|
|||||||
|
|
||||||
manager.logger.Debug().Msgf("button up %d", payload.Key)
|
manager.logger.Debug().Msgf("button up %d", payload.Key)
|
||||||
} else {
|
} else {
|
||||||
err := manager.remote.KeyUp(uint64(payload.Key))
|
err := manager.desktop.KeyUp(uint32(payload.Key))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
manager.logger.Warn().Err(err).Msg("key up failed")
|
manager.logger.Warn().Err(err).Msg("key up failed")
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
package webrtc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pion/logging"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
)
|
|
||||||
|
|
||||||
type nulllog struct{}
|
|
||||||
|
|
||||||
func (l nulllog) Trace(msg string) {}
|
|
||||||
func (l nulllog) Tracef(format string, args ...interface{}) {}
|
|
||||||
func (l nulllog) Debug(msg string) {}
|
|
||||||
func (l nulllog) Debugf(format string, args ...interface{}) {}
|
|
||||||
func (l nulllog) Info(msg string) {}
|
|
||||||
func (l nulllog) Infof(format string, args ...interface{}) {}
|
|
||||||
func (l nulllog) Warn(msg string) {}
|
|
||||||
func (l nulllog) Warnf(format string, args ...interface{}) {}
|
|
||||||
func (l nulllog) Error(msg string) {}
|
|
||||||
func (l nulllog) Errorf(format string, args ...interface{}) {}
|
|
||||||
|
|
||||||
type logger struct {
|
|
||||||
logger zerolog.Logger
|
|
||||||
subsystem string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l logger) Trace(msg string) { l.logger.Trace().Msg(msg) }
|
|
||||||
func (l logger) Tracef(format string, args ...interface{}) { l.logger.Trace().Msgf(format, args...) }
|
|
||||||
func (l logger) Debug(msg string) { l.logger.Debug().Msg(msg) }
|
|
||||||
func (l logger) Debugf(format string, args ...interface{}) { l.logger.Debug().Msgf(format, args...) }
|
|
||||||
func (l logger) Info(msg string) {
|
|
||||||
if strings.Contains(msg, "packetio.Buffer is full") {
|
|
||||||
//l.logger.Panic().Msg(msg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l.logger.Info().Msg(msg)
|
|
||||||
}
|
|
||||||
func (l logger) Infof(format string, args ...interface{}) {
|
|
||||||
msg := fmt.Sprintf(format, args...)
|
|
||||||
if strings.Contains(msg, "packetio.Buffer is full") {
|
|
||||||
// l.logger.Panic().Msg(msg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l.logger.Info().Msg(msg)
|
|
||||||
}
|
|
||||||
func (l logger) Warn(msg string) { l.logger.Warn().Msg(msg) }
|
|
||||||
func (l logger) Warnf(format string, args ...interface{}) { l.logger.Warn().Msgf(format, args...) }
|
|
||||||
func (l logger) Error(msg string) { l.logger.Error().Msg(msg) }
|
|
||||||
func (l logger) Errorf(format string, args ...interface{}) { l.logger.Error().Msgf(format, args...) }
|
|
||||||
|
|
||||||
type loggerFactory struct {
|
|
||||||
logger zerolog.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l loggerFactory) NewLogger(subsystem string) logging.LeveledLogger {
|
|
||||||
if subsystem == "sctp" {
|
|
||||||
return nulllog{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return logger{
|
|
||||||
subsystem: subsystem,
|
|
||||||
logger: l.logger.With().Str("subsystem", subsystem).Logger(),
|
|
||||||
}
|
|
||||||
}
|
|
27
server/internal/webrtc/pionlog/factory.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package pionlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pion/logging"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(logger zerolog.Logger) Factory {
|
||||||
|
return Factory{
|
||||||
|
Logger: logger.With().Str("submodule", "pion").Logger(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Factory struct {
|
||||||
|
Logger zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Factory) NewLogger(subsystem string) logging.LeveledLogger {
|
||||||
|
if subsystem == "sctp" {
|
||||||
|
return nulllog{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return logger{
|
||||||
|
subsystem: subsystem,
|
||||||
|
logger: l.Logger.With().Str("subsystem", subsystem).Logger(),
|
||||||
|
}
|
||||||
|
}
|
66
server/internal/webrtc/pionlog/logger.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package pionlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type logger struct {
|
||||||
|
logger zerolog.Logger
|
||||||
|
subsystem string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Trace(msg string) {
|
||||||
|
l.logger.Trace().Msg(strings.TrimSpace(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Tracef(format string, args ...any) {
|
||||||
|
msg := fmt.Sprintf(format, args...)
|
||||||
|
l.logger.Trace().Msg(strings.TrimSpace(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Debug(msg string) {
|
||||||
|
l.logger.Debug().Msg(strings.TrimSpace(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Debugf(format string, args ...any) {
|
||||||
|
msg := fmt.Sprintf(format, args...)
|
||||||
|
l.logger.Debug().Msg(strings.TrimSpace(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Info(msg string) {
|
||||||
|
if strings.Contains(msg, "duplicated packet") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.logger.Info().Msg(strings.TrimSpace(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Infof(format string, args ...any) {
|
||||||
|
msg := fmt.Sprintf(format, args...)
|
||||||
|
if strings.Contains(msg, "duplicated packet") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.logger.Info().Msg(strings.TrimSpace(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Warn(msg string) {
|
||||||
|
l.logger.Warn().Msg(strings.TrimSpace(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Warnf(format string, args ...any) {
|
||||||
|
msg := fmt.Sprintf(format, args...)
|
||||||
|
l.logger.Warn().Msg(strings.TrimSpace(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Error(msg string) {
|
||||||
|
l.logger.Error().Msg(strings.TrimSpace(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Errorf(format string, args ...any) {
|
||||||
|
msg := fmt.Sprintf(format, args...)
|
||||||
|
l.logger.Error().Msg(strings.TrimSpace(msg))
|
||||||
|
}
|
14
server/internal/webrtc/pionlog/nullog.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package pionlog
|
||||||
|
|
||||||
|
type nulllog struct{}
|
||||||
|
|
||||||
|
func (l nulllog) Trace(msg string) {}
|
||||||
|
func (l nulllog) Tracef(format string, args ...any) {}
|
||||||
|
func (l nulllog) Debug(msg string) {}
|
||||||
|
func (l nulllog) Debugf(format string, args ...any) {}
|
||||||
|
func (l nulllog) Info(msg string) {}
|
||||||
|
func (l nulllog) Infof(format string, args ...any) {}
|
||||||
|
func (l nulllog) Warn(msg string) {}
|
||||||
|
func (l nulllog) Warnf(format string, args ...any) {}
|
||||||
|
func (l nulllog) Error(msg string) {}
|
||||||
|
func (l nulllog) Errorf(format string, args ...any) {}
|
@ -2,6 +2,7 @@ package webrtc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@ -14,14 +15,16 @@ import (
|
|||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"m1k1o/neko/internal/config"
|
||||||
"m1k1o/neko/internal/types"
|
"m1k1o/neko/internal/types"
|
||||||
"m1k1o/neko/internal/types/config"
|
"m1k1o/neko/internal/webrtc/pionlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(sessions types.SessionManager, remote types.RemoteManager, config *config.WebRTC) *WebRTCManager {
|
func New(sessions types.SessionManager, capture types.CaptureManager, desktop types.DesktopManager, config *config.WebRTC) *WebRTCManager {
|
||||||
return &WebRTCManager{
|
return &WebRTCManager{
|
||||||
logger: log.With().Str("module", "webrtc").Logger(),
|
logger: log.With().Str("module", "webrtc").Logger(),
|
||||||
remote: remote,
|
capture: capture,
|
||||||
|
desktop: desktop,
|
||||||
sessions: sessions,
|
sessions: sessions,
|
||||||
config: config,
|
config: config,
|
||||||
}
|
}
|
||||||
@ -31,38 +34,54 @@ type WebRTCManager struct {
|
|||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
videoTrack *webrtc.TrackLocalStaticSample
|
videoTrack *webrtc.TrackLocalStaticSample
|
||||||
audioTrack *webrtc.TrackLocalStaticSample
|
audioTrack *webrtc.TrackLocalStaticSample
|
||||||
videoCodec webrtc.RTPCodecParameters
|
|
||||||
audioCodec webrtc.RTPCodecParameters
|
|
||||||
sessions types.SessionManager
|
sessions types.SessionManager
|
||||||
remote types.RemoteManager
|
capture types.CaptureManager
|
||||||
|
desktop types.DesktopManager
|
||||||
config *config.WebRTC
|
config *config.WebRTC
|
||||||
api *webrtc.API
|
api *webrtc.API
|
||||||
}
|
}
|
||||||
|
|
||||||
func (manager *WebRTCManager) Start() {
|
func (manager *WebRTCManager) Start() {
|
||||||
var err error
|
var err error
|
||||||
manager.audioTrack, manager.audioCodec, err = manager.createTrack(manager.remote.AudioCodec())
|
|
||||||
|
//
|
||||||
|
// audio
|
||||||
|
//
|
||||||
|
|
||||||
|
audioCodec := manager.capture.Audio().Codec()
|
||||||
|
manager.audioTrack, err = webrtc.NewTrackLocalStaticSample(audioCodec.Capability, "audio", "stream")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
manager.logger.Panic().Err(err).Msg("unable to create audio track")
|
manager.logger.Panic().Err(err).Msg("unable to create audio track")
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.remote.OnAudioFrame(func(sample types.Sample) {
|
manager.capture.Audio().OnSample(func(sample types.Sample) {
|
||||||
if err := manager.audioTrack.WriteSample(media.Sample(sample)); err != nil && err != io.ErrClosedPipe {
|
err := manager.audioTrack.WriteSample(media.Sample(sample))
|
||||||
|
if err != nil && errors.Is(err, io.ErrClosedPipe) {
|
||||||
manager.logger.Warn().Err(err).Msg("audio pipeline failed to write")
|
manager.logger.Warn().Err(err).Msg("audio pipeline failed to write")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
manager.videoTrack, manager.videoCodec, err = manager.createTrack(manager.remote.VideoCodec())
|
//
|
||||||
|
// video
|
||||||
|
//
|
||||||
|
|
||||||
|
videoCodec := manager.capture.Video().Codec()
|
||||||
|
manager.videoTrack, err = webrtc.NewTrackLocalStaticSample(videoCodec.Capability, "video", "stream")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
manager.logger.Panic().Err(err).Msg("unable to create video track")
|
manager.logger.Panic().Err(err).Msg("unable to create video track")
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.remote.OnVideoFrame(func(sample types.Sample) {
|
manager.capture.Video().OnSample(func(sample types.Sample) {
|
||||||
if err := manager.videoTrack.WriteSample(media.Sample(sample)); err != nil && err != io.ErrClosedPipe {
|
err := manager.videoTrack.WriteSample(media.Sample(sample))
|
||||||
|
if err != nil && errors.Is(err, io.ErrClosedPipe) {
|
||||||
manager.logger.Warn().Err(err).Msg("video pipeline failed to write")
|
manager.logger.Warn().Err(err).Msg("video pipeline failed to write")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
//
|
||||||
|
// api
|
||||||
|
//
|
||||||
|
|
||||||
if err := manager.initAPI(); err != nil {
|
if err := manager.initAPI(); err != nil {
|
||||||
manager.logger.Panic().Err(err).Msg("failed to initialize webrtc API")
|
manager.logger.Panic().Err(err).Msg("failed to initialize webrtc API")
|
||||||
}
|
}
|
||||||
@ -81,9 +100,7 @@ func (manager *WebRTCManager) Shutdown() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (manager *WebRTCManager) initAPI() error {
|
func (manager *WebRTCManager) initAPI() error {
|
||||||
logger := loggerFactory{
|
logger := pionlog.New(manager.logger)
|
||||||
logger: manager.logger,
|
|
||||||
}
|
|
||||||
|
|
||||||
settings := webrtc.SettingEngine{
|
settings := webrtc.SettingEngine{
|
||||||
LoggerFactory: logger,
|
LoggerFactory: logger,
|
||||||
@ -140,8 +157,8 @@ func (manager *WebRTCManager) initAPI() error {
|
|||||||
|
|
||||||
// Create MediaEngine with selected codecs
|
// Create MediaEngine with selected codecs
|
||||||
engine := webrtc.MediaEngine{}
|
engine := webrtc.MediaEngine{}
|
||||||
_ = engine.RegisterCodec(manager.audioCodec, webrtc.RTPCodecTypeAudio)
|
manager.capture.Audio().Codec().Register(&engine)
|
||||||
_ = engine.RegisterCodec(manager.videoCodec, webrtc.RTPCodecTypeVideo)
|
manager.capture.Video().Codec().Register(&engine)
|
||||||
|
|
||||||
// Register Interceptors
|
// Register Interceptors
|
||||||
i := &interceptor.Registry{}
|
i := &interceptor.Registry{}
|
||||||
@ -304,43 +321,3 @@ func (manager *WebRTCManager) ICEServers() []webrtc.ICEServer {
|
|||||||
func (manager *WebRTCManager) ImplicitControl() bool {
|
func (manager *WebRTCManager) ImplicitControl() bool {
|
||||||
return manager.config.ImplicitControl
|
return manager.config.ImplicitControl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (manager *WebRTCManager) createTrack(codecName string) (*webrtc.TrackLocalStaticSample, webrtc.RTPCodecParameters, error) {
|
|
||||||
var codec webrtc.RTPCodecParameters
|
|
||||||
|
|
||||||
id := ""
|
|
||||||
fb := []webrtc.RTCPFeedback{}
|
|
||||||
|
|
||||||
switch codecName {
|
|
||||||
case "VP8":
|
|
||||||
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: fb}, PayloadType: 96}
|
|
||||||
id = "video"
|
|
||||||
case "VP9":
|
|
||||||
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP9, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: fb}, PayloadType: 98}
|
|
||||||
id = "video"
|
|
||||||
case "H264":
|
|
||||||
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, ClockRate: 90000, Channels: 0, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", RTCPFeedback: fb}, PayloadType: 102}
|
|
||||||
id = "video"
|
|
||||||
case "Opus":
|
|
||||||
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus, ClockRate: 48000, Channels: 2, SDPFmtpLine: "useinbandfec=1", RTCPFeedback: fb}, PayloadType: 111}
|
|
||||||
id = "audio"
|
|
||||||
case "G722":
|
|
||||||
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeG722, ClockRate: 8000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: fb}, PayloadType: 9}
|
|
||||||
id = "audio"
|
|
||||||
case "PCMU":
|
|
||||||
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypePCMU, ClockRate: 8000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: fb}, PayloadType: 0}
|
|
||||||
id = "audio"
|
|
||||||
case "PCMA":
|
|
||||||
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypePCMA, ClockRate: 8000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: fb}, PayloadType: 8}
|
|
||||||
id = "audio"
|
|
||||||
default:
|
|
||||||
return nil, codec, fmt.Errorf("unknown codec %s", codecName)
|
|
||||||
}
|
|
||||||
|
|
||||||
track, err := webrtc.NewTrackLocalStaticSample(codec.RTPCodecCapability, id, "stream")
|
|
||||||
if err != nil {
|
|
||||||
return nil, codec, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return track, codec, nil
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package websocket
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
@ -14,8 +14,7 @@ func (h *MessageHandler) adminLock(id string, session types.Session, payload *me
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := h.locked[payload.Resource]
|
if h.state.IsLocked(payload.Resource) {
|
||||||
if ok {
|
|
||||||
h.logger.Debug().Str("resource", payload.Resource).Msg("resource already locked...")
|
h.logger.Debug().Str("resource", payload.Resource).Msg("resource already locked...")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -30,7 +29,7 @@ func (h *MessageHandler) adminLock(id string, session types.Session, payload *me
|
|||||||
h.sessions.SetControlLocked(true)
|
h.sessions.SetControlLocked(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
h.locked[payload.Resource] = id
|
h.state.Lock(payload.Resource, id)
|
||||||
|
|
||||||
if err := h.sessions.Broadcast(
|
if err := h.sessions.Broadcast(
|
||||||
message.AdminLock{
|
message.AdminLock{
|
||||||
@ -51,8 +50,7 @@ func (h *MessageHandler) adminUnlock(id string, session types.Session, payload *
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := h.locked[payload.Resource]
|
if !h.state.IsLocked(payload.Resource) {
|
||||||
if !ok {
|
|
||||||
h.logger.Debug().Str("resource", payload.Resource).Msg("resource not locked...")
|
h.logger.Debug().Str("resource", payload.Resource).Msg("resource not locked...")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -62,7 +60,7 @@ func (h *MessageHandler) adminUnlock(id string, session types.Session, payload *
|
|||||||
h.sessions.SetControlLocked(false)
|
h.sessions.SetControlLocked(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(h.locked, payload.Resource)
|
h.state.Unlock(payload.Resource)
|
||||||
|
|
||||||
if err := h.sessions.Broadcast(
|
if err := h.sessions.Broadcast(
|
||||||
message.AdminLock{
|
message.AdminLock{
|
||||||
@ -114,7 +112,7 @@ func (h *MessageHandler) adminControl(id string, session types.Session) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *MessageHandler) adminRelease(id string, session types.Session) error {
|
func (h *MessageHandler) AdminRelease(id string, session types.Session) error {
|
||||||
if !session.Admin() {
|
if !session.Admin() {
|
||||||
h.logger.Debug().Msg("user not admin")
|
h.logger.Debug().Msg("user not admin")
|
||||||
return nil
|
return nil
|
||||||
@ -302,7 +300,7 @@ func (h *MessageHandler) adminBan(id string, session types.Session, payload *mes
|
|||||||
}
|
}
|
||||||
|
|
||||||
h.logger.Debug().Str("address", remote).Msg("adding address to banned")
|
h.logger.Debug().Str("address", remote).Msg("adding address to banned")
|
||||||
h.banned[address[0]] = id
|
h.state.Ban(address[0], id)
|
||||||
|
|
||||||
if err := target.Kick("banned"); err != nil {
|
if err := target.Kick("banned"); err != nil {
|
||||||
return err
|
return err
|
@ -1,4 +1,4 @@
|
|||||||
package websocket
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"m1k1o/neko/internal/types"
|
"m1k1o/neko/internal/types"
|
||||||
@ -7,18 +7,37 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (h *MessageHandler) boradcastCreate(session types.Session, payload *message.BroadcastCreate) error {
|
func (h *MessageHandler) boradcastCreate(session types.Session, payload *message.BroadcastCreate) error {
|
||||||
|
broadcast := h.capture.Broadcast()
|
||||||
|
|
||||||
if !session.Admin() {
|
if !session.Admin() {
|
||||||
h.logger.Debug().Msg("user not admin")
|
h.logger.Debug().Msg("user not admin")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pipelineErr := h.broadcast.Create(payload.URL)
|
if payload.URL == "" {
|
||||||
if pipelineErr != nil {
|
return session.Send(
|
||||||
|
message.SystemMessage{
|
||||||
|
Event: event.SYSTEM_ERROR,
|
||||||
|
Title: "Error while starting broadcast",
|
||||||
|
Message: "missing broadcast URL",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if broadcast.Started() {
|
||||||
|
return session.Send(
|
||||||
|
message.SystemMessage{
|
||||||
|
Event: event.SYSTEM_ERROR,
|
||||||
|
Title: "Error while starting broadcast",
|
||||||
|
Message: "server is already broadcasting",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := broadcast.Start(payload.URL); err != nil {
|
||||||
if err := session.Send(
|
if err := session.Send(
|
||||||
message.SystemMessage{
|
message.SystemMessage{
|
||||||
Event: event.SYSTEM_ERROR,
|
Event: event.SYSTEM_ERROR,
|
||||||
Title: "Error while starting broadcast",
|
Title: "Error while starting broadcast",
|
||||||
Message: pipelineErr.Error(),
|
Message: err.Error(),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
h.logger.Warn().Err(err).Msgf("sending event %s has failed", event.SYSTEM_ERROR)
|
h.logger.Warn().Err(err).Msgf("sending event %s has failed", event.SYSTEM_ERROR)
|
||||||
return err
|
return err
|
||||||
@ -33,12 +52,23 @@ func (h *MessageHandler) boradcastCreate(session types.Session, payload *message
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *MessageHandler) boradcastDestroy(session types.Session) error {
|
func (h *MessageHandler) boradcastDestroy(session types.Session) error {
|
||||||
|
broadcast := h.capture.Broadcast()
|
||||||
|
|
||||||
if !session.Admin() {
|
if !session.Admin() {
|
||||||
h.logger.Debug().Msg("user not admin")
|
h.logger.Debug().Msg("user not admin")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
h.broadcast.Destroy()
|
if !broadcast.Started() {
|
||||||
|
return session.Send(
|
||||||
|
message.SystemMessage{
|
||||||
|
Event: event.SYSTEM_ERROR,
|
||||||
|
Title: "Error while stopping broadcast",
|
||||||
|
Message: "server is not broadcasting",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast.Stop()
|
||||||
|
|
||||||
if err := h.boradcastStatus(nil); err != nil {
|
if err := h.boradcastStatus(nil); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -48,14 +78,17 @@ func (h *MessageHandler) boradcastDestroy(session types.Session) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *MessageHandler) boradcastStatus(session types.Session) error {
|
func (h *MessageHandler) boradcastStatus(session types.Session) error {
|
||||||
|
broadcast := h.capture.Broadcast()
|
||||||
|
|
||||||
|
msg := message.BroadcastStatus{
|
||||||
|
Event: event.BORADCAST_STATUS,
|
||||||
|
IsActive: broadcast.Started(),
|
||||||
|
URL: broadcast.Url(),
|
||||||
|
}
|
||||||
|
|
||||||
// if no session, broadcast change
|
// if no session, broadcast change
|
||||||
if session == nil {
|
if session == nil {
|
||||||
if err := h.sessions.AdminBroadcast(
|
if err := h.sessions.AdminBroadcast(msg, nil); err != nil {
|
||||||
message.BroadcastStatus{
|
|
||||||
Event: event.BORADCAST_STATUS,
|
|
||||||
IsActive: h.broadcast.IsActive(),
|
|
||||||
URL: h.broadcast.GetUrl(),
|
|
||||||
}, nil); err != nil {
|
|
||||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.BORADCAST_STATUS)
|
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.BORADCAST_STATUS)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -68,12 +101,7 @@ func (h *MessageHandler) boradcastStatus(session types.Session) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := session.Send(
|
if err := session.Send(msg); err != nil {
|
||||||
message.BroadcastStatus{
|
|
||||||
Event: event.BORADCAST_STATUS,
|
|
||||||
IsActive: h.broadcast.IsActive(),
|
|
||||||
URL: h.broadcast.GetUrl(),
|
|
||||||
}); err != nil {
|
|
||||||
h.logger.Warn().Err(err).Msgf("sending event %s has failed", event.BORADCAST_STATUS)
|
h.logger.Warn().Err(err).Msgf("sending event %s has failed", event.BORADCAST_STATUS)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package websocket
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"m1k1o/neko/internal/types"
|
"m1k1o/neko/internal/types"
|
@ -1,4 +1,4 @@
|
|||||||
package websocket
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"m1k1o/neko/internal/types"
|
"m1k1o/neko/internal/types"
|
||||||
@ -34,8 +34,7 @@ func (h *MessageHandler) controlRequest(id string, session types.Session) error
|
|||||||
// check for host
|
// check for host
|
||||||
if !h.sessions.HasHost() {
|
if !h.sessions.HasHost() {
|
||||||
// check if control is locked or user is admin
|
// check if control is locked or user is admin
|
||||||
_, ok := h.locked["control"]
|
if h.state.IsLocked("control") && !session.Admin() {
|
||||||
if ok && !session.Admin() {
|
|
||||||
h.logger.Debug().Msg("control is locked")
|
h.logger.Debug().Msg("control is locked")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -98,8 +97,7 @@ func (h *MessageHandler) controlGive(id string, session types.Session, payload *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check if control is locked or giver is admin
|
// check if control is locked or giver is admin
|
||||||
_, ok := h.locked["control"]
|
if h.state.IsLocked("control") && !session.Admin() {
|
||||||
if ok && !session.Admin() {
|
|
||||||
h.logger.Debug().Msg("control is locked")
|
h.logger.Debug().Msg("control is locked")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -131,7 +129,7 @@ func (h *MessageHandler) controlClipboard(id string, session types.Session, payl
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
h.remote.WriteClipboard(payload.Text)
|
h.desktop.WriteClipboard(payload.Text)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,41 +140,18 @@ func (h *MessageHandler) controlKeyboard(id string, session types.Session, paylo
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h.desktop.SetKeyboardModifiers(types.KeyboardModifiers{
|
||||||
|
NumLock: payload.NumLock,
|
||||||
|
CapsLock: payload.CapsLock,
|
||||||
|
// TODO: ScrollLock is deprecated.
|
||||||
|
})
|
||||||
|
|
||||||
// change layout
|
// change layout
|
||||||
if payload.Layout != nil {
|
if payload.Layout != nil {
|
||||||
h.remote.SetKeyboardLayout(*payload.Layout)
|
return h.desktop.SetKeyboardMap(types.KeyboardMap{
|
||||||
|
Layout: *payload.Layout,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// set num lock
|
|
||||||
var NumLock = 0
|
|
||||||
if payload.NumLock == nil {
|
|
||||||
NumLock = -1
|
|
||||||
} else if *payload.NumLock {
|
|
||||||
NumLock = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// set caps lock
|
|
||||||
var CapsLock = 0
|
|
||||||
if payload.CapsLock == nil {
|
|
||||||
CapsLock = -1
|
|
||||||
} else if *payload.CapsLock {
|
|
||||||
CapsLock = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// set scroll lock
|
|
||||||
var ScrollLock = 0
|
|
||||||
if payload.ScrollLock == nil {
|
|
||||||
ScrollLock = -1
|
|
||||||
} else if *payload.ScrollLock {
|
|
||||||
ScrollLock = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
h.logger.Debug().
|
|
||||||
Int("NumLock", NumLock).
|
|
||||||
Int("CapsLock", CapsLock).
|
|
||||||
Int("ScrollLock", ScrollLock).
|
|
||||||
Msg("setting keyboard modifiers")
|
|
||||||
|
|
||||||
h.remote.SetKeyboardModifiers(NumLock, CapsLock, ScrollLock)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
@ -1,41 +1,56 @@
|
|||||||
package websocket
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"m1k1o/neko/internal/types"
|
"m1k1o/neko/internal/types"
|
||||||
"m1k1o/neko/internal/types/event"
|
"m1k1o/neko/internal/types/event"
|
||||||
"m1k1o/neko/internal/types/message"
|
"m1k1o/neko/internal/types/message"
|
||||||
"m1k1o/neko/internal/utils"
|
"m1k1o/neko/internal/utils"
|
||||||
|
"m1k1o/neko/internal/websocket/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MessageHandler struct {
|
type MessageHandler struct {
|
||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
sessions types.SessionManager
|
sessions types.SessionManager
|
||||||
webrtc types.WebRTCManager
|
desktop types.DesktopManager
|
||||||
remote types.RemoteManager
|
capture types.CaptureManager
|
||||||
broadcast types.BroadcastManager
|
webrtc types.WebRTCManager
|
||||||
banned map[string]string // IP -> session ID (that banned it)
|
state *state.State
|
||||||
locked map[string]string // resource name -> session ID (that locked it)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *MessageHandler) Connected(admin bool, socket *WebSocket) (bool, string) {
|
func New(
|
||||||
address := socket.Address()
|
sessions types.SessionManager,
|
||||||
|
desktop types.DesktopManager,
|
||||||
|
capture types.CaptureManager,
|
||||||
|
webrtc types.WebRTCManager,
|
||||||
|
state *state.State,
|
||||||
|
) *MessageHandler {
|
||||||
|
return &MessageHandler{
|
||||||
|
logger: log.With().Str("module", "websocket").Str("submodule", "handler").Logger(),
|
||||||
|
sessions: sessions,
|
||||||
|
desktop: desktop,
|
||||||
|
capture: capture,
|
||||||
|
webrtc: webrtc,
|
||||||
|
state: state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *MessageHandler) Connected(admin bool, address string) (bool, string) {
|
||||||
if address == "" {
|
if address == "" {
|
||||||
h.logger.Debug().Msg("no remote address")
|
h.logger.Debug().Msg("no remote address")
|
||||||
} else {
|
} else {
|
||||||
_, ok := h.banned[address]
|
if h.state.IsBanned(address) {
|
||||||
if ok {
|
|
||||||
h.logger.Debug().Str("address", address).Msg("banned")
|
h.logger.Debug().Str("address", address).Msg("banned")
|
||||||
return false, "banned"
|
return false, "banned"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := h.locked["login"]
|
if h.state.IsLocked("login") && !admin {
|
||||||
if ok && !admin {
|
|
||||||
h.logger.Debug().Msg("server locked")
|
h.logger.Debug().Msg("server locked")
|
||||||
return false, "locked"
|
return false, "locked"
|
||||||
}
|
}
|
||||||
@ -149,7 +164,7 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
|
|||||||
case event.ADMIN_CONTROL:
|
case event.ADMIN_CONTROL:
|
||||||
return errors.Wrapf(h.adminControl(id, session), "%s failed", header.Event)
|
return errors.Wrapf(h.adminControl(id, session), "%s failed", header.Event)
|
||||||
case event.ADMIN_RELEASE:
|
case event.ADMIN_RELEASE:
|
||||||
return errors.Wrapf(h.adminRelease(id, session), "%s failed", header.Event)
|
return errors.Wrapf(h.AdminRelease(id, session), "%s failed", header.Event)
|
||||||
case event.ADMIN_GIVE:
|
case event.ADMIN_GIVE:
|
||||||
payload := &message.Admin{}
|
payload := &message.Admin{}
|
||||||
return errors.Wrapf(
|
return errors.Wrapf(
|
@ -1,4 +1,4 @@
|
|||||||
package websocket
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"m1k1o/neko/internal/types"
|
"m1k1o/neko/internal/types"
|
||||||
@ -12,7 +12,11 @@ func (h *MessageHandler) screenSet(id string, session types.Session, payload *me
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.remote.ChangeResolution(payload.Width, payload.Height, payload.Rate); err != nil {
|
if err := h.desktop.SetScreenSize(types.ScreenSize{
|
||||||
|
Width: payload.Width,
|
||||||
|
Height: payload.Height,
|
||||||
|
Rate: payload.Rate,
|
||||||
|
}); err != nil {
|
||||||
h.logger.Warn().Err(err).Msgf("unable to change screen size")
|
h.logger.Warn().Err(err).Msgf("unable to change screen size")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -33,12 +37,12 @@ func (h *MessageHandler) screenSet(id string, session types.Session, payload *me
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *MessageHandler) screenResolution(id string, session types.Session) error {
|
func (h *MessageHandler) screenResolution(id string, session types.Session) error {
|
||||||
if size := h.remote.GetScreenSize(); size != nil {
|
if size := h.desktop.GetScreenSize(); size != nil {
|
||||||
if err := session.Send(message.ScreenResolution{
|
if err := session.Send(message.ScreenResolution{
|
||||||
Event: event.SCREEN_RESOLUTION,
|
Event: event.SCREEN_RESOLUTION,
|
||||||
Width: size.Width,
|
Width: size.Width,
|
||||||
Height: size.Height,
|
Height: size.Height,
|
||||||
Rate: int(size.Rate),
|
Rate: size.Rate,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
h.logger.Warn().Err(err).Msgf("sending event %s has failed", event.SCREEN_RESOLUTION)
|
h.logger.Warn().Err(err).Msgf("sending event %s has failed", event.SCREEN_RESOLUTION)
|
||||||
return err
|
return err
|
||||||
@ -56,7 +60,7 @@ func (h *MessageHandler) screenConfigurations(id string, session types.Session)
|
|||||||
|
|
||||||
if err := session.Send(message.ScreenConfigurations{
|
if err := session.Send(message.ScreenConfigurations{
|
||||||
Event: event.SCREEN_CONFIGURATIONS,
|
Event: event.SCREEN_CONFIGURATIONS,
|
||||||
Configurations: h.remote.ScreenConfigurations(),
|
Configurations: h.desktop.ScreenConfigurations(),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
h.logger.Warn().Err(err).Msgf("sending event %s has failed", event.SCREEN_CONFIGURATIONS)
|
h.logger.Warn().Err(err).Msgf("sending event %s has failed", event.SCREEN_CONFIGURATIONS)
|
||||||
return err
|
return err
|
@ -1,4 +1,4 @@
|
|||||||
package websocket
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"m1k1o/neko/internal/types"
|
"m1k1o/neko/internal/types"
|
||||||
@ -16,7 +16,7 @@ func (h *MessageHandler) SessionCreated(id string, session types.Session) error
|
|||||||
if err := session.Send(message.SystemInit{
|
if err := session.Send(message.SystemInit{
|
||||||
Event: event.SYSTEM_INIT,
|
Event: event.SYSTEM_INIT,
|
||||||
ImplicitHosting: h.webrtc.ImplicitControl(),
|
ImplicitHosting: h.webrtc.ImplicitControl(),
|
||||||
Locks: h.locked,
|
Locks: h.state.AllLocked(),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
h.logger.Warn().Str("id", id).Err(err).Msgf("sending event %s has failed", event.SYSTEM_INIT)
|
h.logger.Warn().Str("id", id).Err(err).Msgf("sending event %s has failed", event.SYSTEM_INIT)
|
||||||
return err
|
return err
|