Archived
2
0

264 Commits
v2.1 ... v2.4

Author SHA1 Message Date
b6e668bc53 send disconnect on server shutdown. 2021-09-11 18:21:28 +02:00
b7950b3650 fix stopsignal for go program. 2021-09-11 18:15:12 +02:00
6ee8bb86fe go mod update. 2021-09-11 17:45:05 +02:00
f16ffba963 remove xserver-xorg-input-void dep. 2021-08-31 18:45:25 +02:00
334f60ac7c upgrade to debian 11, fixes #91. 2021-08-31 18:29:55 +02:00
9be75c20ca typescript catch error any. 2021-08-31 18:25:06 +02:00
81006e02ed node upgrade. 2021-08-31 18:14:50 +02:00
78bf9e276b golang upgrade deps. 2021-08-31 18:13:34 +02:00
ecf59a39bc bump golang version. 2021-08-31 18:13:05 +02:00
def879c586 add empty_displayname to locale. 2021-08-31 18:04:55 +02:00
b808530f0c use async / await. 2021-08-31 17:58:32 +02:00
aae55ea585 edit readme. 2021-08-22 16:14:37 +02:00
89f4e697c2 js lint fix. 2021-08-15 16:43:59 +02:00
523831237d refactor broadcast UI. 2021-08-15 16:41:42 +02:00
313cd55049 add system error. 2021-08-15 16:05:26 +02:00
9e466b08cc catch errors from gst pipeline. 2021-08-15 15:37:27 +02:00
6708ce2caf npm update. 2021-08-15 14:59:06 +02:00
0185178234 go upgrade. 2021-08-15 14:57:39 +02:00
6fcf9ff07e Merge branch 'dev' of github.com:m1k1o/neko into dev 2021-08-15 14:52:43 +02:00
c2fa40d3ff Install without Docker, fixes #87. 2021-08-15 14:51:55 +02:00
d27c329fac How to use RTMP feature, fixes #88. 2021-08-15 14:49:05 +02:00
ab05b3124f Update Chromium extensions (#89)
uBlock Origin updated to v1.37.2
NordVPN updated to v2.31.0

Signed-off-by: Aaron <admin@datahoarder.dev>
2021-08-04 23:34:42 +02:00
4410956eae update README.md 2021-07-26 17:01:05 +02:00
db66cb565c readme: add cap-add SYS_ADMIN to brave. 2021-07-26 16:49:51 +02:00
85d6205823 Lib build fix $te ref (#86) 2021-07-24 18:05:14 +02:00
7cfc8c4795 added brave browser. 2021-07-24 10:35:45 +02:00
c3abbfd577 do not handle msgs on nonexisting id. 2021-07-22 20:58:39 +02:00
46b8c9f42d golang minor changes cleanup. 2021-07-22 20:58:15 +02:00
609b3f0927 Merge branch 'firefox-profile' into dev 2021-07-22 20:45:49 +02:00
bc2fa6cd40 add link to header. 2021-07-22 20:13:58 +02:00
1bf588511a about: use dev branch. 2021-07-22 20:08:38 +02:00
7d94ee603d change version & add fork author. 2021-07-22 20:07:03 +02:00
eb057f1ab2 add description to html. 2021-07-22 20:05:47 +02:00
5606bfa174 change about page to up-to-date. 2021-07-22 20:03:44 +02:00
122fd8b957 custom kick message, fixes #56. 2021-07-17 19:37:50 +02:00
b9b80ed79a show reconnecting notify. #48 2021-07-17 19:18:21 +02:00
2cff2a340f show badge on new messages. 2021-07-17 12:38:12 +02:00
429fc7eb68 fix fast scroll speed on macos. (#85) 2021-07-17 11:56:26 +02:00
ffcca402ef fixed mac os keyboard mapping. (#84) 2021-07-17 11:56:16 +02:00
38bed98741 fix timeout typescript. 2021-07-17 11:30:46 +02:00
45ef058bf4 Update uBlock Origin to v1.36.2 (#80)
Signed-off-by: Aaron <admin@datahoarder.dev>
2021-07-12 13:43:53 +02:00
e5c59faf1a firefox profile directory. 2021-07-06 12:32:04 +02:00
c392cad046 Update uBlock Origin to v1.36.0 (#77)
Signed-off-by: Aaron <admin@datahoarder.dev>
2021-07-05 16:52:51 +02:00
d41580050e switch to latest firefox version. 2021-07-04 20:15:56 +02:00
02a38d8805 change badges in README. 2021-06-29 00:19:19 +02:00
bf428c5154 upgrade dependencies. 2021-06-28 23:54:38 +02:00
86a9effe41 don't kill webrtc on temporary network issues #48. 2021-06-27 22:20:15 +02:00
733c39412b use log panic in webrtc. 2021-06-26 13:45:22 +02:00
7aa7e0eacb custom ipfetch #63. 2021-06-26 13:44:41 +02:00
3a79615a2b ungoogled-chromium fetch latest release. 2021-06-26 13:19:49 +02:00
da246345e1 update readme. 2021-06-26 13:19:18 +02:00
1800d077d8 Build by github actions (#70)
* BUILD_IMAGE from environment vairables has precedence.

* add workflow.

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

Signed-off-by: Aaron <admin@datahoarder.dev>
2021-04-02 22:26:20 +02:00
b264e397d8 add language picker. 2021-04-02 15:29:27 +00:00
fd4b4bbfa2 Merge branch 'pr-37' into dev 2021-04-02 15:10:21 +00:00
700d43710e lint fix. 2021-04-02 15:06:13 +00:00
faab040bd5 proofread 2021-04-01 23:23:08 +02:00
2867b42745 Merge pull request #36 from mbattista/arm-container
Arm container
2021-04-01 23:19:24 +02:00
70860ab83c easy development tools. 2021-04-01 23:18:50 +02:00
24699ec512 set H264 in arm base. 2021-04-01 22:08:35 +02:00
b6c032921d bugfix. 2021-04-01 21:12:32 +02:00
b2f60d36a5 add arm video pipeline to container. 2021-04-01 21:07:28 +02:00
67c931350b updated README. 2021-04-01 21:00:57 +02:00
b6b1cd01ac changed ARM builds. 2021-04-01 21:00:48 +02:00
631e8775fe set base image from ARG. 2021-04-01 20:37:51 +02:00
653e32697f add chromium image. 2021-04-01 20:36:43 +02:00
5082bab6de move ungoogled-chromium. 2021-04-01 20:00:59 +02:00
9cf824c5ea some chromium tests 2021-04-01 13:46:17 +01:00
5ff441582d v4l2 as example pipeline 2021-03-30 13:15:55 +01:00
a287ca1188 changed README to reflect changes 2021-03-29 22:17:12 +01:00
afbd1f6f59 update to new version 2021-03-29 22:09:09 +01:00
3989635b90 Change the way we export (breaking change) 2021-03-29 11:12:07 +00:00
99d528f2ea translations 2021-03-29 11:11:37 +00:00
78d2d706af wrong condition 2021-03-29 11:11:26 +00:00
78a1744da4 Merge pull request #1 from m1k1o/dev
Update from remote dev
2021-03-29 13:04:05 +02:00
8efc5d7094 merge from remote 2021-03-29 11:03:25 +00:00
a1fcf87345 dev branch 2021-03-29 10:55:31 +00:00
3cb4a1795b arm based container 2021-03-29 10:27:28 +01:00
03c991ca79 Merge pull request #35 from mbattista/update-dependencies
updated dependencies
2021-03-29 10:25:19 +02:00
7edd9c090f added versions to readme. 2021-03-29 10:24:33 +02:00
23b6c82241 go mod tidy. 2021-03-29 10:20:30 +02:00
b88f1750f7 updated dependencies 2021-03-28 19:44:43 +00:00
2b90d42f75 general build. 2021-03-22 22:01:54 +01:00
733cd55c3c Merge pull request #33 from mbattista/vlc-wesnoth
VLC and Wesnoth
2021-03-22 18:58:06 +01:00
b0884352b8 add vncviewer to images. 2021-03-22 18:56:51 +01:00
22622325ed fix comment. 2021-03-22 18:55:45 +01:00
e01b60368c vlc default folder, no startup dialog and fix openbox. 2021-03-22 18:53:41 +01:00
6699ff1c17 nopasswd for sudo 2021-03-22 16:15:01 +00:00
2ad760c987 changed README 2021-03-22 15:36:48 +00:00
1b52d6db20 removing wesnoth adding xfce 2021-03-22 15:22:52 +00:00
0d54b71055 fix some typos 2021-03-21 23:00:33 +00:00
39f5c0e7fa added vlc for local files and wesnoth as a showcase 2021-03-21 22:52:58 +00:00
b96ba47224 add members & host to stats. 2021-03-19 22:06:40 +01:00
bbae073104 add /stats endpoint. 2021-03-19 21:33:49 +01:00
9771b551df proper disconnect. 2021-03-19 20:16:08 +01:00
bbea5f5715 Merge branch 'dev' of github.com:m1k1o/neko into dev 2021-03-19 14:42:57 +01:00
ed2cdaf71d add HEALTHCHECK. 2021-03-19 14:42:54 +01:00
cb586df833 Merge pull request #32 from whalehub/patch-1 2021-03-15 20:32:19 +01:00
d77751dabc Dockerfile: Update Chromium to v89.0.4389.90
Signed-off-by: Aaron <admin@datahoarder.dev>
2021-03-15 18:34:57 +01:00
929f315f6e Merge pull request #30 from mbattista/ice-race-condition
should fix race condition
2021-03-13 10:33:49 +01:00
5762f33e36 fix lint and clear candidates array. 2021-03-13 10:32:05 +01:00
a961dd6428 should fix race condition 2021-03-12 23:45:01 +00:00
108ac79443 autologin with URL pwd first. 2021-03-12 22:24:52 +01:00
577f508912 Merge pull request #28 from whalehub/patch-1
Dockerfile: Update Chromium to v88.0.4324.182
2021-03-12 17:55:31 +01:00
7080f6adc7 Dockerfile: Update Chromium to v88.0.4324.182
Signed-off-by: Aaron <admin@datahoarder.dev>
2021-03-12 17:10:10 +01:00
19631d8ae9 Revert "lower keyframes max dist for faster loading."
This reverts commit 14939db65a.
2021-03-12 01:15:31 +01:00
14939db65a lower keyframes max dist for faster loading. 2021-03-10 23:18:14 +01:00
b75303aa30 ws relative url only path. 2021-03-10 23:10:14 +01:00
79e817e0b2 local path on resources. 2021-03-10 23:07:26 +01:00
69e7990afb relative websocket URL. 2021-03-10 23:02:34 +01:00
afb416ea57 go files serve RequestURI -> URL.Path. 2021-03-10 22:52:41 +01:00
a5c90f6198 client: fix relative paths. 2021-03-10 22:34:27 +01:00
c980409e3d client fix build err. 2021-03-10 22:15:29 +01:00
085806d1b2 local default URI for client. 2021-03-10 22:08:04 +01:00
aace4add5b update Readme. 2021-03-10 21:47:16 +01:00
d3a16ea212 remove autopassword param on login. 2021-03-10 21:39:52 +01:00
d763895665 invite links. 2021-03-10 21:35:40 +01:00
9e64b47dea lint fix. 2021-03-09 18:20:06 +01:00
f68b8fe922 remove console.log. 2021-03-09 18:17:03 +01:00
6497109292 edit build in package json. 2021-03-09 18:07:35 +01:00
9b9a3ad4af add initWithURL. 2021-03-09 18:06:57 +01:00
577cd5110e lint code. 2021-03-09 18:02:20 +01:00
cdeeff680b Fix i18n 2021-03-09 17:16:53 +01:00
dc3bb4f837 init 2021-03-09 15:56:52 +01:00
15edc0f73e client linter fix. 2021-03-01 15:10:20 +01:00
0e5e1faf35 Merge pull request #25 from mbattista/dev
removes small lags from video live streamings
2021-03-01 14:50:20 +01:00
69e0979aa0 fix log severity. 2021-03-01 14:31:27 +01:00
377ecd7d06 fix indentation. 2021-03-01 14:31:16 +01:00
6e2b5084e7 fixes https://github.com/m1k1o/neko/issues/22 2021-03-01 01:07:31 +00:00
9c3d441d16 removes small lags from video live streamings 2021-02-28 23:12:03 +00:00
c410134c7d add vncviewer. 2021-02-19 17:13:52 +01:00
72b28d69e6 add Mobile support to readme. 2021-02-18 20:12:33 +01:00
2782a1dd08 requestFullscreen compatibility. 2021-02-18 20:07:06 +01:00
6057a73a08 fixed iOS video load bug. 2021-02-18 19:53:15 +01:00
afcd029e4d fix kicking user. 2021-02-18 18:33:21 +01:00
3dd48c91ac client lint fix. 2021-02-18 18:06:15 +01:00
3160382015 dispaly PIP only if available. 2021-02-18 18:02:23 +01:00
1834186f40 Merge pull request #21 from mbattista/dev
update to pion v3
2021-02-15 15:43:28 +01:00
56b1aa92f4 add WebRTC timeouts. 2021-02-15 15:42:10 +01:00
595259b30c fix sessions manager thread safety. 2021-02-15 15:41:08 +01:00
321e52ee4f pass screen frame rate to gstreamer pipeline. 2021-02-15 14:59:20 +01:00
27740b0af0 Revert "Offer ICERestart"
This reverts commit 646e8af042.
2021-02-15 00:53:20 +01:00
646e8af042 Offer ICERestart 2021-02-14 23:34:01 +00:00
e57fe5efac nack is nativly implemented by pion webrtc v3, gstreamer has 25 fps with no additional parameters 2021-02-14 22:50:49 +00:00
29fc67aff9 fix logging for WebRTC. 2021-02-14 21:39:05 +01:00
8ef91be6ad update README. 2021-02-14 21:15:34 +01:00
61eed9ca98 use audio and video bitrate. 2021-02-14 21:07:56 +01:00
34954d1a54 update go packages version. 2021-02-14 20:26:50 +01:00
f24c99f90c remove ClockRate from gst pipelines. 2021-02-14 20:26:35 +01:00
b1e358c0bc Merge branch 'dev' of https://github.com/mbattista/neko into dev 2021-02-14 19:14:32 +00:00
405ef7b9dd bitrate as integer 2021-02-14 19:13:52 +00:00
d53bb724b2 fix lint error 2021-02-14 19:18:27 +01:00
4795d3ac96 wrong docker entries 2021-02-14 16:51:08 +00:00
82cc13b680 back to recvonly 2021-02-14 16:49:08 +00:00
a362df4976 update to pion v3 2021-02-14 16:30:24 +00:00
00a785f4c5 edit README 2021-02-13 13:04:43 +01:00
762449e46c fix mobile UI 2021-02-13 13:01:51 +01:00
eddd59c49d fix client linter 2021-02-13 12:31:13 +01:00
61e6f62ff5 remove HTML tags from username 2021-02-13 12:19:39 +01:00
c2e57cf51a fix client linter 2021-02-13 12:05:59 +01:00
b8b43d69b0 add playsinline attribute for video 2021-02-12 22:48:31 +01:00
a67d1d0e4f show clipboard when writeText or readText is not available 2021-02-11 23:03:08 +01:00
78fe08a029 Merge branch 'dev' of github.com:m1k1o/neko into dev 2021-02-09 21:23:43 +01:00
3b51c02486 add nordvpn to firefox 2021-02-09 21:23:16 +01:00
f7ae2f3b3c add widevine support to chromium 2021-02-09 21:20:53 +01:00
8c3a54c4d5 Merge pull request #20 from mbattista/dev
Removed double h264 encoding
2021-01-31 23:23:40 +01:00
31cd834526 Removed double h264 encoding
if h264enc and openh264 is installed it lead to both strings in the gstreamer pipeline which resulted in no video output
2021-01-31 23:12:52 +01:00
43dceb363f Merge pull request #19 from whalehub/patch-1
Dockerfile: Update Chromium to v88.0.4324.96
2021-01-30 18:29:17 +01:00
f0e04828e8 Dockerfile: Update Chromium to v88.0.4324.96
Signed-off-by: Aaron <admin@datahoarder.dev>
2021-01-30 18:05:26 +01:00
bac0686f20 Merge branch 'master' of https://github.com/nurdism/neko into sk_lang 2020-07-11 23:18:28 +02:00
c146534ae1 Merge branch 'master' of https://github.com/nurdism/neko into sk_lang 2020-06-09 14:55:21 +02:00
6a7327c238 SK translation fixed 2020-04-20 21:11:58 +02:00
1ce276f313 Merge branch 'master' of github.com:m1k1o/neko into sk_lang 2020-04-20 21:05:55 +02:00
ee2f27f80a initial SK support 2020-04-06 12:44:47 +02:00
134 changed files with 10915 additions and 2889 deletions

76
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,76 @@
name: "CI for builds"
on:
push:
branches: [ dev ]
#
# Run this action periodically to keep browsers up-to-date
# even if there is no activity in this repo.
#
schedule:
- cron: "43 2 * * 1"
env:
DOCKER_IMAGE: m1k1o/neko
jobs:
build-base:
runs-on: ubuntu-latest
#
# do not run on forks
#
if: github.repository_owner == 'm1k1o'
steps:
- name: Check Out Repo
uses: actions/checkout@v2
- name: Login to Docker Hub
run: |
docker login --username "${DOCKER_USERNAME}" --password-stdin "${DOCKER_REGISTRY}" <<< "${DOCKER_TOKEN}"
env:
DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
- name: Build base
run: |
BUILD_IMAGE=${DOCKER_IMAGE} .m1k1o/build ${DOCKER_TAG}
docker push ${DOCKER_IMAGE}:${DOCKER_TAG}
env:
DOCKER_TAG: base
build:
runs-on: ubuntu-latest
#
# do not run on forks
#
if: github.repository_owner == 'm1k1o'
needs: [ build-base ]
strategy:
matrix:
tags: [ firefox, chromium, google-chrome, ungoogled-chromium, brave, tor-browser, vncviewer, vlc, xfce ]
env:
DOCKER_TAG: ${{ matrix.tags }}
steps:
- name: Check Out Repo
uses: actions/checkout@v2
- name: Login to Docker Hub
run: |
docker login --username "${DOCKER_USERNAME}" --password-stdin "${DOCKER_REGISTRY}" <<< "${DOCKER_TOKEN}"
env:
DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
- name: Build container
run: |
BUILD_IMAGE=${DOCKER_IMAGE} .m1k1o/build ${DOCKER_TAG}
docker push ${DOCKER_IMAGE}:${DOCKER_TAG}
- name: Push latest tag
if: ${{ matrix.tags == 'firefox' }}
run: |
docker pull ${DOCKER_IMAGE}:${DOCKER_TAG}
docker tag ${DOCKER_IMAGE}:${DOCKER_TAG} ${DOCKER_IMAGE}:latest
docker push ${DOCKER_IMAGE}:latest

18
.m1k1o/.env.default Normal file
View File

@ -0,0 +1,18 @@
#
# you can copy this file to .env.local, if you don't want to have it pushed to repository
#
# this is how will be your images called. you can change it to your fork.
# only need to do this once. here.
BUILD_IMAGE="m1k1o/neko"
# this is where your services will be acessible
CLIENT_PORT=8080
SERVER_PORT=8081
# on which image you want to test it
SERVER_TAG="chromium"
# this is needed for WebRTC. specify your local IP address and free UDP port range.
SERVER_EPR=55000-55009
SERVER_IP=10.8.0.1

40
.m1k1o/README.md Normal file
View File

@ -0,0 +1,40 @@
# How to contribute to neko
If you want to contribute, but do not want to install anything on your host system, we got you covered. You only need docker. Technically, it could be done using vs code development in container, but this is more fun:).
You need to copy `.env.development` to `.env` and customize values.
## Step 1: Building server
- `./build` - You can use this command to build your specified `SERVER_TAG` along with base image.
If you want, you can build other tags. `base` tag needs to be build first:
- `./build base`
- `./build firefox`
- `./build chromium`
- `./build google-chrome`
- etc...
## Step 2: Starting server
- `./start-server` - Starting server image you specified in `.env`.
- `./start-server -r` - Shortcut for rebuilding server binary and then starting.
If you are changing something in the server code, you do not want to rebuild container each time. You can just rebuild your binary:
- `./rebuild-server` - Rebuild only server binary.
- `./rebuild-server -f` - Force to rebuild whole Golang environment (you should do this only of you change some dependencies).
## Step 3: Serving client
- `./serve-client` - Serving vue.js client.
- `./serve-client -i` - Install all dependencies.
## Debug
You can navigate to `CLIENT_PORT` and see live client there. It will be connected to your local server on `SERVER_PORT`.
If you are leaving client as is and not changing it, you don't need to start `./serve-client` and you can access server's GUI directly on `SERVER_PORT`.
Feel free to open new PR.

View File

@ -1,7 +1,7 @@
#
# STAGE 1: SERVER
#
FROM golang:1.15-buster as server
FROM golang:1.17-bullseye as server
WORKDIR /src
#
@ -27,12 +27,12 @@ RUN set -eux; apt-get update; \
#
# build server
COPY server/ .
RUN go get -v -t -d . && go build -o bin/neko -i cmd/neko/main.go
RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
#
# STAGE 2: CLIENT
#
FROM node:12-buster-slim as client
FROM node:14-bullseye-slim as client
WORKDIR /src
#
@ -48,7 +48,7 @@ RUN npm run build
#
# STAGE 3: RUNTIME
#
FROM debian:buster-slim
FROM debian:bullseye-slim
#
# avoid warnings by switching to noninteractive
@ -65,7 +65,7 @@ ARG USER_GID=$USER_UID
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends wget ca-certificates supervisor; \
apt-get install -y --no-install-recommends pulseaudio dbus-x11 xserver-xorg-video-dummy; \
apt-get install -y --no-install-recommends libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx5; \
apt-get install -y --no-install-recommends libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx6; \
#
# gst
apt-get install -y --no-install-recommends libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
@ -117,6 +117,9 @@ ENV NEKO_BIND=:8080
COPY --from=server /src/bin/neko /usr/bin/neko
COPY --from=client /src/dist/ /var/www
HEALTHCHECK --interval=10s --timeout=5s --retries=8 \
CMD wget -O - http://localhost:${NEKO_BIND#*:}/health || exit 1
#
# run neko
CMD ["/usr/bin/supervisord", "-c", "/etc/neko/supervisord.conf"]

136
.m1k1o/base/Dockerfile.arm Normal file
View File

@ -0,0 +1,136 @@
#
# STAGE 1: SERVER
#
FROM arm32v7/golang:1.17-buster as server
WORKDIR /src
#
# install dependencies
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends git cmake make python2 libx11-dev libxrandr-dev libxtst-dev \
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-omx; \
#
# install libclipboard
set -eux; \
cd /tmp; \
git clone https://github.com/jtanx/libclipboard; \
cd libclipboard; \
cmake .; \
make -j4; \
make install; \
rm -rf /tmp/libclipboard; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# build server
COPY server/ .
RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
#
# STAGE 2: CLIENT
#
FROM node:14-buster-slim as client
# install dependencies
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends python2 build-essential
WORKDIR /src
#
# install dependencies
COPY client/package*.json ./
RUN npm install
#
# build client
COPY client/ .
RUN npm run build
#
# STAGE 3: RUNTIME
#
FROM balenalib/raspberry-pi-debian:latest
#
# avoid warnings by switching to noninteractive
ENV DEBIAN_FRONTEND=noninteractive
#
# set custom user
ARG USERNAME=neko
ARG USER_UID=1000
ARG USER_GID=$USER_UID
#
# install dependencies
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends wget ca-certificates supervisor; \
apt-get install -y --no-install-recommends pulseaudio dbus-x11 xserver-xorg-video-dummy; \
apt-get install -y --no-install-recommends libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx5; \
#
# gst
apt-get install -y --no-install-recommends libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-pulseaudio gstreamer1.0-omx; \
#
# create a non-root user
groupadd --gid $USER_GID $USERNAME; \
useradd --uid $USER_UID --gid $USERNAME --shell /bin/bash --create-home $USERNAME; \
adduser $USERNAME audio; \
adduser $USERNAME video; \
adduser $USERNAME pulse; \
#
# setup pulseaudio
mkdir -p /home/$USERNAME/.config/pulse/; \
echo "default-server=unix:/tmp/pulseaudio.socket" > /home/$USERNAME/.config/pulse/client.conf; \
#
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
mkdir /tmp/.X11-unix; \
chmod 1777 /tmp/.X11-unix; \
chown $USERNAME /tmp/.X11-unix/; \
#
# make directories for neko
mkdir -p /etc/neko /var/www /var/log/neko; \
chmod 1777 /var/log/neko; \
chown $USERNAME /var/log/neko/; \
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy config files
COPY .m1k1o/base/dbus /usr/bin/dbus
COPY .m1k1o/base/default.pa /etc/pulse/default.pa
COPY .m1k1o/base/supervisord.conf /etc/neko/supervisord.conf
COPY .m1k1o/base/xorg.conf /etc/neko/xorg.conf
#
# set default envs
ENV USER=$USERNAME
ENV DISPLAY=:99.0
ENV NEKO_PASSWORD=neko
ENV NEKO_PASSWORD_ADMIN=admin
ENV NEKO_BIND=:8080
#
# custom arm values -> video pipeline with GPU encoding
ENV NEKO_H264=1
ENV NEKO_VIDEO='ximagesrc display-name=%s use-damage=0 show-pointer=true use-damage=false ! video/x-raw,framerate=30/1 ! videoconvert ! queue ! video/x-raw,framerate=30/1,format=NV12 ! v4l2h264enc extra-controls="controls,h264_profile=0,video_bitrate=1250000;" ! h264parse config-interval=3 ! video/x-h264,profile=baseline,stream-format=byte-stream'
#
# copy static files from previous stages
COPY --from=server /src/bin/neko /usr/bin/neko
COPY --from=client /src/dist/ /var/www
HEALTHCHECK --interval=10s --timeout=5s --retries=8 \
CMD wget -O - http://localhost:${NEKO_BIND#*:}/health || exit 1
#
# run neko
CMD ["/usr/bin/supervisord", "-c", "/etc/neko/supervisord.conf"]

View File

@ -1,5 +1,6 @@
[supervisord]
nodaemon=true
user=root
pidfile=/var/run/supervisord.pid
logfile=/dev/null
logfile_maxbytes=0
@ -44,6 +45,8 @@ redirect_stderr=true
[program:neko]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/neko serve --static "/var/www"
stopsignal=INT
stopwaitsecs=5
autorestart=true
priority=800
user=%(ENV_USER)s

23
.m1k1o/brave/Dockerfile Normal file
View File

@ -0,0 +1,23 @@
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends apt-transport-https curl openbox; \
#
# install brave browser
curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg; \
echo "deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg arch=amd64] https://brave-browser-apt-release.s3.brave.com/ stable main" \
| tee /etc/apt/sources.list.d/brave-browser-release.list; \
apt-get update; \
apt-get install -y --no-install-recommends brave-browser; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy configuation files
COPY supervisord.conf /etc/neko/supervisord/brave.conf
COPY --chown=neko preferences.json /home/neko/.config/brave/Default/Preferences
COPY policies.json /etc/brave/policies/managed/policies.json
COPY openbox.xml /etc/neko/openbox.xml

763
.m1k1o/brave/openbox.xml Normal file
View File

@ -0,0 +1,763 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Default openbox config but all window decorations are moved
thereby making it harder to accidentally close the virtual browser -->
<openbox_config xmlns="http://openbox.org/3.4/rc"
xmlns:xi="http://www.w3.org/2001/XInclude">
<resistance>
<strength>10</strength>
<screen_edge_strength>20</screen_edge_strength>
</resistance>
<applications>
<!-- Match all windows and remove their decorations (obxprop | grep "^_OB_APP") -->
<application class="Brave-browser*" name="brave-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>

View File

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

View File

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

View File

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

View File

@ -1,35 +1,88 @@
#!/bin/sh
cd "$(dirname "$0")"
BASE=../
BASE="${PWD}/../"
# BUILD_IMAGE from environment vairables has precedence
if [ ! -z "${BUILD_IMAGE}" ]
then
ENV_BUILD_IMAGE="${BUILD_IMAGE}"
fi
if [ -f ".env.default" ]
then
export $(cat .env.default | sed 's/#.*//g' | xargs)
fi
if [ -f ".env" ]
then
export $(cat .env | sed 's/#.*//g' | xargs)
fi
# BUILD_IMAGE from environment vairables has precedence
if [ ! -z "${ENV_BUILD_IMAGE}" ]
then
BUILD_IMAGE="${ENV_BUILD_IMAGE}"
unset ENV_BUILD_IMAGE
fi
if [ -z "${1}" ] && [ ! -z "${SERVER_TAG}" ]
then
./build base
./build ${SERVER_TAG}
exit 0
fi
build_client() {
docker build -t neko-dev-client -f base/Dockerfile --target client "$BASE"
docker run --rm -v "$BASE"/client/dist:/tmp/dist neko-dev-client sh -c "rm -rf /tmp/dist/*; cp -r /src/dist/* /tmp/dist"
docker build -t neko-dev-client -f base/Dockerfile --target client "${BASE}"
docker run --rm \
--user "$(id -u):$(id -g)" \
-v "${BASE}client/dist:/tmp/dist" \
neko-dev-client sh -c "rm -rf /tmp/dist/*; cp -r /src/dist/* /tmp/dist"
}
build_server() {
docker build -t neko-dev-server -f base/Dockerfile --target server "$BASE"
docker run --rm -v "$BASE"/server/bin:/tmp/bin neko-dev-server sh -c "rm -rf /tmp/bin/neko; cp /src/bin/neko /tmp/bin"
docker build -t neko-dev-server -f base/Dockerfile --target server "${BASE}"
docker run --rm \
--user "$(id -u):$(id -g)" \
-v "${BASE}server/bin:/tmp/bin" \
neko-dev-server sh -c "rm -rf /tmp/bin/neko; cp /src/bin/neko /tmp/bin"
}
build_base() {
docker build -t m1k1o/neko:base -f base/Dockerfile "$BASE"
build() {
if [ "$1" = "base" ]
then
# build base
docker build -t "${BUILD_IMAGE}:base" -f base/Dockerfile "${BASE}"
else
# build image
docker build -t "${BUILD_IMAGE}:$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:base" -f "$1/Dockerfile" "$1/"
fi
}
build_firefox() {
docker build -t m1k1o/neko:firefox -f firefox/Dockerfile firefox/
}
build_chromium() {
docker build -t m1k1o/neko:chromium -f chromium/Dockerfile chromium/
build_arm() {
if [ "$1" = "base" ]
then
# build ARM base
docker build -t "${BUILD_IMAGE}:arm-base" -f base/Dockerfile.arm "${BASE}"
elif [ -f "$1/Dockerfile.arm" ]
then
# build dedicated ARM image
docker build -t "${BUILD_IMAGE}:arm-$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:arm-base" -f "$1/Dockerfile.arm" "$1/"
else
# try to build ARM image with common Dockerfile
docker build -t "${BUILD_IMAGE}:arm-$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:arm-base" -f "$1/Dockerfile" "$1/"
fi
}
case $1 in
client) build_client;;
serve) build_server;;
base) build_base;;
firefox) build_firefox;;
chromium) build_chromium;;
*) echo "Unknown $1";;
server) build_server;;
# build arm- images
arm-*) build_arm "${1#arm-}";;
# build images
*) build "$1";;
esac

View File

@ -1,32 +1,26 @@
FROM m1k1o/neko:base
ARG SRC_URL="https://github.com/macchrome/linchrome/releases/download/v87.0.4280.88-r812852-portable-ungoogled-Lin64/ungoogled-chromium_87.0.4280.88_1.vaapi_linux.tar.xz"
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
#
# install custom chromium build from woolyss with support for hevc/x265
# install neko chromium
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends libatk1.0-0 libatk-bridge2.0-0 libatomic1 \
libcups2 libgtk-3-0 libnss3 libpci3 libxcomposite1 libxss1 openbox xz-utils; \
wget -O - /tmp/chromium.tar.xz "${SRC_URL}" | tar -xJf- -C /tmp; \
mv /tmp/ungoogled-chromium_* /usr/lib/chromium; \
apt-get install -y --no-install-recommends unzip chromium chromium-sandbox openbox; \
#
# make required changes for sandbox mode
mv /usr/lib/chromium/chrome_sandbox /usr/lib/chromium/chrome-sandbox; \
chown root:root /usr/lib/chromium/chrome-sandbox; \
chmod 4755 /usr/lib/chromium/chrome-sandbox; \
# install widevine module
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"; \
unzip -p /tmp/widevine.zip libwidevinecdm.so > /usr/lib/chromium/libwidevinecdm.so; \
chmod 644 /usr/lib/chromium/libwidevinecdm.so; \
rm /tmp/widevine.zip; \
#
# clean up
apt-get --purge autoremove -y xz-utils; \
apt-get --purge autoremove -y unzip; \
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy configuation files
COPY supervisord.conf /etc/neko/supervisord/chromium.conf
COPY preferences.json /usr/lib/chromium/master_preferences
COPY --chown=neko preferences.json /home/neko/.config/chromium/Default/Preferences
COPY policies.json /etc/chromium/policies/managed/policies.json
COPY openbox.xml /etc/neko/openbox.xml
#
# copy extensions and policy files
COPY extensions /usr/share/chromium/extensions

View File

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

View File

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

View File

@ -24,6 +24,10 @@
"file://*",
"chrome://policy"
],
"ExtensionInstallForcelist": [
"cjpalhdlnbpafiamejdnhcphjbkeiagm;https://clients2.google.com/service/update2/crx",
"fjoaledfpmneenckfbpdfhkmimnjocfa;https://clients2.google.com/service/update2/crx"
],
"ExtensionInstallWhitelist": [
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
"fjoaledfpmneenckfbpdfhkmimnjocfa"

View File

@ -1,6 +1,6 @@
[program:chromium]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/lib/chromium/chrome-wrapper --window-position=0,0 --display=%(ENV_DISPLAY)s --start-maximized --bwsi --test-type --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage
command=/usr/bin/chromium --window-position=0,0 --display=%(ENV_DISPLAY)s --user-data-dir=/home/neko/.config/chromium --no-first-run --start-maximized --bwsi --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage
autorestart=true
priority=800
user=%(ENV_USER)s

View File

@ -1,22 +1,35 @@
FROM m1k1o/neko:base
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
ARG SRC_URL="https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=en-US"
#
# install firefox-esr
# install firefox
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends openbox firefox-esr; \
apt-get install -y --no-install-recommends openbox \
xz-utils bzip2 libgtk-3-0 libdbus-glib-1-2; \
#
# install extensions
mkdir -p /usr/lib/firefox-esr/distribution/extensions; \
wget -O '/usr/lib/firefox-esr/distribution/extensions/uBlock0@raymondhill.net.xpi' https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi; \
# fetch latest release
wget -O /tmp/firefox-setup.tar.bz2 "${SRC_URL}"; \
mkdir /usr/lib/firefox; \
tar -xjf /tmp/firefox-setup.tar.bz2 -C /usr/lib; \
rm -f /tmp/firefox-setup.tar.bz2; \
ln -s /usr/lib/firefox/firefox /usr/bin/firefox; \
#
# create a profile directory
mkdir -p /home/neko/.mozilla/firefox/profile.default/extensions; \
chown -R neko:neko /home/neko/.mozilla/firefox/profile.default; \
#
# clean up
apt-get --purge autoremove -y xz-utils bzip2; \
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy configuation files
COPY supervisord.conf /etc/neko/supervisord/firefox.conf
COPY neko.js /usr/lib/firefox-esr/mozilla.cfg
COPY autoconfig.js /usr/lib/firefox-esr/defaults/pref/autoconfig.js
COPY policies.json /usr/lib/firefox-esr/distribution/policies.json
COPY neko.js /usr/lib/firefox/mozilla.cfg
COPY autoconfig.js /usr/lib/firefox/defaults/pref/autoconfig.js
COPY policies.json /usr/lib/firefox/distribution/policies.json
COPY --chown=neko profiles.ini /home/neko/.mozilla/firefox/profiles.ini
COPY openbox.xml /etc/neko/openbox.xml

View File

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

View File

@ -32,4 +32,6 @@ lockPref("devtools.theme", "dark");
lockPref("ui.systemUsesDarkTheme", 1);
lockPref("lightweightThemes.usedThemes","[]");
lockPref("lightweightThemes.selectedThemeID", "firefox-compact-dark@mozilla.org");
lockPref("extensions.activeThemeID", "firefox-compact-dark@mozilla.org");
lockPref("browser.theme.toolbar-theme", 0);
lockPref("browser.in-content.dark-mode", true);

View File

@ -56,6 +56,10 @@
"*": {
"installation_mode": "blocked"
},
"nordvpnproxy@nordvpn.com": {
"install_url": "https://addons.mozilla.org/firefox/downloads/latest/nordvpn-proxy-extension/latest.xpi",
"installation_mode": "force_installed"
},
"uBlock0@raymondhill.net": {
"install_url": "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi",
"installation_mode": "force_installed"
@ -73,7 +77,7 @@
"HardwareAcceleration": false,
"Homepage": {
"Additional": [],
"StartPage": "none"
"StartPage": "home"
},
"NewTabPage": true,
"NoDefaultBookmarks": true,

View File

@ -0,0 +1,8 @@
[General]
StartWithLastProfile=1
[Profile0]
Name=default
IsRelative=1
Path=profile.default
Default=1

View File

@ -1,10 +1,10 @@
[program:firefox-esr]
[program:firefox]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/lib/firefox-esr/firefox-esr --display=%(ENV_DISPLAY)s -setDefaultBrowser -width 1280 -height 720
command=/usr/bin/firefox --no-remote -P default --display=%(ENV_DISPLAY)s -setDefaultBrowser -width 1280 -height 720
autorestart=true
priority=800
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/firefox-esr.log
stdout_logfile=/var/log/neko/firefox.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true

View File

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

View File

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

View File

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

View File

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

View File

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

21
.m1k1o/rebuild-server Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash
if [ -f ".env.default" ]
then
export $(cat .env.default | sed 's/#.*//g' | xargs)
fi
if [ -f ".env" ]
then
export $(cat .env | sed 's/#.*//g' | xargs)
fi
# use -f to force rebuild
if [ "$(docker images -q neko_dev_server 2> /dev/null)" == "" ] || [ "$1" == "-f" ]; then
docker build -t neko_dev_server -f base/Dockerfile --target server ../
fi
docker run --rm -it \
-v "${PWD}/../server:/src" \
--entrypoint="go" \
neko_dev_server build -o "bin/neko" "cmd/neko/main.go"

29
.m1k1o/serve-client Executable file
View File

@ -0,0 +1,29 @@
#!/bin/bash
if [ -f ".env.default" ]
then
export $(cat .env.default | sed 's/#.*//g' | xargs)
fi
if [ -f ".env" ]
then
export $(cat .env | sed 's/#.*//g' | xargs)
fi
# use -i to install
if [ ! -d "${PWD}/../client/node_modules" ] || [ "$1" == "-i" ]; then
docker run --rm -it \
-v "${PWD}/../client:/app" \
--workdir="/app" \
--entrypoint="npm" \
node:14-buster-slim install
fi
docker run --rm -it \
-p "${CLIENT_PORT}:8080" \
-v "${PWD}/../client:/app" \
-e "VUE_APP_SERVER_PORT=${SERVER_PORT}" \
--workdir="/app" \
--entrypoint="npm" \
node:14-buster-slim run serve

32
.m1k1o/start-server Executable file
View File

@ -0,0 +1,32 @@
#!/bin/bash
if [ -f ".env.default" ]
then
export $(cat .env.default | sed 's/#.*//g' | xargs)
fi
if [ -f ".env" ]
then
export $(cat .env | sed 's/#.*//g' | xargs)
fi
BINARY_PATH="${PWD}/../server/bin/neko"
# use -r to rebuild
if [ ! -f "${BINARY_PATH}" ] || [ "$1" == "-r" ]; then
./rebuild-server
fi
docker run --rm -it \
--name "neko_dev" \
-p "${SERVER_PORT}:8080" \
-p "${SERVER_EPR}:${SERVER_EPR}/udp" \
-e "NEKO_SCREEN=1920x1080@60" \
-e "NEKO_EPR=${SERVER_EPR}" \
-e "NEKO_NAT1TO1=${SERVER_IP}" \
-e "NEKO_ICELITE=true" \
-e "NEKO_MAX_FPS=25" \
-v "${BINARY_PATH}:/usr/bin/neko" \
--shm-size=2G \
--cap-add SYS_ADMIN \
${BUILD_IMAGE}:${SERVER_TAG}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,43 @@
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
ARG API_URL="https://api.github.com/repos/macchrome/linchrome/releases/latest"
#
# install custom chromium build from woolyss with support for hevc/x265
RUN set -eux; apt-get update; \
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; \
#
# fetch latest release
SRC_URL="$(wget -O - "${API_URL}" 2>/dev/null | jq -r ".assets[] | .browser_download_url" | grep tar.xz)"; \
wget -O - /tmp/chromium.tar.xz "${SRC_URL}" | tar -xJf- -C /tmp; \
mv /tmp/ungoogled-chromium_* /usr/lib/chromium; \
#
# make required changes for sandbox mode
mv /usr/lib/chromium/chrome_sandbox /usr/lib/chromium/chrome-sandbox; \
chown root:root /usr/lib/chromium/chrome-sandbox; \
chmod 4755 /usr/lib/chromium/chrome-sandbox; \
#
# install widevine module
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"; \
unzip -p /tmp/widevine.zip libwidevinecdm.so > /usr/lib/chromium/libwidevinecdm.so; \
chmod 644 /usr/lib/chromium/libwidevinecdm.so; \
rm /tmp/widevine.zip; \
#
# 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/ungoogled-chromium.conf
COPY preferences.json /usr/lib/chromium/master_preferences
COPY policies.json /etc/chromium/policies/managed/policies.json
COPY openbox.xml /etc/neko/openbox.xml
#
# copy extensions and policy files
COPY extensions /usr/share/chromium/extensions

View File

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

View File

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

View File

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

View File

@ -0,0 +1,34 @@
{
"HomepageLocation": "",
"AutoFillEnabled": false,
"AutofillAddressEnabled": false,
"AutofillCreditCardEnabled": false,
"BrowserSignin": 0,
"DefaultNotificationsSetting": 2,
"DeveloperToolsAvailability": 2,
"EditBookmarksEnabled": false,
"FullscreenAllowed": true,
"IncognitoModeAvailability": 1,
"SyncDisabled": true,
"AutoplayAllowed": true,
"BrowserAddPersonEnabled": false,
"BrowserGuestModeEnabled": false,
"DefaultPopupsSetting": 2,
"DownloadRestrictions": 3,
"VideoCaptureAllowed": true,
"AllowFileSelectionDialogs": false,
"PromptForDownloadLocation": false,
"BookmarkBarEnabled": false,
"PasswordManagerEnabled": false,
"URLBlacklist": [
"file://*",
"chrome://policy"
],
"ExtensionInstallWhitelist": [
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
"fjoaledfpmneenckfbpdfhkmimnjocfa"
],
"ExtensionInstallBlacklist": [
"*"
]
}

View File

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

View File

@ -0,0 +1,21 @@
[program:ungoogled-chromium]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/lib/chromium/chrome-wrapper --window-position=0,0 --display=%(ENV_DISPLAY)s --start-maximized --bwsi --test-type --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage
autorestart=true
priority=800
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/ungoogled-chromium.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

18
.m1k1o/vlc/Dockerfile Normal file
View File

@ -0,0 +1,18 @@
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
#
# install vlc
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends openbox vlc; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
ENV VLC_MEDIA="/media"
#
# copy configuation files
COPY supervisord.conf /etc/neko/supervisord/vlc.conf
COPY openbox.xml /etc/neko/openbox.xml

763
.m1k1o/vlc/openbox.xml Normal file
View File

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

View File

@ -0,0 +1,21 @@
[program:vlc]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/vlc --x11-display=%(ENV_DISPLAY)s --no-qt-privacy-ask %(ENV_VLC_MEDIA)s
autorestart=true
priority=800
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/vlc.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true
[program:openbox]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
autorestart=true
priority=300
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/openbox.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true

View File

@ -0,0 +1,18 @@
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
#
# install vncviewer
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends openbox xtightvncviewer; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
ENV NEKO_VNC_URL=""
#
# copy configuation files
COPY supervisord.conf /etc/neko/supervisord/vncviewer.conf
COPY openbox.xml /etc/neko/openbox.xml

View File

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

View File

@ -0,0 +1,21 @@
[program:vncviewer]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/vncviewer -autopass -x11cursor -nojpeg -quality 9 -compresslevel 9 %(ENV_NEKO_VNC_URL)s
autorestart=true
priority=800
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/vncviewer.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

20
.m1k1o/xfce/Dockerfile Normal file
View File

@ -0,0 +1,20 @@
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
#
# install xfce
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends xfce4 xfce4-terminal sudo; \
#
# add user to sudoers
usermod -aG sudo neko; \
echo "neko:neko" | chpasswd; \
echo "%sudo ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers; \
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy configuation files
COPY supervisord.conf /etc/neko/supervisord/xfce.conf

View File

@ -0,0 +1,11 @@
[program:xfce]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/startxfce4
autorestart=true
priority=500
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/xfce.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true

391
README.md
View File

@ -1,12 +1,18 @@
<div align="center">
<a href="https://n.eko.moe/#/" ><img src="https://raw.githubusercontent.com/nurdism/neko/master/docs/_media/logo.png" width="450" height="auto"/></a>
<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"/>
</a>
<p align="center">
<img src="https://img.shields.io/github/v/release/nurdism/neko" alt="release">
<img src="https://img.shields.io/github/license/nurdism/neko" alt="license">
<img src="https://img.shields.io/docker/pulls/nurdism/neko" alt="pulls">
<img src="https://img.shields.io/github/issues/nurdism/neko" alt="issues">
<a href="https://discord.gg/3U6hWpC" ><img src="https://discordapp.com/api/guilds/665851821906067466/widget.png" alt="Chat on discord"><a/>
<a href="https://github.com/nurdism/neko/actions" ><img src="https://github.com/nurdism/neko/workflows/deploy/badge.svg" alt="build"><a/>
<img src="https://img.shields.io/github/v/release/m1k1o/neko" alt="release">
<img src="https://img.shields.io/github/license/m1k1o/neko" alt="license">
<img src="https://img.shields.io/docker/pulls/m1k1o/neko" alt="pulls">
<img src="https://img.shields.io/github/issues/m1k1o/neko" alt="issues">
<a href="https://discord.gg/3U6hWpC">
<img src="https://discordapp.com/api/guilds/665851821906067466/widget.png" alt="Chat on discord">
</a>
<a href="https://github.com/m1k1o/neko/actions">
<img src="https://github.com/m1k1o/neko/actions/workflows/build.yml/badge.svg" alt="build">
</a>
</p>
<br/>
<br/>
@ -16,65 +22,247 @@
</div>
# n.eko (m1k1o fork)
This app uses Web RTC to stream a desktop inside of a docker container. This is fork of https://github.com/nurdism/neko.
This app uses Web-RTC to stream a desktop inside a docker container. This is a fork of https://github.com/nurdism/neko.
For n.eko room management software, visit https://github.com/m1k1o/neko-rooms.
## Differences to original repository.
### New Features
- Clipboard button with text area - for browsers, that don't support clipboard syncing or for HTTP.
- Keyboard modifier state synchronization (Num Lock, Caps Lock, Scroll Lock) for each hosting.
- Added chromium ungoogled (with h265 support) an kept up to date by @whalehub.
- Clipboard button with text area - for browsers, that don't support clipboard syncing (Firefox, what a shame...) or for HTTP.
- Keyboard modifier state synchronization (Num-Lock, Caps-Lock, Scroll-Lock) for each hosting.
- Added chromium ungoogled (with h265 support) an kept up to date (by @whalehub).
- Added Picture in Picture button (only for watching screen, controlling not possible).
- Added RTMP broadcast. Enables broadcasting neko screen to local RTMP server, YouTube or Twitch.
- Added RTMP broadcast. Enables broadcasting n.eko screen to local RTMP server, YouTube or Twitch. Example: `rtmp://a.rtmp.youtube.com/live2/<your-streaming-key>`.
- Stereo sound (works properly only in Firefox host).
- Added limited support for some mobile browsers with `playsinline` attribute.
- Added `VIDEO_BITRATE` and `AUDIO_BITRATE` in kbit/s to control stream quality (in collaboration with @mbattista).
- Added `MAX_FPS`, where you can specify max WebRTC frame rate. When set to `0`, frame rate won't be capped, and you can enjoy your real `60fps` experience. Originally, it was constant at `25fps`.
- Invite links. You can invite people, and they don't need to enter passwords by themselves (and get confused about user accounts that do not exist). You can put your password in URL using `?pwd=<your-password>` and it will be automatically used when logging in.
- Added `/stats?pwd=<admin>` endpoint to get total active connections, host and members.
- Added `m1k1o/neko:vlc` tag, use VLC to watch local files together (by @mbattista).
- Added `m1k1o/neko:xfce` tag, as a non-video related showcase (by @mbattista).
- Added ARM-based images, for Raspberry Pi support (by @mbattista).
- Added simple language picker.
- Added `?usr=<display-name>` that will prefill username. This allows creating auto-join links.
- Added `?cast=1` that will hide all control and show only video.
- Shake keyboard icon if someone attempted to control when is nobody hosting.
- Support for password protected `NEKO_ICESERVERS` (by @mbattista).
- Added bunch of translations (🇸🇰, 🇪🇸, 🇸🇪, 🇳🇴, 🇫🇷) by various people.
- Added `m1k1o/neko:google-chrome` tag.
- Show red dot badge on sidebar toggle if there are new messages, and user can't see them.
- Added `m1k1o/neko:brave` tag.
### Bugs
- Fixed minor gst pipeline bug.
- Locked screen only for users, admins can still join.
- Fixed h264 pipelines bugs (by @mbattista).
- Fixed sessions manager thread safety by adding mutexes (caused panic in rare edge cases).
- Now when user gets kicked, he won't join as a ghost user again but will be logged out.
- **iOS compatibility!** Fixed a really strange CSS bug, which prevented iOS from loading the video.
- Proper disconnect only once with unsubscribing events. When Web-RTC fails, user won't be logged in without username again.
- Upgraded and fixed emojis to a new major version.
- Fixed bad `keymap -> keysym` translation to respect active modifiers (#45, with @mbattista).
- Respecting `NEKO_DEBUG` env variable.
- Full-screen support for iOS devices.
- Added `chrome-sandbox` to fix weird bug when chromium didn't start.
- Fixed keyboard mapping on macOS, when CMD could not be used for copy & paste.
- Fixed stop signal sent by supervisor to gracefully shut down neko server.
### Misc
- Custom docker workflow.
- Based on debian buster instead of stretch.
- Custom avatars without any 3rd party depenency.
- Based on Debian buster instead of stretch.
- Versions bumped: Go 16, Node.js 14 (by @mbattista).
- Custom avatars without any 3rd party dependency.
- Ignore duplicate notify bars.
- No pointer events for notify bars.
- Disable debug mode by default.
- Remove HTML tags from username.
- Upgraded `pion/webrtc` to v3 (by @mbattista).
- Added `requestFullscreen` compatibility for older browsers and iOS devices.
- Fixed small lags in video and improved video UX (by @mbattista).
- Added `m1k1o/neko:vncviewer` tag, use `NEKO_VNC_URL` to specify VNC target and use n.eko as a bridge.
- Ability to include n.eko as a component in another Vue.Js project (by @gbrian).
- Added HEALTHCHECK to Dockerfile.
- Arguments in broadcast pipeline are optional, not positional and can be repeated `{url} {device} {display}`.
- Chat messages are dense, when repeated, they are joined together.
- While IP address fetching is now proxy ignored.
- Start unmuted on reconnects.
- Switched to the latest Firefox version instead of esr.
- Fixed very fast scroll speed on macOS.
- Broadcast pipeline errors are reported to the user.
- On stopping server all websocket connections are going to be gracefully disconnected.
# Getting started & FAQ
Use following docker images:
Use the following docker images:
- `m1k1o/neko:latest` - for Firefox.
- `m1k1o/neko:chromium` - for Chromium Ungoogled (needs `--cap-add=SYS_ADMIN`).
- `m1k1o/neko:chromium` - for Chromium (needs `--cap-add=SYS_ADMIN`).
- `m1k1o/neko:google-chrome` - for Google Chrome (needs `--cap-add=SYS_ADMIN`).
- `m1k1o/neko:ungoogled-chromium` - for [Ungoogled Chromium](https://github.com/Eloston/ungoogled-chromium) (needs `--cap-add=SYS_ADMIN`) (by @whalehub).
- `m1k1o/neko:brave` - for [Brave Browser](https://brave.com) (needs `--cap-add=SYS_ADMIN`).
- `m1k1o/neko:tor-browser` - for Tor Browser.
- `m1k1o/neko:vncviewer` - for simple VNC viewer (specify `NEKO_VNC_URL` to your VNC target).
- `m1k1o/neko:vlc` - for VLC Video player (needs volume mounted to `/media` with local video files, or setting `VLC_MEDIA=/media` path).
- `m1k1o/neko:xfce` - for a shared desktop / installing shared software.
- `m1k1o/neko:base` - for custom base.
Networking:
For ARM-based devices (like Raspberry Pi, with GPU hardware acceleration):
- `m1k1o/neko:arm-firefox` - for Firefox.
- `m1k1o/neko:arm-chromium` - for Chromium.
- `m1k1o/neko:arm-base` - for custom arm based.
Images are built using GitHub actions on every push and on weekly basis to keep all browsers up-to-date,
### Networking:
- If you want to use n.eko in **external** network, you can omit `NEKO_NAT1TO1`. It will automatically get your Public IP.
- If you want to use n.eko in **internal** network, set `NEKO_NAT1TO1` to your local IP address (e.g. `NEKO_NAT1TO1: 192.168.1.20`)-
- Currently, it is not supported to supply multiple NAT addresses (see https://github.com/m1k1o/neko/issues/47).
Why so many ports?
- WebRTC needs UDP ports for each channel it creates towards users.
- Every user will need 2 UDP ports (for getting audio/video and sending mouse positions).
- You can freely limit number of UDP ports. But you can't map them to diferent ports.
- This **WONT** work: `32000-32100:52000-52100/udp`
### Why so many ports?
- WebRTC needs UDP ports in order to transfer Audio/Video towards user and Mouse/Keyboard events to the server in real time.
- If you don't set `NEKO_ICELITE=true`, every user will need 2 UDP ports.
- If you set `NEKO_ICELITE=true`, every user will need only 1 UDP port. It is **recommended** to use *ice-lite*.
- Do not forget, they are **UDP** ports, that configuration must be correct in your firewall/router/docker.
- You can freely limit number of UDP ports. But you can't map them to different ports.
- This **WON'T** work: `32000-32100:52000-52100/udp`
- You can change API port (8080).
- This **WILL** work: `3000:8080`
Behind reverse proxy?
- Nginx configuration: https://github.com/nurdism/neko/issues/111#issuecomment-742656957
- Apache configuration: https://github.com/nurdism/neko/blob/cad98a62a5bd7f1daf2c11980631bb14ba81a1f6/docs/apache-proxypass-config.md#example-apache-config
- Traefik configuration: https://github.com/m1k1o/neko-vpn/blob/a1b934515dcf597992a515d61d307c2450a11002/docker-compose.yml#L38-L43
### Behind reverse proxy?
Want to use VPN for your neko browsing?
<details>
<summary>Traefik2 configuration</summary>
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.services.neko-frontend.loadbalancer.server.port=8080"
- "traefik.http.routers.neko.rule=${TRAEFIK_RULE}"
- "traefik.http.routers.neko.entrypoints=${TRAEFIK_ENTRYPOINTS}"
- "traefik.http.routers.neko.tls.certresolver=${TRAEFIK_CERTRESOLVER}"
```
(by @m1k1o, [example](https://github.com/m1k1o/neko-vpn/blob/a1b934515dcf597992a515d61d307c2450a11002/docker-compose.yml#L38-L43))
</details>
<details>
<summary>Nginx configuration</summary>
```conf
server {
listen 443 ssl http2;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Protocol $scheme;
}
}
```
(by @GigaFyde, [source](https://github.com/nurdism/neko/issues/111#issuecomment-742656957))
</details>
<details>
<summary>Apache configuration</summary>
```xml
<VirtualHost *:80>
# The ServerName directive sets the request scheme, hostname and port that
# the server uses to identify itself. This is used when creating
# redirection URLs. In the context of virtual hosts, the ServerName
# specifies what hostname must appear in the request's Host: header to
# match this virtual host. For the default virtual host (this file) this
# value is not decisive as it is used as a last resort host regardless.
# However, you must set it for any further virtual host explicitly.
# Paths of those modules might vary across different distros.
LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so
LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so
LoadModule proxy_wstunnel_module /usr/lib/apache2/modules/mod_proxy_wstunnel.so
ServerName example.com
ServerAlias www.example.com
ProxyRequests Off
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/
RewriteEngine on
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule /ws(.*) "ws://localhost:8080/ws$1" [P,L]
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the loglevel for particular
# modules, e.g.
#LogLevel info ssl:warn
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to
# include a line for only one particular virtual host. For example the
# following line enables the CGI configuration for this host only
# after it has been globally disabled with "a2disconf".
#Include conf-available/serve-cgi-bin.conf
</VirtualHost>
```
(by @DarkReaper231, [source](https://github.com/nurdism/neko/blob/cad98a62a5bd7f1daf2c11980631bb14ba81a1f6/docs/apache-proxypass-config.md#example-apache-config))
</details>
<details>
<summary>Caddy configuration</summary>
```conf
https://example.com {
reverse_proxy localhost:8080 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
```
(by @ccallahan, [source](https://github.com/nurdism/neko/pull/125/commits/eb4ceda75423b0d960c8aea0240acf6d7a10fef4))
</details>
### Want to customize and install own add-ons, set custom bookmarks?
- 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/dev/.m1k1o/firefox/policies.json) file, modify and mount it as: ` -v '${PWD}/policies.json:/usr/share/firefox-esr/distribution/policies.json'`
- For Chromium, copy [this](https://github.com/m1k1o/neko/blob/dev/.m1k1o/chromium/policies.json) file, modify and mount it as: ` -v '${PWD}/policies.json:/etc/chromium/policies/managed/policies.json'`
### Want to use VPN for your n.eko browsing?
- Check this out: https://github.com/m1k1o/neko-vpn
Accounts:
- There are no accounts, display name (a.k.a. username) can be freely chosen. Only paword needs to match. Depeding on which password matches, visitor gets its privilege:
### Want to have multiple rooms on demand?
- Check this out: https://github.com/m1k1o/neko-rooms
### Want to use different Apps than Browser?
- Check this out: https://github.com/m1k1o/neko-apps
### Accounts:
- There are no accounts, display name (a.k.a. username) can be freely chosen. Only password needs to match. Depending on which password matches, the visitor gets its privilege:
- Anyone, who enters with `NEKO_PASSWORD` will be **user**.
- Anyone, who enters with `NEKO_PASSWORD_ADMIN` will be **admin**.
Screen size
### Screen size
- Only admins can change screen size.
- You can set default screen size, but this size **MUST** be one from list, that your server supports.
- You can set a default screen size, but this size **MUST** be one from the list, that your server supports.
- You will get this list in frontend, where you can choose from.
## Firefox
@ -97,7 +285,7 @@ services:
NEKO_NAT1TO1: <your-IP>
```
## Chromium Ungoogled
## Chromium
```yaml
version: "3.4"
@ -118,3 +306,142 @@ services:
NEKO_EPR: 52000-52100
NEKO_NAT1TO1: <your-IP>
```
## VLC
```yaml
version: "3.4"
services:
neko:
image: "m1k1o/neko:vlc"
restart: "unless-stopped"
shm_size: "2gb"
volumes:
- "<your-video-folder>:/video"
ports:
- "8080:8080"
- "52000-52100:52000-52100/udp"
cap_add:
- SYS_ADMIN
environment:
NEKO_SCREEN: '1920x1080@30'
NEKO_PASSWORD: neko
NEKO_PASSWORD_ADMIN: admin
NEKO_EPR: 52000-52100
NEKO_NAT1TO1: <your-IP>
```
## Raspberry Pi
Note! Since this pipeline is using H264, that enables GPU HW acceleration for Raspberry Pi, you are only able to connect from browsers supporting H264 for WebRTC. At the time of implementing, [Firefox does not support this](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#supported-foot-1).
```yaml
version: "3.4"
services:
neko:
image: "m1k1o/neko:arm-chromium"
restart: "unless-stopped"
# increase on rpi's with more then 1gb ram.
shm_size: "520mb"
ports:
- "8088:8080"
- "52000-52100:52000-52100/udp"
# this is important since we need a GPU for hardware acceleration alternatively mount the devices into the docker.
privileged: true
environment:
NEKO_SCREEN: '1280x720@30'
NEKO_PASSWORD: 'neko'
NEKO_PASSWORD_ADMIN: 'admin'
NEKO_EPR: 52000-52100
# optional: change target bitrate and framerate on this parameter.
NEKO_VIDEO: |
ximagesrc display-name=%s use-damage=0 show-pointer=true use-damage=false
! video/x-raw,framerate=30/1
! videoconvert
! queue
! video/x-raw,framerate=30/1,format=NV12
! v4l2h264enc extra-controls="controls,h264_profile=0,video_bitrate=1250000;"
! h264parse config-interval=3
! video/x-h264,profile=baseline,stream-format=byte-stream
```
## Not using docker?
You can execute `neko --help` to see available arguments. In [Dockerfile](https://github.com/m1k1o/neko/blob/dev/.m1k1o/base/Dockerfile) you can find required dependencies and install them manually.
## Mobile support
N.eko is now working on iOS and Android! Also, the UI screens have been fixed for small screens.
![mobile-screens](https://i.imgur.com/K9gfscU.png)
## Docker-Compose Environment Options
```code
NEKO_SCREEN:
- Resolution after startup. Only Admins can change this later.
- e.g. '1920x1080@30'
NEKO_PASSWORD:
- Password for the user login
- e.g. 'user_password'
NEKO_PASSWORD_ADMIN
- Password for the admin login
- e.g. 'admin_password'
NEKO_EPR:
- For WebRTC needed range of ports
- e.g. 52000-52100
NEKO_VP8:
- If vp8 should be used as video encoder for the stream (default encoder)
- e.g. 'true'
NEKO_VP9:
- 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:
- Bitrate of the video stream in kb/s
- e.g. 3500
NEKO_VIDEO:
- Makes it possible to create custom gstreamer pipelines. With this you could find the best quality for your CPU
- Installed are gstreamer1.0-plugins-base / gstreamer1.0-plugins-good / gstreamer1.0-plugins-bad / gstreamer1.0-plugins-ugly
- e.g. ' ximagesrc display-name=%s show-pointer=true use-damage=false ! video/x-raw,framerate=30/1 ! videoconvert ! queue ! video/x-raw,format=NV12 ! x264enc threads=4 bitrate=3500 key-int-max=60 vbv-buf-capacity=4000 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream '
NEKO_MAX_FPS:
- The resulting stream frames per seconds should be capped (0 for uncapped)
- e.g. 0
NEKO_OPUS:
- If opus should be used as audio encoder for the stream (default encoder)
- e.g. 'true'
NEKO_G722:
- If g722 should be used as audio encoder for the stream
- 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:
- Bitrate of the audio stream in kb/s
- e.g. 196
NEKO_CERT:
- Path to the SSL-Certificate
- e.g. '/certs/cert.pem'
NEKO_KEY:
- Path to the SSL-Certificate private key
- e.g. '/certs/key.pem'
NEKO_ICELITE:
- Use the ice lite protocol
- e.g. false
NEKO_ICESERVER:
- Describes a single STUN and TURN server that can be used by the ICEAgent to establish a connection with a peer (simple usage for server without authentication)
- e.g. 'stun:stun.l.google.com:19302'
NEKO_ICESERVERS:
- Describes multiple STUN and TURN server that can be used by the ICEAgent to establish a connection with a peer
- e.g. '[{"urls": ["turn:turn.example.com:19302", "stun:stun.example.com:19302"], "username": "name", "credential": "password"}, {"urls": ["stun:stun.example2.com:19302"]}]'
- [More information](https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer)
```
# How to contribute?
Navigate to [.m1k1o/README.md](.m1k1o/README.md) for further information.

View File

@ -15,53 +15,55 @@
"scripts": {
"serve": "vue-cli-service serve --mode development",
"build": "vue-cli-service build",
"build:lib": "vue-cli-service build --target lib --name neko-lib 'src/lib.ts'",
"build:emoji": "ts-node --files --project tools/tsconfig.json tools/emoji.ts",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.14.0",
"@fortawesome/fontawesome-free": "^5.15.4",
"animejs": "^3.2.0",
"axios": "^0.19.1",
"date-fns": "^2.16.1",
"emoji-datasource": "^5.0.1",
"emojilib": "^2.4.0",
"axios": "^0.21.1",
"date-fns": "^2.23.0",
"emoji-datasource": "^6.0.1",
"eventemitter3": "^4.0.7",
"resize-observer-polyfill": "^1.5.1",
"simple-markdown": "^0.7.2",
"sweetalert2": "^9.17.2",
"sweetalert2": "^10.15.7",
"typed-vuex": "^0.1.21",
"v-tooltip": "^2.0.3",
"vue": "^2.6.12",
"vue": "^2.6.14",
"vue-class-component": "^7.2.6",
"vue-clickaway": "^2.2.2",
"vue-context": "^5.2.0",
"vue-i18n": "^8.21.1",
"vue-i18n": "^8.25.0",
"vue-notification": "^1.3.20",
"vue-property-decorator": "^8.5.1",
"vue-property-decorator": "^9.1.2",
"vuex": "^3.5.1"
},
"devDependencies": {
"@types/animejs": "^3.1.2",
"@types/node": "^13.13.21",
"@types/animejs": "^3.1.4",
"@types/node": "^14.17.12",
"@types/vue": "^2.0.0",
"@types/vue-clickaway": "^2.2.0",
"@typescript-eslint/eslint-plugin": "^2.34.0",
"@typescript-eslint/parser": "^2.34.0",
"@typescript-eslint/eslint-plugin": "^4.30.0",
"@typescript-eslint/parser": "^4.30.0",
"@vue/cli-plugin-babel": "^4.5.6",
"@vue/cli-plugin-eslint": "^4.5.6",
"@vue/cli-plugin-typescript": "^4.5.6",
"@vue/cli-plugin-vuex": "^4.5.6",
"@vue/cli-service": "^4.5.6",
"@vue/cli-service": "^4.5.12",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^5.1.0",
"@vue/eslint-config-typescript": "^7.0.0",
"core-js": "^3.16.4",
"emojilib": "^3.0.4",
"eslint": "^6.8.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-vue": "^6.2.2",
"node-sass": "^4.14.1",
"prettier": "^2.1.2",
"sass-loader": "^8.0.0",
"ts-node": "^8.10.2",
"typescript": "^3.9.7",
"vue-template-compiler": "^2.6.12"
"eslint-plugin-prettier": "^3.4.1",
"eslint-plugin-vue": "^7.17.0",
"node-sass": "^5.0.0",
"prettier": "^2.3.2",
"sass-loader": "^10.1.1",
"ts-node": "^9.1.1",
"typescript": "^4.4.2",
"vue-template-compiler": "^2.6.14"
}
}

File diff suppressed because one or more lines are too long

View File

@ -5,18 +5,22 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>n.eko</title>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#19bd9c">
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png">
<link rel="manifest" href="site.webmanifest">
<link rel="mask-icon" href="safari-pinned-tab.svg" color="#19bd9c">
<meta name="msapplication-TileColor" content="#19bd9c">
<meta name="theme-color" content="#19bd9c">
<style> /* weird iOS bug, if this is not set right here, video just does not start */ .video-container { width: 100%; height: 100%; } </style>
</head>
<body>
<noscript>
<strong>We're sorry but n.eko doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="neko"></div>
<p>
A self hosted virtual browser (<a href="https://github.com/m1k1o/neko">m1k1o/neko</a>) that runs in docker.
</p>
</body>
</html>

View File

@ -5,20 +5,20 @@
</template>
<template v-else>
<main class="neko-main">
<div class="header-container">
<div v-if="!hideControls" class="header-container">
<neko-header />
</div>
<div class="video-container">
<neko-video ref="video" />
<neko-video ref="video" :hideControls="hideControls" @control-attempt="controlAttempt" />
</div>
<div class="room-container">
<div v-if="!hideControls" class="room-container">
<neko-members />
<div class="room-menu">
<div class="settings">
<neko-menu />
</div>
<div class="controls">
<neko-controls />
<neko-controls :shakeKbd="shakeKbd" />
</div>
<div class="emotes">
<neko-emotes />
@ -26,10 +26,16 @@
</div>
</div>
</main>
<neko-side v-if="side" />
<neko-side v-if="!hideControls && side" />
<neko-connect v-if="!connected" />
<neko-about v-if="about" />
<notifications group="neko" position="top left" :ignoreDuplicates="true" style="top: 50px;pointer-events: none" />
<notifications
v-if="!hideControls"
group="neko"
position="top left"
style="top: 50px; pointer-events: none"
:ignoreDuplicates="true"
/>
</template>
</div>
</template>
@ -109,31 +115,35 @@
}
@media only screen and (max-width: 600px) {
#neko {
&.expanded {
.neko-main {
transform: translateX(-$side-width);
}
.neko-menu {
transform: translateX(-$side-width);
#neko.expanded {
.neko-main {
transform: translateX(calc(-100% + 65px));
video {
display: none;
}
}
.neko-menu {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 65px;
width: calc(100% - 65px);
}
}
}
@media only screen and (max-width: 768px) {
#neko {
.neko-main {
.room-container {
display: none;
}
}
#neko .neko-main .room-container {
display: none;
}
}
</style>
<script lang="ts">
import { Vue, Component, Ref } from 'vue-property-decorator'
import { Vue, Component, Ref, Watch } from 'vue-property-decorator'
import Connect from '~/components/connect.vue'
import Video from '~/components/video.vue'
@ -164,6 +174,25 @@
export default class extends Vue {
@Ref('video') video!: Video
shakeKbd = false
get hideControls() {
return !!new URL(location.href).searchParams.get('cast')
}
@Watch('hideControls', { immediate: true })
onHideControls() {
this.$accessor.video.setMuted(false)
this.$accessor.settings.setSound(false)
}
controlAttempt() {
if (this.shakeKbd || this.$accessor.remote.hosted) return
this.shakeKbd = true
window.setTimeout(() => (this.shakeKbd = false), 5000)
}
get about() {
return this.$accessor.client.about
}

File diff suppressed because it is too large Load Diff

View File

@ -143,23 +143,27 @@
return this.$accessor.client.about_page
}
async Load() {
this.loading = true
try {
const res = await this.$http.get<string>('https://raw.githubusercontent.com/m1k1o/neko/dev/README.md')
const res2 = await this.$http.post('https://api.github.com/markdown', {
text: res.data,
mode: 'gfm',
context: 'github/gollum',
})
this.$accessor.client.setAbout(res2.data)
} catch (err: any) {
console.error(err)
} finally {
this.loading = false
}
}
mounted() {
if (this.about === '') {
this.loading = true
this.$http
.get<string>('https://raw.githubusercontent.com/nurdism/neko/master/docs/README.md')
.then((res) => {
return this.$http.post('https://api.github.com/markdown', {
text: res.data,
mode: 'gfm',
context: 'github/gollum',
})
})
.then((res) => {
this.$accessor.client.setAbout(res.data)
this.loading = false
})
.catch((err) => console.error(err))
this.Load()
}
}

View File

@ -2,13 +2,16 @@
<!--
<img :src="`https://ui-avatars.com/api/?name=${seed}&size=${size}`" />
-->
<div class="avatar" :style="{
width: size + 'px',
height: size + 'px',
lineHeight: size + 'px',
fontSize: (size/2) + 'px',
backgroundColor: Background(seed),
}">
<div
class="avatar"
:style="{
width: size + 'px',
height: size + 'px',
lineHeight: size + 'px',
fontSize: size / 2 + 'px',
backgroundColor: Background(seed),
}"
>
{{ seed.substring(0, 2).toUpperCase() }}
</div>
</template>
@ -32,21 +35,24 @@
name: 'neko-avatar',
})
export default class extends Vue {
@Prop(String) readonly seed: string | undefined;
@Prop(Number) readonly size: number | undefined;
@Prop(String) readonly seed: string | undefined
@Prop(Number) readonly size: number | undefined
Background(seed: string) {
let a = 0, b = 0, c = 0;
for(let i = 0; i < seed.length; i++) {
a += seed.charCodeAt(i) * 3;
b += seed.charCodeAt(i) * 5;
c += seed.charCodeAt(i) * 7;
let a = 0,
b = 0,
c = 0
for (let i = 0; i < seed.length; i++) {
a += seed.charCodeAt(i) * 3
b += seed.charCodeAt(i) * 5
c += seed.charCodeAt(i) * 7
}
let x = Math.floor(128 + (a % 128));
let y = Math.floor(128 + (b % 128));
let z = Math.floor(128 + (c % 128));
return "rgb(" + x + "," + y + "," + z + ")";
let x = Math.floor(128 + (a % 128))
let y = Math.floor(128 + (b % 128))
let z = Math.floor(128 + (c % 128))
return 'rgb(' + x + ',' + y + ',' + z + ')'
}
}
</script>

View File

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

View File

@ -1,14 +1,6 @@
<template>
<div
class="clipboard"
v-if="opened"
@click="$event.stopPropagation()"
>
<textarea
ref="textarea"
v-model="clipboard"
@focus="$event.target.select()"
/>
<div class="clipboard" v-if="opened" @click="$event.stopPropagation()">
<textarea ref="textarea" v-model="clipboard" @focus="$event.target.select()" />
</div>
</template>
@ -23,7 +15,8 @@
bottom: 10px;
right: 10px;
&, textarea {
&,
textarea {
max-width: 320px;
width: 100%;
max-height: 120px;
@ -52,7 +45,7 @@
@Ref('textarea') readonly _textarea!: HTMLTextAreaElement
private opened: boolean = false
private typing: any = null
private typing?: number
get clipboard() {
return this.$accessor.remote.clipboard
@ -63,15 +56,16 @@
if (this.typing) {
clearTimeout(this.typing)
this.typing = undefined
}
this.typing = setTimeout(() => this.$accessor.remote.sendClipboard(this.clipboard), 500)
this.typing = window.setTimeout(() => this.$accessor.remote.sendClipboard(this.clipboard), 500)
}
open() {
this.opened = true
document.body.addEventListener('click', this.close)
setTimeout(() => this._textarea.focus(), 0)
window.setTimeout(() => this._textarea.focus(), 0)
}
close() {

View File

@ -1,14 +1,15 @@
<template>
<div class="connect">
<div class="window">
<div class="logo">
<div class="logo" title="About n.eko" @click.stop.prevent="about">
<img src="@/assets/images/logo.svg" alt="n.eko" />
<span><b>n</b>.eko</span>
</div>
<form class="message" v-if="!connecting" @submit.stop.prevent="connect">
<span>{{ $t('connect.title') }}</span>
<span v-if="!autoPassword">{{ $t('connect.login_title') }}</span>
<span v-else>{{ $t('connect.invitation_title') }}</span>
<input type="text" :placeholder="$t('connect.displayname')" v-model="displayname" />
<input type="password" :placeholder="$t('connect.password')" v-model="password" />
<input type="password" :placeholder="$t('connect.password')" v-model="password" v-if="!autoPassword" />
<button type="submit" @click.stop.prevent="login">
{{ $t('connect.connect') }}
</button>
@ -46,6 +47,7 @@
flex-direction: row;
justify-content: center;
align-items: center;
cursor: pointer;
img {
height: 90px;
@ -150,12 +152,30 @@
@Component({ name: 'neko-connect' })
export default class extends Vue {
private displayname = ''
private password = ''
private autoPassword: string | null = new URL(location.href).searchParams.get('pwd')
private displayname: string = ''
private password: string = ''
mounted() {
if (this.$accessor.displayname !== '' && this.$accessor.password !== '') {
this.$accessor.login({ displayname: this.$accessor.displayname, password: this.$accessor.password })
// auto-password fill
let password = this.$accessor.password
if (this.autoPassword !== null) {
this.removeUrlParam('pwd')
password = this.autoPassword
}
// auto-user fill
let displayname = this.$accessor.displayname
const usr = new URL(location.href).searchParams.get('usr')
if (usr) {
this.removeUrlParam('usr')
displayname = this.$accessor.displayname || usr
}
if (displayname !== '' && password !== '') {
this.$accessor.login({ displayname, password })
this.autoPassword = null
}
}
@ -163,8 +183,48 @@
return this.$accessor.connecting
}
removeUrlParam(param: string) {
let url = document.location.href
let urlparts = url.split('?')
if (urlparts.length >= 2) {
let urlBase = urlparts.shift()
let queryString = urlparts.join('?')
let prefix = encodeURIComponent(param) + '='
let pars = queryString.split(/[&;]/g)
for (let i = pars.length; i-- > 0; ) {
if (pars[i].lastIndexOf(prefix, 0) !== -1) {
pars.splice(i, 1)
}
}
url = urlBase + (pars.length > 0 ? '?' + pars.join('&') : '')
window.history.pushState('', document.title, url)
}
}
login() {
this.$accessor.login({ displayname: this.displayname, password: this.password })
let password = this.password
if (this.autoPassword !== null) {
password = this.autoPassword
}
if (this.displayname == '') {
this.$swal({
title: this.$t('connect.error') as string,
text: this.$t('connect.empty_displayname') as string,
icon: 'error',
})
return
}
this.$accessor.login({ displayname: this.displayname, password })
this.autoPassword = null
}
about() {
this.$accessor.client.toggleAbout()
}
}
</script>

View File

@ -39,10 +39,10 @@
<template v-if="admin && !child.data.member.admin">
<li class="seperator" />
<li>
<span @click="kick(child.data.member)" style="color: #f04747;">{{ $t('context.kick') }}</span>
<span @click="kick(child.data.member)" style="color: #f04747">{{ $t('context.kick') }}</span>
</li>
<li>
<span @click="ban(child.data.member)" style="color: #f04747;">{{ $t('context.ban') }}</span>
<span @click="ban(child.data.member)" style="color: #f04747">{{ $t('context.ban') }}</span>
</li>
</template>
</template>
@ -165,64 +165,64 @@
this.context.open(event, data)
}
kick(member: Member) {
this.$swal({
async kick(member: Member) {
const value = await this.$swal({
title: this.$t('context.confirm.kick_title', { name: member.displayname }) as string,
text: this.$t('context.confirm.kick_text', { name: member.displayname }) as string,
icon: 'warning',
showCancelButton: true,
confirmButtonText: this.$t('context.confirm.button_yes') as string,
cancelButtonText: this.$t('context.confirm.button_cancel') as string,
}).then(({ value }) => {
if (value) {
this.$accessor.user.kick(member)
}
})
if (value) {
this.$accessor.user.kick(member)
}
}
ban(member: Member) {
this.$swal({
async ban(member: Member) {
const value = await this.$swal({
title: this.$t('context.confirm.ban_title', { name: member.displayname }) as string,
text: this.$t('context.confirm.ban_text', { name: member.displayname }) as string,
icon: 'warning',
showCancelButton: true,
confirmButtonText: this.$t('context.confirm.button_yes') as string,
cancelButtonText: this.$t('context.confirm.button_cancel') as string,
}).then(({ value }) => {
if (value) {
this.$accessor.user.ban(member)
}
})
if (value) {
this.$accessor.user.ban(member)
}
}
mute(member: Member) {
this.$swal({
async mute(member: Member) {
const value = await this.$swal({
title: this.$t('context.confirm.mute_title', { name: member.displayname }) as string,
text: this.$t('context.confirm.mute_text', { name: member.displayname }) as string,
icon: 'warning',
showCancelButton: true,
confirmButtonText: this.$t('context.confirm.button_yes') as string,
cancelButtonText: this.$t('context.confirm.button_cancel') as string,
}).then(({ value }) => {
if (value) {
this.$accessor.user.mute(member)
}
})
if (value) {
this.$accessor.user.mute(member)
}
}
unmute(member: Member) {
this.$swal({
async unmute(member: Member) {
const value = await this.$swal({
title: this.$t('context.confirm.unmute_title', { name: member.displayname }) as string,
text: this.$t('context.confirm.unmute_text', { name: member.displayname }) as string,
icon: 'warning',
showCancelButton: true,
confirmButtonText: this.$t('context.confirm.button_yes') as string,
cancelButtonText: this.$t('context.confirm.button_cancel') as string,
}).then(({ value }) => {
if (value) {
this.$accessor.user.unmute(member)
}
})
if (value) {
this.$accessor.user.unmute(member)
}
}
adminRelease(member: Member) {

View File

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

View File

@ -1,9 +1,9 @@
<template>
<div class="header">
<div class="neko">
<a href="https://github.com/m1k1o/neko" title="Github repository" target="_blank" class="neko">
<img src="@/assets/images/logo.svg" alt="n.eko" />
<span><b>n</b>.eko</span>
</div>
</a>
<ul class="menu">
<li>
<i
@ -15,8 +15,8 @@
? $t('room.unlock')
: $t('room.lock')
: locked
? $t('room.unlocked')
: $t('room.locked'),
? $t('room.locked')
: $t('room.unlocked'),
placement: 'bottom',
offset: 5,
boundariesElement: 'body',
@ -25,6 +25,7 @@
/>
</li>
<li>
<span v-if="showBadge" class="badge">&bull;</span>
<i class="fas fa-bars toggle" @click="toggleMenu" />
</li>
</ul>
@ -45,6 +46,8 @@
align-items: center;
width: 150px;
margin-left: 20px;
color: $text-normal;
text-decoration: none;
img {
display: block;
@ -94,6 +97,40 @@
.toggle {
background: $background-primary;
}
.badge {
position: absolute;
background: red;
font-weight: bold;
font-size: 1.25em;
border-radius: 50%;
width: 20px;
height: 20px;
text-align: center;
line-height: 20px;
pointer-events: none;
transform: translate(-50%, -25%) scale(1);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 1);
animation: badger-pulse 2s infinite;
}
@keyframes badger-pulse {
0% {
transform: translate(-50%, -25%) scale(0.85);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.7);
}
70% {
transform: translate(-50%, -25%) scale(1);
box-shadow: 0 0 0 10px rgba(0, 0, 0, 0);
}
100% {
transform: translate(-50%, -25%) scale(0.85);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
}
}
}
}
}
@ -112,8 +149,22 @@
return this.$accessor.locked
}
get side() {
return this.$accessor.client.side
}
get texts() {
return this.$accessor.chat.texts
}
get showBadge() {
return !this.side && this.readTexts != this.texts
}
readTexts: number = 0
toggleMenu() {
this.$accessor.client.toggleSide()
this.readTexts = this.texts
}
toggleLock() {

View File

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

View File

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

View File

@ -44,19 +44,23 @@
<span />
</label>
</li>
<template v-if="admin">
<li>
<span>{{ $t('setting.broadcast_is_active') }}</span>
<label class="switch">
<input type="checkbox" v-model="broadcast_is_active" />
<span />
</label>
</li>
<li>
<span>{{ $t('setting.broadcast_url') }}</span>
<input v-model="broadcast_url" :disabled="broadcast_is_active" class="input">
</li>
</template>
<li class="broadcast" v-if="admin">
<div>
<span>{{ $t('setting.broadcast_title') }}</span>
<button v-if="!broadcast_is_active" @click.stop.prevent="$accessor.settings.broadcastCreate(broadcast_url)">
<i class="fas fa-play"></i>
</button>
<button v-else @click.stop.prevent="$accessor.settings.broadcastDestroy()" class="btn-red">
<i class="fas fa-stop"></i>
</button>
</div>
<input
v-model="broadcast_url"
:disabled="broadcast_is_active"
class="input"
placeholder="rtmp://a.rtmp.youtube.com/live2/<stream-key>"
/>
</li>
<li v-if="connected">
<button @click.stop.prevent="logout">{{ $t('logout') }}</button>
</li>
@ -266,6 +270,34 @@
background: none;
}
}
&.broadcast {
display: flex;
flex-direction: column;
div {
margin-bottom: 10px;
display: flex;
justify-content: space-between;
button {
flex-shrink: 1;
width: auto !important;
margin: 0;
padding: 0 10px;
&.btn-red {
background: #a62626;
}
}
}
.input {
text-align: left;
width: auto !important;
margin: 0;
}
}
}
}
}
@ -276,7 +308,7 @@
@Component({ name: 'neko-settings' })
export default class extends Vue {
private broadcast_url: string = '';
private broadcast_url: string = ''
get admin() {
return this.$accessor.user.admin
@ -338,14 +370,6 @@
return this.$accessor.settings.broadcast_is_active
}
set broadcast_is_active(value: boolean) {
if (value) {
this.$accessor.settings.broadcastCreate(this.broadcast_url)
} else {
this.$accessor.settings.broadcastDestroy()
}
}
get broadcast_url_remote() {
return this.$accessor.settings.broadcast_url
}

View File

@ -2,7 +2,7 @@
<div ref="component" class="video">
<div ref="player" class="player">
<div ref="container" class="player-container">
<video ref="video" />
<video ref="video" playsinline />
<div class="emotes">
<template v-for="(emote, index) in emotes">
<neko-emote :id="index" :key="index" />
@ -26,7 +26,7 @@
</div>
<div ref="aspect" class="player-aspect" />
</div>
<ul v-if="!fullscreen" class="video-menu top">
<ul v-if="!fullscreen && !hideControls" class="video-menu top">
<li><i @click.stop.prevent="requestFullscreen" class="fas fa-expand"></i></li>
<li v-if="admin"><i @click.stop.prevent="onResolution" class="fas fa-desktop"></i></li>
<li class="request-control">
@ -36,10 +36,13 @@
/>
</li>
</ul>
<ul v-if="!fullscreen" class="video-menu bottom">
<li v-if="hosting && !clipboard_available"><i @click.stop.prevent="onClipboard" class="fas fa-clipboard"></i></li>
<ul v-if="!fullscreen && !hideControls" class="video-menu bottom">
<li v-if="hosting && (!clipboard_read_available || !clipboard_write_available)">
<i @click.stop.prevent="onClipboard" class="fas fa-clipboard"></i>
</li>
<li>
<i
v-if="pip_available"
@click.stop.prevent="requestPictureInPicture"
v-tooltip="{ content: 'Picture-in-Picture', placement: 'left', offset: 5, boundariesElement: 'body' }"
class="fas fa-external-link-alt"
@ -47,7 +50,7 @@
</li>
</ul>
<neko-resolution ref="resolution" v-if="admin" />
<neko-clipboard ref="clipboard" v-if="hosting && !clipboard_available" />
<neko-clipboard ref="clipboard" v-if="hosting && (!clipboard_read_available || !clipboard_write_available)" />
</div>
</div>
</template>
@ -180,15 +183,18 @@
</style>
<script lang="ts">
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
import { Component, Ref, Watch, Vue, Prop } from 'vue-property-decorator'
import ResizeObserver from 'resize-observer-polyfill'
import Emote from './emote.vue'
import Resolution from './resolution.vue'
import Clipboard from './clipboard.vue'
// @ts-ignore
import GuacamoleKeyboard from '~/utils/guacamole-keyboard.ts'
const WHEEL_LINE_HEIGHT = 19
@Component({
name: 'neko-video',
components: {
@ -207,10 +213,13 @@
@Ref('resolution') readonly _resolution!: any
@Ref('clipboard') readonly _clipboard!: any
@Prop(Boolean) readonly hideControls = false
private keyboard = GuacamoleKeyboard()
private observer = new ResizeObserver(this.onResise.bind(this))
private focused = false
private fullscreen = false
private startsMuted = true
get admin() {
return this.$accessor.user.admin
@ -272,8 +281,17 @@
return this.$accessor.settings.scroll_invert
}
get clipboard_available() {
return 'clipboard' in navigator
get pip_available() {
//@ts-ignore
return typeof document.createElement('video').requestPictureInPicture === 'function'
}
get clipboard_read_available() {
return 'clipboard' in navigator && typeof navigator.clipboard.readText === 'function'
}
get clipboard_write_available() {
return 'clipboard' in navigator && typeof navigator.clipboard.writeText === 'function'
}
get clipboard() {
@ -321,6 +339,7 @@
onMutedChanged(muted: boolean) {
if (this._video) {
this._video.muted = muted
this.startsMuted = muted
}
}
@ -349,7 +368,7 @@
@Watch('clipboard')
onClipboardChanged(clipboard: string) {
if (this.clipboard_available && typeof navigator.clipboard.writeText === 'function') {
if (this.clipboard_write_available) {
navigator.clipboard.writeText(clipboard).catch(console.error)
}
}
@ -370,7 +389,7 @@
this._video.addEventListener('canplaythrough', () => {
this.$accessor.video.setPlayable(true)
if (this.autoplay) {
if (!document.hasFocus() || !this.$accessor.active) {
if (this.startsMuted && (!document.hasFocus() || !this.$accessor.active)) {
this.$accessor.video.setMuted(true)
this._video.muted = true
}
@ -398,7 +417,7 @@
return true
}
this.$client.sendData('keydown', { key })
this.$client.sendData('keydown', { key: this.keyMap(key) })
return false
}
this.keyboard.onkeyup = (key: number) => {
@ -406,7 +425,7 @@
return
}
this.$client.sendData('keyup', { key })
this.$client.sendData('keyup', { key: this.keyMap(key) })
}
this.keyboard.listenTo(this._overlay)
}
@ -418,19 +437,60 @@
/* Guacamole Keyboard does not provide destroy functions */
}
play() {
get hasMacOSKbd() {
return /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)
}
KeyTable = {
XK_ISO_Level3_Shift: 0xfe03, // AltGr
XK_Mode_switch: 0xff7e, // Character set switch
XK_Control_L: 0xffe3, // Left control
XK_Control_R: 0xffe4, // Right control
XK_Meta_L: 0xffe7, // Left meta
XK_Meta_R: 0xffe8, // Right meta
XK_Alt_L: 0xffe9, // Left alt
XK_Alt_R: 0xffea, // Right alt
XK_Super_L: 0xffeb, // Left super
XK_Super_R: 0xffec, // Right super
}
keyMap(key: number): number {
// Alt behaves more like AltGraph on macOS, so shuffle the
// keys around a bit to make things more sane for the remote
// server. This method is used by noVNC, RealVNC and TigerVNC
// (and possibly others).
if (this.hasMacOSKbd) {
switch (key) {
case this.KeyTable.XK_Meta_L:
key = this.KeyTable.XK_Control_L
break
case this.KeyTable.XK_Super_L:
key = this.KeyTable.XK_Alt_L
break
case this.KeyTable.XK_Super_R:
key = this.KeyTable.XK_Super_L
break
case this.KeyTable.XK_Alt_L:
key = this.KeyTable.XK_Mode_switch
break
case this.KeyTable.XK_Alt_R:
key = this.KeyTable.XK_ISO_Level3_Shift
break
}
}
return key
}
async play() {
if (!this._video.paused || !this.playable) {
return
}
try {
this._video
.play()
.then(() => {
this.onResise()
})
.catch((err) => this.$log.error)
} catch (err) {
await this._video.play()
this.onResise()
} catch (err: any) {
this.$log.error(err)
}
}
@ -463,9 +523,40 @@
this.$accessor.remote.toggle()
}
_elementRequestFullscreen(el: HTMLElement) {
if (typeof el.requestFullscreen === 'function') {
el.requestFullscreen()
//@ts-ignore
} else if (typeof el.webkitRequestFullscreen === 'function') {
//@ts-ignore
el.webkitRequestFullscreen()
//@ts-ignore
} else if (typeof el.webkitEnterFullscreen === 'function') {
//@ts-ignore
el.webkitEnterFullscreen()
//@ts-ignore
} else if (typeof el.msRequestFullScreen === 'function') {
//@ts-ignore
el.msRequestFullScreen()
} else {
return false
}
return true
}
requestFullscreen() {
this._player.requestFullscreen()
this.onResise()
// try to fullscreen player element
if (this._elementRequestFullscreen(this._player)) {
this.onResise()
return
}
// fallback to fullscreen video itself (on mobile devices)
if (this._elementRequestFullscreen(this._video)) {
this.onResise()
return
}
}
requestPictureInPicture() {
@ -474,21 +565,21 @@
this.onResise()
}
onFocus() {
async onFocus() {
if (!document.hasFocus() || !this.$accessor.active) {
return
}
if (this.hosting && this.clipboard_available && typeof navigator.clipboard.readText === 'function') {
navigator.clipboard
.readText()
.then((text) => {
if (this.clipboard !== text) {
this.$accessor.remote.setClipboard(text)
this.$accessor.remote.sendClipboard(text)
}
})
.catch(this.$log.error)
if (this.hosting && this.clipboard_read_available) {
try {
const text = await navigator.clipboard.readText()
if (this.clipboard !== text) {
this.$accessor.remote.setClipboard(text)
this.$accessor.remote.sendClipboard(text)
}
} catch (err: any) {
this.$log.error(err)
}
}
}
@ -501,6 +592,7 @@
})
}
wheelThrottle = false
onWheel(e: WheelEvent) {
if (!this.hosting || this.locked) {
return
@ -510,6 +602,16 @@
let x = e.deltaX
let y = e.deltaY
// Pixel units unless it's non-zero.
// Note that if deltamode is line or page won't matter since we aren't
// sending the mouse wheel delta to the server anyway.
// The difference between pixel and line can be important however since
// we have a threshold that can be smaller than the line height.
if (e.deltaMode !== 0) {
x *= WHEEL_LINE_HEIGHT
y *= WHEEL_LINE_HEIGHT
}
if (this.scroll_invert) {
x = x * -1
y = y * -1
@ -518,13 +620,25 @@
x = Math.min(Math.max(x, -this.scroll), this.scroll)
y = Math.min(Math.max(y, -this.scroll), this.scroll)
this.$client.sendData('wheel', { x, y })
if (!this.wheelThrottle) {
this.wheelThrottle = true
this.$client.sendData('wheel', { x, y })
window.setTimeout(() => {
this.wheelThrottle = false
}, 100)
}
}
onMouseDown(e: MouseEvent) {
if (!this.hosting) {
this.$emit('control-attempt', e)
}
if (!this.hosting || this.locked) {
return
}
this.onMousePos(e)
this.$client.sendData('mousedown', { key: e.button + 1 })
}
@ -533,6 +647,7 @@
if (!this.hosting || this.locked) {
return
}
this.onMousePos(e)
this.$client.sendData('mouseup', { key: e.button + 1 })
}
@ -546,11 +661,11 @@
}
onMouseEnter(e: MouseEvent) {
if(this.hosting) {
if (this.hosting) {
this.$accessor.remote.syncKeyboardModifierState({
capsLock: e.getModifierState("CapsLock"),
numLock: e.getModifierState("NumLock"),
scrollLock: e.getModifierState("ScrollLock"),
capsLock: e.getModifierState('CapsLock'),
numLock: e.getModifierState('NumLock'),
scrollLock: e.getModifierState('ScrollLock'),
})
}
@ -560,11 +675,11 @@
}
onMouseLeave(e: MouseEvent) {
if(this.hosting) {
if (this.hosting) {
this.$accessor.remote.setKeyboardModifierState({
capsLock: e.getModifierState("CapsLock"),
numLock: e.getModifierState("NumLock"),
scrollLock: e.getModifierState("ScrollLock"),
capsLock: e.getModifierState('CapsLock'),
numLock: e.getModifierState('NumLock'),
scrollLock: e.getModifierState('ScrollLock'),
})
}

84
client/src/lib.ts Normal file
View File

@ -0,0 +1,84 @@
import { accessor as neko } from './store'
import { PluginObject } from 'vue'
// Plugins
import Logger from './plugins/log'
import Client from './plugins/neko'
import Axios from './plugins/axios'
import Swal from './plugins/swal'
import Anime from './plugins/anime'
import { i18n } from './plugins/i18n'
// Components
import Connect from '~/components/connect.vue'
import Video from '~/components/video.vue'
import Menu from '~/components/menu.vue'
import Side from '~/components/side.vue'
import Controls from '~/components/controls.vue'
import Members from '~/components/members.vue'
import Emotes from '~/components/emotes.vue'
import About from '~/components/about.vue'
import Header from '~/components/header.vue'
import Chat from '~/components/chat.vue'
import Clipboard from '~/components/clipboard.vue'
import Emoji from '~/components/emoji.vue'
import Emote from '~/components/emote.vue'
import Context from '~/components/context.vue'
import Markdown from '~/components/markdown'
import Avatar from '~/components/avatar.vue'
// Vue
import Vue from 'vue'
import ToolTip from 'v-tooltip'
Vue.use(ToolTip)
const exportMixin = {
computed: {
$accessor() {
return neko
},
$client () {
return window.$client
}
},
}
const plugini18n: PluginObject<undefined> = {
install(Vue) {
Vue.prototype.i18n = i18n
Vue.prototype.$t = i18n.t.bind(i18n)
Vue.prototype.$te = i18n.te.bind(i18n)
},
}
function extend (component: any) {
return component
.use(plugini18n)
.use(Logger)
.use(Axios)
.use(Swal)
.use(Anime)
.use(Client)
.extend(exportMixin)
}
export const NekoConnect = extend(Connect)
export const NekoVideo = extend(Video)
export const NekoMenu = extend(Menu)
export const NekoSide = extend(Side)
export const NekoControls = extend(Controls)
export const NekoMembers = extend(Members)
export const NekoEmotes = extend(Emotes)
export const NekoAbout = extend(About)
export const NekoHeader = extend(Header)
export const NekoChat = extend(Chat)
export const NekoClipboard = extend(Clipboard)
export const NekoEmoji = extend(Emoji)
export const NekoEmote = extend(Emote)
export const NekoMarkdown = extend(Markdown)
export const NekoContext = extend(Context)
export const NekoAvatar = extend(Avatar)
neko.initialise()
export default neko

View File

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

103
client/src/locale/es-sp.ts Normal file
View File

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

103
client/src/locale/fr-fr.ts Normal file
View File

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

View File

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

103
client/src/locale/nb-no.ts Normal file
View File

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

100
client/src/locale/sk-sk.ts Normal file
View File

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

103
client/src/locale/sv-se.ts Normal file
View File

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

View File

@ -2,7 +2,7 @@ import EventEmitter from 'eventemitter3'
import { OPCODE } from './data'
import { EVENT, WebSocketEvents } from './events'
import { WebSocketMessages, WebSocketPayloads, SignalProvidePayload } from './messages'
import { WebSocketMessages, WebSocketPayloads, SignalProvidePayload, SignalCandidatePayload } from './messages'
export interface BaseEvents {
info: (...message: any[]) => void
@ -15,10 +15,11 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
protected _ws?: WebSocket
protected _peer?: RTCPeerConnection
protected _channel?: RTCDataChannel
protected _timeout?: NodeJS.Timeout
protected _timeout?: number
protected _displayname?: string
protected _state: RTCIceConnectionState = 'disconnected'
protected _id = ''
protected _candidates: RTCIceCandidate[] = []
get id() {
return this._id
@ -51,21 +52,17 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
return
}
if (displayname === '') {
throw new Error('Must add a displayname') // TODO: Better handling
}
this._displayname = displayname
this[EVENT.CONNECTING]()
try {
this._ws = new WebSocket(`${url}ws?password=${password}`)
this._ws = new WebSocket(`${url}?password=${encodeURIComponent(password)}`)
this.emit('debug', `connecting to ${this._ws.url}`)
this._ws.onmessage = this.onMessage.bind(this)
this._ws.onerror = event => this.onError.bind(this)
this._ws.onclose = event => this.onDisconnected.bind(this, new Error('websocket closed'))
this._timeout = setTimeout(this.onTimeout.bind(this), 15000)
} catch (err) {
this._ws.onerror = (event) => this.onError.bind(this)
this._ws.onclose = (event) => this.onDisconnected.bind(this, new Error('websocket closed'))
this._timeout = window.setTimeout(this.onTimeout.bind(this), 15000)
} catch (err: any) {
this.onDisconnected(err)
}
}
@ -73,19 +70,46 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
protected disconnect() {
if (this._timeout) {
clearTimeout(this._timeout)
this._timeout = undefined
}
if (this.socketOpen) {
if (this._ws) {
// reset all events
this._ws.onmessage = () => {}
this._ws.onerror = () => {}
this._ws.onclose = () => {}
try {
this._ws!.close()
this._ws.close()
} catch (err) {}
this._ws = undefined
}
if (this.peerConnected) {
if (this._channel) {
// reset all events
this._channel.onmessage = () => {}
this._channel.onerror = () => {}
this._channel.onclose = () => {}
try {
this._peer!.close()
this._channel.close()
} catch (err) {}
this._channel = undefined
}
if (this._peer) {
// reset all events
this._peer.onconnectionstatechange = () => {}
this._peer.onsignalingstatechange = () => {}
this._peer.oniceconnectionstatechange = () => {}
this._peer.ontrack = () => {}
try {
this._peer.close()
} catch (err) {}
this._peer = undefined
}
@ -156,7 +180,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
this._ws!.send(JSON.stringify({ event, ...payload }))
}
public createPeer(sdp: string, lite: boolean, servers: string[]) {
public async createPeer(sdp: string, lite: boolean, servers: RTCIceServer[]) {
this.emit('debug', `creating peer`)
if (!this.socketOpen) {
this.emit(
@ -175,19 +199,19 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
this._peer = new RTCPeerConnection()
if (lite !== true) {
this._peer = new RTCPeerConnection({
iceServers: [{ urls: servers }],
iceServers: servers,
})
}
this._peer.onconnectionstatechange = event => {
this._peer.onconnectionstatechange = (event) => {
this.emit('debug', `peer connection state changed`, this._peer ? this._peer.connectionState : undefined)
}
this._peer.onsignalingstatechange = event => {
this._peer.onsignalingstatechange = (event) => {
this.emit('debug', `peer signaling state changed`, this._peer ? this._peer.signalingState : undefined)
}
this._peer.oniceconnectionstatechange = event => {
this._peer.oniceconnectionstatechange = (event) => {
this._state = this._peer!.iceConnectionState
this.emit('debug', `peer ice connection state changed: ${this._peer!.iceConnectionState}`)
@ -196,16 +220,24 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
case 'checking':
if (this._timeout) {
clearTimeout(this._timeout)
this._timeout = undefined
}
break
case 'connected':
this.onConnected()
break
case 'disconnected':
this[EVENT.RECONNECTING]()
break
// https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling#ice_connection_state
// We don't watch the disconnected signaling state here as it can indicate temporary issues and may
// go back to a connected state after some time. Watching it would close the video call on any temporary
// network issue.
case 'failed':
this.onDisconnected(new Error('peer failed'))
break
case 'disconnected':
this.onDisconnected(new Error('peer disconnected'))
case 'closed':
this.onDisconnected(new Error('peer closed'))
break
}
}
@ -220,19 +252,26 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
this._channel.onclose = this.onDisconnected.bind(this, new Error('peer data channel closed'))
this._peer.setRemoteDescription({ type: 'offer', sdp })
this._peer
.createAnswer()
.then(d => {
this._peer!.setLocalDescription(d)
this._ws!.send(
JSON.stringify({
event: EVENT.SIGNAL.ANSWER,
sdp: d.sdp,
displayname: this._displayname,
}),
)
})
.catch(err => this.emit('error', err))
for (const candidate of this._candidates) {
this._peer.addIceCandidate(candidate)
}
this._candidates = []
try {
const d = await this._peer.createAnswer()
this._peer!.setLocalDescription(d)
this._ws!.send(
JSON.stringify({
event: EVENT.SIGNAL.ANSWER,
sdp: d.sdp,
displayname: this._displayname,
}),
)
} catch (err: any) {
this.emit('error', err)
}
}
private onMessage(e: MessageEvent) {
@ -247,6 +286,17 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
return
}
if (event === EVENT.SIGNAL.CANDIDATE) {
const { data } = payload as SignalCandidatePayload
const candidate: RTCIceCandidate = JSON.parse(data)
if (this._peer) {
this._peer.addIceCandidate(candidate)
} else {
this._candidates.push(candidate)
}
return
}
// @ts-ignore
if (typeof this[event] === 'function') {
// @ts-ignore
@ -277,6 +327,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
private onConnected() {
if (this._timeout) {
clearTimeout(this._timeout)
this._timeout = undefined
}
if (!this.connected) {
@ -292,6 +343,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
this.emit('debug', `connection timeout`)
if (this._timeout) {
clearTimeout(this._timeout)
this._timeout = undefined
}
this.onDisconnected(new Error('connection timeout'))
}
@ -306,6 +358,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
this.emit('warn', `unhandled websocket event '${event}':`, payload)
}
protected abstract [EVENT.RECONNECTING](): void
protected abstract [EVENT.CONNECTING](): void
protected abstract [EVENT.CONNECTED](): void
protected abstract [EVENT.DISCONNECTED](reason?: Error): void

View File

@ -1,5 +1,6 @@
export const EVENT = {
// Internal Events
RECONNECTING: 'RECONNECTING',
CONNECTING: 'CONNECTING',
CONNECTED: 'CONNECTED',
DISCONNECTED: 'DISCONNECTED',
@ -10,10 +11,12 @@ export const EVENT = {
// Websocket Events
SYSTEM: {
DISCONNECT: 'system/disconnect',
ERROR: 'system/error',
},
SIGNAL: {
ANSWER: 'signal/answer',
PROVIDE: 'signal/provide',
CANDIDATE: 'signal/candidate',
},
MEMBER: {
LIST: 'member/list',
@ -39,9 +42,9 @@ export const EVENT = {
SET: 'screen/set',
},
BROADCAST: {
STATUS: "broadcast/status",
CREATE: "broadcast/create",
DESTROY: "broadcast/destroy",
STATUS: 'broadcast/status',
CREATE: 'broadcast/create',
DESTROY: 'broadcast/destroy',
},
ADMIN: {
BAN: 'admin/ban',
@ -78,7 +81,7 @@ export type ControlEvents =
export type SystemEvents = typeof EVENT.SYSTEM.DISCONNECT
export type MemberEvents = typeof EVENT.MEMBER.LIST | typeof EVENT.MEMBER.CONNECTED | typeof EVENT.MEMBER.DISCONNECTED
export type SignalEvents = typeof EVENT.SIGNAL.ANSWER | typeof EVENT.SIGNAL.PROVIDE
export type SignalEvents = typeof EVENT.SIGNAL.ANSWER | typeof EVENT.SIGNAL.PROVIDE | typeof EVENT.SIGNAL.CANDIDATE
export type ChatEvents = typeof EVENT.CHAT.MESSAGE | typeof EVENT.CHAT.EMOTE
export type ScreenEvents = typeof EVENT.SCREEN.CONFIGURATIONS | typeof EVENT.SCREEN.RESOLUTION | typeof EVENT.SCREEN.SET

View File

@ -6,7 +6,7 @@ import { EVENT } from './events'
import { accessor } from '~/store'
import {
DisconnectPayload,
SystemMessagePayload,
SignalProvidePayload,
MemberListPayload,
MemberDisconnectPayload,
@ -28,10 +28,21 @@ interface NekoEvents extends BaseEvents {}
export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
private $vue!: Vue
private $accessor!: typeof accessor
private url!: string
init(vue: Vue) {
const url =
process.env.NODE_ENV === 'development'
? `ws://${location.host.split(':')[0]}:${process.env.VUE_APP_SERVER_PORT}/ws`
: location.protocol.replace(/^http/, 'ws') + '//' + location.host + location.pathname.replace(/\/$/, '') + '/ws'
this.initWithURL(vue, url)
}
initWithURL(vue: Vue, url: string) {
this.$vue = vue
this.$accessor = vue.$accessor
this.url = url
}
private cleanup() {
@ -43,12 +54,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
}
login(password: string, displayname: string) {
const url =
process.env.NODE_ENV === 'development'
? `ws://${location.host.split(':')[0]}:${process.env.VUE_APP_SERVER_PORT}/`
: `${/https/gi.test(location.protocol) ? 'wss' : 'ws'}://${location.host}/`
this.connect(url, password, displayname)
this.connect(this.url, password, displayname)
}
logout() {
@ -64,6 +70,16 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
/////////////////////////////
// Internal Events
/////////////////////////////
protected [EVENT.RECONNECTING]() {
this.$vue.$notify({
group: 'neko',
type: 'warning',
title: this.$vue.$t('connection.reconnecting') as string,
duration: 5000,
speed: 1000,
})
}
protected [EVENT.CONNECTING]() {
this.$accessor.setConnnecting()
}
@ -71,7 +87,11 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
protected [EVENT.CONNECTED]() {
this.$accessor.user.setMember(this.id)
this.$accessor.setConnected(true)
this.$accessor.setConnected(true)
this.$vue.$notify({
group: 'neko',
clean: true,
})
this.$vue.$notify({
group: 'neko',
@ -84,6 +104,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
protected [EVENT.DISCONNECTED](reason?: Error) {
this.cleanup()
this.$vue.$notify({
group: 'neko',
type: 'error',
@ -109,8 +130,14 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
/////////////////////////////
// System Events
/////////////////////////////
protected [EVENT.SYSTEM.DISCONNECT]({ message }: DisconnectPayload) {
protected [EVENT.SYSTEM.DISCONNECT]({ message }: SystemMessagePayload) {
if (message == 'kicked') {
this.$accessor.logout()
message = this.$vue.$t('connection.kicked') as string
}
this.onDisconnected(new Error(message))
this.$vue.$swal({
title: this.$vue.$t('connection.disconnected'),
text: message,
@ -119,6 +146,15 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
})
}
protected [EVENT.SYSTEM.ERROR]({ title, message }: SystemMessagePayload) {
this.$vue.$swal({
title,
text: message,
icon: 'error',
confirmButtonText: this.$vue.$t('connection.button_confirm') as string,
})
}
/////////////////////////////
// Member Events
/////////////////////////////
@ -177,7 +213,9 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$vue.$notify({
group: 'neko',
type: 'info',
title: this.$vue.$t('notifications.controls_taken', { name: this.$vue.$t('you') }) as string,
title: this.$vue.$t('notifications.controls_taken', {
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
duration: 5000,
speed: 1000,
})
@ -202,7 +240,9 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$vue.$notify({
group: 'neko',
type: 'info',
title: this.$vue.$t('notifications.controls_released', { name: this.$vue.$t('you') }) as string,
title: this.$vue.$t('notifications.controls_released', {
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
duration: 5000,
speed: 1000,
})
@ -259,7 +299,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.controls_given', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -350,7 +390,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.banned', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -370,7 +410,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.kicked', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -392,7 +432,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.muted', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -414,7 +454,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.unmuted', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -463,7 +503,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.controls_taken_steal', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -490,7 +530,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.controls_released_steal', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -513,7 +553,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.controls_given', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),

View File

@ -15,6 +15,7 @@ export type WebSocketMessages =
| WebSocketMessage
| SignalProvideMessage
| SignalAnswerMessage
| SignalCandidateMessage
| MemberListMessage
| MemberConnectMessage
| MemberDisconnectMessage
@ -26,6 +27,7 @@ export type WebSocketMessages =
export type WebSocketPayloads =
| SignalProvidePayload
| SignalAnswerPayload
| SignalCandidatePayload
| MemberListPayload
| Member
| ControlPayload
@ -48,10 +50,12 @@ export interface WebSocketMessage {
SYSTEM MESSAGES/PAYLOADS
*/
// system/disconnect
export interface DisconnectMessage extends WebSocketMessage, DisconnectPayload {
event: typeof EVENT.SYSTEM.DISCONNECT
// system/error
export interface SystemMessage extends WebSocketMessage, SystemMessagePayload {
event: typeof EVENT.SYSTEM.DISCONNECT | typeof EVENT.SYSTEM.ERROR
}
export interface DisconnectPayload {
export interface SystemMessagePayload {
title: string
message: string
}
@ -65,7 +69,7 @@ export interface SignalProvideMessage extends WebSocketMessage, SignalProvidePay
export interface SignalProvidePayload {
id: string
lite: boolean
ice: string[]
ice: RTCIceServer[]
sdp: string
}
@ -78,6 +82,14 @@ export interface SignalAnswerPayload {
displayname: string
}
// signal/candidate
export interface SignalCandidateMessage extends WebSocketMessage, SignalCandidatePayload {
event: typeof EVENT.SIGNAL.CANDIDATE
}
export interface SignalCandidatePayload {
data: string
}
/*
MEMBER MESSAGES/PAYLOADS
*/

View File

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

View File

@ -25,10 +25,10 @@ class VueSweetalert2 {
if (options) {
const mixed = Swal.mixin(options)
return mixed.fire.apply(mixed, args)
return mixed.fire(...args)
}
return Swal.fire.apply(Swal, args)
return Swal.fire(...args)
}
let methodName: string | number | symbol
@ -40,7 +40,7 @@ class VueSweetalert2 {
swalFunction[methodName] = ((method) => {
return (...args: any[]) => {
// @ts-ignore
return Swal[method].apply(Swal, args)
return Swal[method](...args)
}
})(methodName)
}

View File

@ -23,6 +23,7 @@ interface Message {
export const state = () => ({
history: [] as Message[],
emotes: {} as Emotes,
texts: 0,
})
export const getters = getterTree(state, {
@ -31,6 +32,10 @@ export const getters = getterTree(state, {
export const mutations = mutationTree(state, {
addMessage(state, message: Message) {
if (message.type == 'text') {
state.texts++
}
state.history = state.history.concat([message])
},
@ -52,6 +57,7 @@ export const mutations = mutationTree(state, {
reset(state) {
state.emotes = {}
state.history = []
state.texts = 0
},
})

View File

@ -58,17 +58,17 @@ export const mutations = mutationTree(state, {
export const actions = actionTree(
{ state, getters, mutations },
{
initialise() {
$http
.get<Emojis>('/emoji.json')
.then((req) => {
for (const group of req.data.groups) {
accessor.emoji.addGroup(group)
}
accessor.emoji.setList(req.data.list)
accessor.emoji.setKeywords(req.data.keywords)
})
.catch(console.error)
async initialise() {
try {
const req = await $http.get<Emojis>('emoji.json')
for (const group of req.data.groups) {
accessor.emoji.addGroup(group)
}
accessor.emoji.setList(req.data.list)
accessor.emoji.setKeywords(req.data.keywords)
} catch (err: any) {
console.error(err)
}
},
},
)

View File

@ -3,9 +3,8 @@ import { Member } from '~/neko/types'
import { EVENT } from '~/neko/events'
import { accessor } from '~/store'
const keyboardModifierState =
(capsLock: boolean, numLock: boolean, scrollLock: boolean) =>
Number(capsLock) + 2*Number(numLock) + 4*Number(scrollLock)
const keyboardModifierState = (capsLock: boolean, numLock: boolean, scrollLock: boolean) =>
Number(capsLock) + 2 * Number(numLock) + 4 * Number(scrollLock)
export const namespaced = true
@ -82,7 +81,7 @@ export const actions = actionTree(
},
request({ getters }) {
if (!accessor.connected || !getters.hosting) {
if (!accessor.connected || getters.hosting) {
return
}
@ -90,7 +89,7 @@ export const actions = actionTree(
},
release({ getters }) {
if (!accessor.connected || getters.hosting) {
if (!accessor.connected || !getters.hosting) {
return
}
@ -155,11 +154,11 @@ export const actions = actionTree(
syncKeyboardModifierState({ state, getters }, { capsLock, numLock, scrollLock }) {
if (state.keyboardModifierState === keyboardModifierState(capsLock, numLock, scrollLock)) {
return ;
return
}
accessor.remote.setKeyboardModifierState({ capsLock, numLock, scrollLock })
$client.sendMessage(EVENT.CONTROL.KEYBOARD, { capsLock, numLock, scrollLock })
}
},
},
)

View File

@ -19,9 +19,9 @@ export const state = () => {
keyboard_layout: get<string>('keyboard_layout', 'us'),
keyboard_layouts_list: {} as KeyboardLayouts,
broadcast_is_active: false,
broadcast_url: "",
broadcast_url: '',
}
}
@ -62,7 +62,7 @@ export const mutations = mutationTree(state, {
state.keyboard_layouts_list = value
},
setBroadcastStatus(state, { url, isActive }) {
state.broadcast_url = url,
state.broadcast_url = url
state.broadcast_is_active = isActive
},
})
@ -70,14 +70,13 @@ export const mutations = mutationTree(state, {
export const actions = actionTree(
{ state, getters, mutations },
{
initialise() {
$http
.get<KeyboardLayouts>('/keyboard_layouts.json')
.then((req) => {
accessor.settings.setKeyboardLayoutsList(req.data)
console.log(req.data)
})
.catch(console.error)
async initialise() {
try {
const req = await $http.get<KeyboardLayouts>('keyboard_layouts.json')
accessor.settings.setKeyboardLayoutsList(req.data)
} catch (err: any) {
console.error(err)
}
},
broadcastStatus({ getters }, { url, isActive }) {

View File

@ -48,6 +48,11 @@ export const mutations = mutationTree(state, {
state.id = id
},
addMember(state, member: Member) {
// remove html tags
const tmp = document.createElement('div')
tmp.innerHTML = member.displayname
member.displayname = tmp.textContent || tmp.innerText || ''
state.members = {
...state.members,
[member.id]: {

View File

@ -1,3 +1,4 @@
/* eslint-disable */
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file

View File

@ -4,41 +4,41 @@ export interface GuacamoleKeyboardInterface {
/**
* Fired whenever the user presses a key with the element associated
* with this Guacamole.Keyboard in focus.
*
*
* @event
* @param {Number} keysym The keysym of the key being pressed.
* @return {Boolean} true if the key event should be allowed through to the
* browser, false otherwise.
*/
onkeydown?: (keysym: number) => boolean;
onkeydown?: (keysym: number) => boolean
/**
* Fired whenever the user releases a key with the element associated
* with this Guacamole.Keyboard in focus.
*
*
* @event
* @param {Number} keysym The keysym of the key being released.
*/
onkeyup?: (keysym: number) => void;
onkeyup?: (keysym: number) => void
/**
* Marks a key as pressed, firing the keydown event if registered. Key
* repeat for the pressed key will start after a delay if that key is
* not a modifier. The return value of this function depends on the
* return value of the keydown event handler, if any.
*
*
* @param {Number} keysym The keysym of the key to press.
* @return {Boolean} true if event should NOT be canceled, false otherwise.
*/
press: (keysym: number) => boolean;
press: (keysym: number) => boolean
/**
* Marks a key as released, firing the keyup event if registered.
*
*
* @param {Number} keysym The keysym of the key to release.
*/
release: (keysym: number) => void;
release: (keysym: number) => void
/**
* Presses and releases the keys necessary to type the given string of
* text.
@ -46,14 +46,14 @@ export interface GuacamoleKeyboardInterface {
* @param {String} str
* The string to type.
*/
type: (str: string) => void;
type: (str: string) => void
/**
* Resets the state of this keyboard, releasing all keys, and firing keyup
* events for each released key.
*/
reset: () => void;
reset: () => void
/**
* Attaches event listeners to the given Element, automatically translating
* received key, input, and composition events into simple keydown/keyup
@ -64,13 +64,13 @@ export interface GuacamoleKeyboardInterface {
* The Element to attach event listeners to for the sake of handling
* key or input events.
*/
listenTo: (element: Element | Document) => void;
listenTo: (element: Element | Document) => void
}
export default function(element?: Element): GuacamoleKeyboardInterface {
var Keyboard = {};
export default function (element?: Element): GuacamoleKeyboardInterface {
const Keyboard = {}
GuacamoleKeyboard.bind(Keyboard, element)();
GuacamoleKeyboard.bind(Keyboard, element)()
return Keyboard as GuacamoleKeyboardInterface;
return Keyboard as GuacamoleKeyboardInterface
}

View File

@ -2,7 +2,7 @@ export function makeid(length: number) {
let result = ''
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
const charactersLength = characters.length
for (var i = 0; i < length; i++) {
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result

View File

@ -13,7 +13,7 @@ export function set<T extends string | number | boolean>(key: string, val: T) {
}
export function get<T extends string | number | boolean>(key: string, def: T): T {
let store = localStorage.getItem(key)
const store = localStorage.getItem(key)
if (store) {
switch (typeof def) {
case 'number':

View File

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

View File

@ -5,12 +5,14 @@ module.exports = {
css: {
loaderOptions: {
sass: {
prependData: `
additionalData: `
@import "@/assets/styles/_variables.scss";
`,
},
},
},
publicPath: './',
assetsDir: './',
configureWebpack: {
resolve: {
alias: {
@ -19,4 +21,7 @@ module.exports = {
},
},
},
devServer: {
disableHostCheck: true,
}
}

View File

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

View File

@ -1,35 +1,56 @@
module n.eko.moe/neko
go 1.13
go 1.17
require (
github.com/coreos/go-etcd v2.0.0+incompatible // indirect
github.com/cpuguy83/go-md2man v1.0.10 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-chi/chi v4.1.0+incompatible
github.com/golang/protobuf v1.3.5 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-chi/chi v4.1.2+incompatible
github.com/gopherjs/gopherjs v0.0.0-20210901121439-eee08aaf2717 // indirect
github.com/gorilla/websocket v1.4.2
github.com/kataras/go-events v0.0.2
github.com/lucas-clemente/quic-go v0.15.2 // indirect
github.com/mitchellh/mapstructure v1.2.2 // indirect
github.com/pelletier/go-toml v1.7.0 // indirect
github.com/pion/ice v0.7.12 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pion/ice/v2 v2.1.12 // indirect
github.com/pion/interceptor v0.0.18
github.com/pion/logging v0.2.2
github.com/pion/rtp v1.4.0 // indirect
github.com/pion/sdp/v2 v2.3.5 // indirect
github.com/pion/turn v1.4.0 // indirect
github.com/pion/webrtc/v2 v2.2.4
github.com/pion/rtp v1.7.2 // indirect
github.com/pion/srtp/v2 v2.0.5 // indirect
github.com/pion/webrtc/v3 v3.0.32
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.18.0
github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cobra v0.0.7
github.com/rs/zerolog v1.25.0
github.com/smartystreets/assertions v1.2.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/cobra v1.2.1
github.com/spf13/viper v1.8.1
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
golang.org/x/net v0.0.0-20210908191846-a5e095526f91 // indirect
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
require (
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pion/datachannel v1.4.21 // indirect
github.com/pion/dtls/v2 v2.0.9 // indirect
github.com/pion/mdns v0.0.5 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.7 // indirect
github.com/pion/sctp v1.7.12 // indirect
github.com/pion/sdp/v3 v3.0.4 // indirect
github.com/pion/stun v0.3.5 // indirect
github.com/pion/transport v0.12.3 // indirect
github.com/pion/turn/v2 v2.0.5 // indirect
github.com/pion/udp v0.1.1 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.6.2
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 // indirect
golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8 // indirect
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d // indirect
golang.org/x/text v0.3.2 // indirect
gopkg.in/ini.v1 v1.55.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/ini.v1 v1.63.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,8 @@
package broadcast
import (
"sync"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
@ -9,6 +11,7 @@ import (
)
type BroadcastManager struct {
mu sync.Mutex
logger zerolog.Logger
pipeline *gst.Pipeline
remote *config.Remote
@ -27,9 +30,9 @@ func New(remote *config.Remote, config *config.Broadcast) *BroadcastManager {
}
}
func (manager *BroadcastManager) Start() {
func (manager *BroadcastManager) Start() error {
if !manager.enabled || manager.IsActive() {
return
return nil
}
var err error
@ -40,18 +43,19 @@ func (manager *BroadcastManager) Start() {
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...")
if err != nil {
manager.logger.Panic().Err(err).Msg("unable to create rtmp pipeline")
return
}
manager.pipeline.Play()
return nil
}
func (manager *BroadcastManager) Stop() {
@ -67,13 +71,25 @@ func (manager *BroadcastManager) IsActive() bool {
return manager.pipeline != nil
}
func (manager *BroadcastManager) Create(url string) {
func (manager *BroadcastManager) Create(url string) error {
manager.mu.Lock()
defer manager.mu.Unlock()
manager.url = url
manager.enabled = true
manager.Start()
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
}

Some files were not shown because too many files have changed in this diff Show More