Merge branch 'master' of https://github.com/nurdism/neko into sk_lang

This commit is contained in:
m1k1o 2020-07-11 23:18:28 +02:00
commit bac0686f20
30 changed files with 2684 additions and 1007 deletions

View File

@ -73,7 +73,8 @@ build() {
build_image "xfce4"; \ build_image "xfce4"; \
build_image "jwm"; \ build_image "jwm"; \
build_image "firefox"; \ build_image "firefox"; \
build_image "chromium"; build_image "chromium"; \
build_image "tor-browser";
fi fi
sudo docker images nurdism/neko sudo docker images nurdism/neko
@ -90,6 +91,7 @@ push() {
sudo docker push nurdism/neko:jwm sudo docker push nurdism/neko:jwm
sudo docker push nurdism/neko:firefox sudo docker push nurdism/neko:firefox
sudo docker push nurdism/neko:chromium sudo docker push nurdism/neko:chromium
sudo docker push nurdism/neko:tor-browser
fi fi
} }

View File

@ -0,0 +1,28 @@
FROM nurdism/neko:openbox
#
# install dependencies
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends curl xz-utils file libgtk-3-0 libdbus-glib-1-2; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
WORKDIR /home/neko
USER neko
#
# download TOR browser
RUN 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 tor.tar.xz "https://www.torproject.org/$DOWNLOAD_URI"; \
tar -xvJf tor.tar.xz; \
rm -f tor.tar.xz*;
USER root
#
# copy configuation file
COPY .docker/files/tor-browser/supervisord.conf /etc/neko/supervisord/tor-browser.conf
COPY .docker/files/tor-browser/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,13 @@
[program:tor-browser]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/home/neko/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

View File

@ -0,0 +1 @@
{"af":"Afghani","al":"Albanian","et":"Amharic","ma":"Arabic (Morocco)","sy":"Arabic (Syria)","am":"Armenian","az":"Azerbaijani","ml":"Bambara","bd":"Bangla","by":"Belarusian","be":"Belgian","dz":"Berber (Algeria, Latin characters)","ba":"Bosnian","bg":"Bulgarian","mm":"Burmese","hr":"Croatian","cz":"Czech","dk":"Danish","mv":"Dhivehi","nl":"Dutch","bt":"Dzongkha","au":"English (Australian)","cm":"English (Cameroon)","gh":"English (Ghana)","ng":"English (Nigeria)","za":"English (South Africa)","us":"English (US)","gb":"English (UK)","ee":"Estonian","fo":"Faroese","ph":"Filipino","fi":"Finnish","fr":"French","ca":"French (Canada)","cd":"French (Democratic Republic of the Congo)","gn":"French (Guinea)","tg":"French (Togo)","ge":"Georgian","de":"German","at":"German (Austria)","ch":"German (Switzerland)","gr":"Greek","il":"Hebrew","hu":"Hungarian","cn":"Chinese","is":"Icelandic","in":"Indian","id":"Indonesian (Jawi)","iq":"Iraqi","ie":"Irish","it":"Italian","jp":"Japanese","kz":"Kazakh","kh":"Khmer (Cambodia)","kr":"Korean","kg":"Kyrgyz","la":"Lao","lv":"Latvian","lt":"Lithuanian","mk":"Macedonian","my":"Malay (Jawi)","mt":"Maltese","md":"Moldavian","mn":"Mongolian","me":"Montenegrin","np":"Nepali","no":"Norwegian","ir":"Persian","pl":"Polish","pt":"Portuguese","br":"Portuguese (Brazil)","ro":"Romanian","ru":"Russian","rs":"Serbian","lk":"Sinhala (phonetic)","sk":"Slovak","si":"Slovenian","es":"Spanish","ke":"Swahili (Kenya)","tz":"Swahili (Tanzania)","se":"Swedish","tw":"Taiwanese","tj":"Tajik","th":"Thai","bw":"Tswana","tr":"Turkish","tm":"Turkmen","ua":"Ukrainian","pk":"Urdu (Pakistan)","uz":"Uzbek","vn":"Vietnamese","sn":"Wolof"}

View File

@ -4,7 +4,7 @@
<li> <li>
<span>{{ $t('setting.scroll') }}</span> <span>{{ $t('setting.scroll') }}</span>
<label class="slider"> <label class="slider">
<input type="range" min="5" max="100" v-model="scroll" /> <input type="range" min="1" max="100" v-model="scroll" />
</label> </label>
</li> </li>
<li> <li>
@ -35,6 +35,19 @@
<span /> <span />
</label> </label>
</li> </li>
<li>
<span>{{ $t('setting.keyboard_layout') }}</span>
<label class="select">
<select v-model="keyboard_layout">
<option
v-for="(name, code) in keyboard_layouts_list"
:key="code"
:value="code"
>{{ name }}</option>
</select>
<span />
</label>
</li>
<li v-if="connected"> <li v-if="connected">
<button @click.stop.prevent="logout">{{ $t('logout') }}</button> <button @click.stop.prevent="logout">{{ $t('logout') }}</button>
</li> </li>
@ -182,6 +195,31 @@
} }
} }
} }
.select {
max-width: 120px;
select {
display: block;
width: 100%;
max-width: 100%;
padding: 4px;
margin: 0;
line-height: 30px;
font-weight: bold;
border: 0;
border-radius: 12px;
color: black;
background-color: $style-primary;
option {
font-weight: normal;
color: $text-normal;
background-color: $background-tertiary;
}
}
}
} }
} }
} }
@ -236,6 +274,19 @@
this.$accessor.settings.setSound(value) this.$accessor.settings.setSound(value)
} }
get keyboard_layouts_list() {
return this.$accessor.settings.keyboard_layouts_list
}
get keyboard_layout() {
return this.$accessor.settings.keyboard_layout
}
set keyboard_layout(value: string) {
this.$accessor.settings.setKeyboardLayout(value)
this.$accessor.remote.changeKeyboard()
}
logout() { logout() {
this.$accessor.logout() this.$accessor.logout()
} }

View File

@ -20,8 +20,6 @@
@mouseup.stop.prevent="onMouseUp" @mouseup.stop.prevent="onMouseUp"
@mouseenter.stop.prevent="onMouseEnter" @mouseenter.stop.prevent="onMouseEnter"
@mouseleave.stop.prevent="onMouseLeave" @mouseleave.stop.prevent="onMouseLeave"
@keydown.stop.prevent="onKeyDown"
@keyup.stop.prevent="onKeyUp"
/> />
<div v-if="!playing" class="player-overlay"> <div v-if="!playing" class="player-overlay">
<i @click.stop.prevent="toggle" v-if="playable" class="fas fa-play-circle" /> <i @click.stop.prevent="toggle" v-if="playable" class="fas fa-play-circle" />
@ -142,6 +140,8 @@
import Emote from './emote.vue' import Emote from './emote.vue'
import Resolution from './resolution.vue' import Resolution from './resolution.vue'
import GuacamoleKeyboard from '~/utils/guacamole-keyboard.ts'
@Component({ @Component({
name: 'neko-video', name: 'neko-video',
components: { components: {
@ -158,10 +158,10 @@
@Ref('video') readonly _video!: HTMLVideoElement @Ref('video') readonly _video!: HTMLVideoElement
@Ref('resolution') readonly _resolution!: any @Ref('resolution') readonly _resolution!: any
private keyboard = GuacamoleKeyboard()
private observer = new ResizeObserver(this.onResise.bind(this)) private observer = new ResizeObserver(this.onResise.bind(this))
private focused = false private focused = false
private fullscreen = false private fullscreen = false
private activeKeys: Set<number> = new Set()
get admin() { get admin() {
return this.$accessor.user.admin return this.$accessor.user.admin
@ -335,6 +335,24 @@
document.addEventListener('focusin', this.onFocus.bind(this)) document.addEventListener('focusin', this.onFocus.bind(this))
document.addEventListener('focusout', this.onBlur.bind(this)) document.addEventListener('focusout', this.onBlur.bind(this))
/* Initialize Guacamole Keyboard */
this.keyboard.onkeydown = (key: number) => {
if (!this.focused || !this.hosting || this.locked) {
return true
}
this.$client.sendData('keydown', { key })
return false
}
this.keyboard.onkeyup = (key: number) => {
if (!this.focused || !this.hosting || this.locked) {
return
}
this.$client.sendData('keyup', { key })
}
this.keyboard.listenTo(this._overlay)
} }
beforeDestroy() { beforeDestroy() {
@ -342,6 +360,7 @@
this.$accessor.video.setPlayable(false) this.$accessor.video.setPlayable(false)
document.removeEventListener('focusin', this.onFocus.bind(this)) document.removeEventListener('focusin', this.onFocus.bind(this))
document.removeEventListener('focusout', this.onBlur.bind(this)) document.removeEventListener('focusout', this.onBlur.bind(this))
/* Guacamole Keyboard does not provide destroy functions */
} }
play() { play() {
@ -409,10 +428,7 @@
return return
} }
for (let key of this.activeKeys) { this.keyboard.reset()
this.$client.sendData('keyup', { key })
this.activeKeys.delete(key)
}
} }
onMousePos(e: MouseEvent) { onMousePos(e: MouseEvent) {
@ -449,7 +465,7 @@
return return
} }
this.onMousePos(e) this.onMousePos(e)
this.$client.sendData('mousedown', { key: e.button }) this.$client.sendData('mousedown', { key: e.button + 1 })
} }
onMouseUp(e: MouseEvent) { onMouseUp(e: MouseEvent) {
@ -457,7 +473,7 @@
return return
} }
this.onMousePos(e) this.onMousePos(e)
this.$client.sendData('mouseup', { key: e.button }) this.$client.sendData('mouseup', { key: e.button + 1 })
} }
onMouseMove(e: MouseEvent) { onMouseMove(e: MouseEvent) {
@ -477,44 +493,6 @@
this.focused = false this.focused = false
} }
// frick you firefox
getCode(e: KeyboardEvent): number {
let key = e.keyCode
if (key === 59 && (e.key === ';' || e.key === ':')) {
key = 186
}
if (key === 61 && (e.key === '=' || e.key === '+')) {
key = 187
}
if (key === 173 && (e.key === '-' || e.key === '_')) {
key = 189
}
return key
}
onKeyDown(e: KeyboardEvent) {
if (!this.focused || !this.hosting || this.locked) {
return
}
let key = this.getCode(e)
this.$client.sendData('keydown', { key })
this.activeKeys.add(key)
}
onKeyUp(e: KeyboardEvent) {
if (!this.focused || !this.hosting || this.locked) {
return
}
let key = this.getCode(e)
this.$client.sendData('keyup', { key })
this.activeKeys.delete(key)
}
onResise() { onResise() {
let height = 0 let height = 0
if (!this.fullscreen) { if (!this.fullscreen) {

View File

@ -60,6 +60,7 @@ export const setting = {
autoplay: 'Autoplay Video', autoplay: 'Autoplay Video',
ignore_emotes: 'Ignore Emotes', ignore_emotes: 'Ignore Emotes',
chat_sound: 'Play Chat Sound', chat_sound: 'Play Chat Sound',
keyboard_layout: 'Change Keyboard Layout',
} }
export const connection = { export const connection = {

View File

@ -123,19 +123,19 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
break break
case 'keydown': case 'keydown':
case 'mousedown': case 'mousedown':
buffer = new ArrayBuffer(5) buffer = new ArrayBuffer(11)
payload = new DataView(buffer) payload = new DataView(buffer)
payload.setUint8(0, OPCODE.KEY_DOWN) payload.setUint8(0, OPCODE.KEY_DOWN)
payload.setUint16(1, 1, true) payload.setUint16(1, 8, true)
payload.setUint16(3, data.key, true) payload.setBigUint64(3, BigInt(data.key), true)
break break
case 'keyup': case 'keyup':
case 'mouseup': case 'mouseup':
buffer = new ArrayBuffer(5) buffer = new ArrayBuffer(11)
payload = new DataView(buffer) payload = new DataView(buffer)
payload.setUint8(0, OPCODE.KEY_UP) payload.setUint8(0, OPCODE.KEY_UP)
payload.setUint16(1, 1, true) payload.setUint16(1, 8, true)
payload.setUint16(3, data.key, true) payload.setBigUint64(3, BigInt(data.key), true)
break break
default: default:
this.emit('warn', `unknown data event: ${event}`) this.emit('warn', `unknown data event: ${event}`)

View File

@ -27,6 +27,7 @@ export const EVENT = {
REQUESTING: 'control/requesting', REQUESTING: 'control/requesting',
CLIPBOARD: 'control/clipboard', CLIPBOARD: 'control/clipboard',
GIVE: 'control/give', GIVE: 'control/give',
KEYBOARD: 'control/keyboard'
}, },
CHAT: { CHAT: {
MESSAGE: 'chat/message', MESSAGE: 'chat/message',
@ -67,6 +68,7 @@ export type ControlEvents =
| typeof EVENT.CONTROL.REQUEST | typeof EVENT.CONTROL.REQUEST
| typeof EVENT.CONTROL.GIVE | typeof EVENT.CONTROL.GIVE
| typeof EVENT.CONTROL.CLIPBOARD | typeof EVENT.CONTROL.CLIPBOARD
| typeof EVENT.CONTROL.KEYBOARD
export type SystemEvents = typeof EVENT.SYSTEM.DISCONNECT export type SystemEvents = typeof EVENT.SYSTEM.DISCONNECT
export type MemberEvents = typeof EVENT.MEMBER.LIST | typeof EVENT.MEMBER.CONNECTED | typeof EVENT.MEMBER.DISCONNECTED export type MemberEvents = typeof EVENT.MEMBER.LIST | typeof EVENT.MEMBER.CONNECTED | typeof EVENT.MEMBER.DISCONNECTED

View File

@ -165,6 +165,8 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
///////////////////////////// /////////////////////////////
protected [EVENT.CONTROL.LOCKED]({ id }: ControlPayload) { protected [EVENT.CONTROL.LOCKED]({ id }: ControlPayload) {
this.$accessor.remote.setHost(id) this.$accessor.remote.setHost(id)
this.$accessor.remote.changeKeyboard()
const member = this.member(id) const member = this.member(id)
if (!member) { if (!member) {
return return
@ -251,6 +253,8 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
} }
this.$accessor.remote.setHost(member) this.$accessor.remote.setHost(member)
this.$accessor.remote.changeKeyboard()
this.$accessor.chat.newMessage({ this.$accessor.chat.newMessage({
id, id,
content: this.$vue.$t('notifications.controls_given', { content: this.$vue.$t('notifications.controls_given', {
@ -431,6 +435,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
protected [EVENT.ADMIN.CONTROL]({ id, target }: AdminTargetPayload) { protected [EVENT.ADMIN.CONTROL]({ id, target }: AdminTargetPayload) {
this.$accessor.remote.setHost(id) this.$accessor.remote.setHost(id)
this.$accessor.remote.changeKeyboard()
if (!target) { if (!target) {
this.$accessor.chat.newMessage({ this.$accessor.chat.newMessage({
@ -495,6 +500,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
} }
this.$accessor.remote.setHost(member) this.$accessor.remote.setHost(member)
this.$accessor.remote.changeKeyboard()
this.$accessor.chat.newMessage({ this.$accessor.chat.newMessage({
id, id,

View File

@ -30,6 +30,7 @@ export type WebSocketPayloads =
| Member | Member
| ControlPayload | ControlPayload
| ControlClipboardPayload | ControlClipboardPayload
| ControlKeyboardPayload
| ChatPayload | ChatPayload
| ChatSendPayload | ChatSendPayload
| EmojiSendPayload | EmojiSendPayload
@ -120,6 +121,10 @@ export interface ControlClipboardPayload {
text: string text: string
} }
export interface ControlKeyboardPayload {
layout: string
}
/* /*
CHAT PAYLOADS CHAT PAYLOADS
*/ */

View File

@ -55,6 +55,7 @@ export const actions = actionTree(
{ {
initialise(store) { initialise(store) {
accessor.emoji.initialise() accessor.emoji.initialise()
accessor.settings.initialise()
}, },
lock() { lock() {

View File

@ -133,5 +133,13 @@ export const actions = actionTree(
$client.sendMessage(EVENT.ADMIN.GIVE, { id: member.id }) $client.sendMessage(EVENT.ADMIN.GIVE, { id: member.id })
}, },
changeKeyboard({ getters }) {
if (!accessor.connected || !getters.hosting) {
return
}
$client.sendMessage(EVENT.CONTROL.KEYBOARD, { layout: accessor.settings.keyboard_layout })
}
}, },
) )

View File

@ -1,8 +1,13 @@
import { getterTree, mutationTree } from 'typed-vuex' import { getterTree, mutationTree, actionTree } from 'typed-vuex'
import { get, set } from '~/utils/localstorage' import { get, set } from '~/utils/localstorage'
import { accessor } from '~/store'
export const namespaced = true export const namespaced = true
interface KeyboardLayouts {
[code: string]: string
}
export const state = () => { export const state = () => {
return { return {
scroll: get<number>('scroll', 10), scroll: get<number>('scroll', 10),
@ -10,6 +15,9 @@ export const state = () => {
autoplay: get<boolean>('autoplay', true), autoplay: get<boolean>('autoplay', true),
ignore_emotes: get<boolean>('ignore_emotes', false), ignore_emotes: get<boolean>('ignore_emotes', false),
chat_sound: get<boolean>('chat_sound', true), chat_sound: get<boolean>('chat_sound', true),
keyboard_layout: get<string>('keyboard_layout', 'us'),
keyboard_layouts_list: {} as KeyboardLayouts,
} }
} }
@ -40,4 +48,28 @@ export const mutations = mutationTree(state, {
state.chat_sound = value state.chat_sound = value
set('chat_sound', value) set('chat_sound', value)
}, },
setKeyboardLayout(state, value: string) {
state.keyboard_layout = value
set('keyboard_layout', value)
},
setKeyboardLayoutsList(state, value: KeyboardLayouts) {
state.keyboard_layouts_list = value
},
}) })
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)
},
},
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,76 @@
import GuacamoleKeyboard from './guacamole-keyboard.js'
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;
/**
* 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;
/**
* 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;
/**
* 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;
/**
* Presses and releases the keys necessary to type the given string of
* text.
*
* @param {String} str
* The string to type.
*/
type: (str: string) => void;
/**
* Resets the state of this keyboard, releasing all keys, and firing keyup
* events for each released key.
*/
reset: () => void;
/**
* Attaches event listeners to the given Element, automatically translating
* received key, input, and composition events into simple keydown/keyup
* events signalled through this Guacamole.Keyboard's onkeydown and
* onkeyup handlers.
*
* @param {Element|Document} element
* The Element to attach event listeners to for the sake of handling
* key or input events.
*/
listenTo: (element: Element | Document) => void;
}
export default function(element?: Element): GuacamoleKeyboardInterface {
var Keyboard = {};
GuacamoleKeyboard.bind(Keyboard, element)();
return Keyboard as GuacamoleKeyboardInterface;
}

View File

@ -102,11 +102,9 @@ func (manager *RemoteManager) StartStream() {
if !xorg.ValidScreenSize(manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate) { if !xorg.ValidScreenSize(manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate) {
manager.logger.Warn().Msgf("invalid screen option %dx%d@%d", manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate) manager.logger.Warn().Msgf("invalid screen option %dx%d@%d", manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate)
} else { } else if err := xorg.ChangeScreenSize(manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate); err != nil {
if err := xorg.ChangeScreenSize(manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate); err != nil {
manager.logger.Warn().Err(err).Msg("unable to change screen size") manager.logger.Warn().Err(err).Msg("unable to change screen size")
} }
}
manager.createPipelines() manager.createPipelines()
manager.video.Start() manager.video.Start()
@ -183,19 +181,19 @@ func (manager *RemoteManager) Scroll(x, y int) {
xorg.Scroll(x, y) xorg.Scroll(x, y)
} }
func (manager *RemoteManager) ButtonDown(code int) (*types.Button, error) { func (manager *RemoteManager) ButtonDown(code int) error {
return xorg.ButtonDown(code) return xorg.ButtonDown(code)
} }
func (manager *RemoteManager) KeyDown(code int) (*types.Key, error) { func (manager *RemoteManager) KeyDown(code uint64) error {
return xorg.KeyDown(code) return xorg.KeyDown(code)
} }
func (manager *RemoteManager) ButtonUp(code int) (*types.Button, error) { func (manager *RemoteManager) ButtonUp(code int) error {
return xorg.ButtonUp(code) return xorg.ButtonUp(code)
} }
func (manager *RemoteManager) KeyUp(code int) (*types.Key, error) { func (manager *RemoteManager) KeyUp(code uint64) error {
return xorg.KeyUp(code) return xorg.KeyUp(code)
} }
@ -218,3 +216,7 @@ func (manager *RemoteManager) ScreenConfigurations() map[int]types.ScreenConfigu
func (manager *RemoteManager) GetScreenSize() *types.ScreenSize { func (manager *RemoteManager) GetScreenSize() *types.ScreenSize {
return xorg.GetScreenSize() return xorg.GetScreenSize()
} }
func (manager *RemoteManager) SetKeyboardLayout(layout string) {
xorg.SetKeyboardLayout(layout)
}

View File

@ -22,6 +22,7 @@ const (
CONTROL_REQUESTING = "control/requesting" CONTROL_REQUESTING = "control/requesting"
CONTROL_GIVE = "control/give" CONTROL_GIVE = "control/give"
CONTROL_CLIPBOARD = "control/clipboard" CONTROL_CLIPBOARD = "control/clipboard"
CONTROL_KEYBOARD = "control/keyboard"
) )
const ( const (

View File

@ -46,6 +46,11 @@ type Clipboard struct {
Text string `json:"text"` Text string `json:"text"`
} }
type Keyboard struct {
Event string `json:"event"`
Layout string `json:"layout"`
}
type Control struct { type Control struct {
Event string `json:"event"` Event string `json:"event"`
ID string `json:"id"` ID string `json:"id"`

View File

@ -15,11 +15,12 @@ type RemoteManager interface {
ScreenConfigurations() map[int]ScreenConfiguration ScreenConfigurations() map[int]ScreenConfiguration
Move(x, y int) Move(x, y int)
Scroll(x, y int) Scroll(x, y int)
ButtonDown(code int) (*Button, error) ButtonDown(code int) error
KeyDown(code int) (*Key, error) KeyDown(code uint64) error
ButtonUp(code int) (*Button, error) ButtonUp(code int) error
KeyUp(code int) (*Key, error) KeyUp(code uint64) error
ReadClipboard() string ReadClipboard() string
WriteClipboard(data string) WriteClipboard(data string)
ResetKeys() ResetKeys()
SetKeyboardLayout(layout string)
} }

View File

@ -33,7 +33,7 @@ type PayloadScroll struct {
type PayloadKey struct { type PayloadKey struct {
PayloadHeader PayloadHeader
Key uint16 Key uint64
} }
func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) error { func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) error {
@ -85,21 +85,21 @@ func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) e
} }
if payload.Key < 8 { if payload.Key < 8 {
button, err := manager.remote.ButtonDown(int(payload.Key)) err := manager.remote.ButtonDown(int(payload.Key))
if err != nil { if err != nil {
manager.logger.Warn().Err(err).Msg("key down failed") manager.logger.Warn().Err(err).Msg("button down failed")
return nil return nil
} }
manager.logger.Debug().Msgf("button down %s(%d)", button.Name, payload.Key) manager.logger.Debug().Msgf("button down %d", payload.Key)
} else { } else {
key, err := manager.remote.KeyDown(int(payload.Key)) err := manager.remote.KeyDown(uint64(payload.Key))
if err != nil { if err != nil {
manager.logger.Warn().Err(err).Msg("key down failed") manager.logger.Warn().Err(err).Msg("key down failed")
return nil return nil
} }
manager.logger.Debug().Msgf("key down %s(%d)", key.Name, payload.Key) manager.logger.Debug().Msgf("key down %d", payload.Key)
} }
break break
@ -111,21 +111,21 @@ func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) e
} }
if payload.Key < 8 { if payload.Key < 8 {
button, err := manager.remote.ButtonUp(int(payload.Key)) err := manager.remote.ButtonUp(int(payload.Key))
if err != nil { if err != nil {
manager.logger.Warn().Err(err).Msg("button up failed") manager.logger.Warn().Err(err).Msg("button up failed")
return nil return nil
} }
manager.logger.Debug().Msgf("button up %s(%d)", button.Name, payload.Key) manager.logger.Debug().Msgf("button up %d", payload.Key)
} else { } else {
key, err := manager.remote.KeyUp(int(payload.Key)) err := manager.remote.KeyUp(uint64(payload.Key))
if err != nil { if err != nil {
manager.logger.Warn().Err(err).Msg("key up failed") manager.logger.Warn().Err(err).Msg("key up failed")
return nil return nil
} }
manager.logger.Debug().Msgf("key up %s(%d)", key.Name, payload.Key) manager.logger.Debug().Msgf("key up %d", payload.Key)
} }
break break
case OP_KEY_CLK: case OP_KEY_CLK:

View File

@ -115,3 +115,14 @@ func (h *MessageHandler) controlClipboard(id string, session types.Session, payl
h.remote.WriteClipboard(payload.Text) h.remote.WriteClipboard(payload.Text)
return nil return nil
} }
func (h *MessageHandler) controlKeyboard(id string, session types.Session, payload *message.Keyboard) error {
// check if session is host
if !h.sessions.IsHost(id) {
h.logger.Debug().Str("id", id).Msg("is not the host")
return nil
}
h.remote.SetKeyboardLayout(payload.Layout)
return nil
}

View File

@ -89,6 +89,13 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
utils.Unmarshal(payload, raw, func() error { utils.Unmarshal(payload, raw, func() error {
return h.controlClipboard(id, session, payload) return h.controlClipboard(id, session, payload)
}), "%s failed", header.Event) }), "%s failed", header.Event)
case event.CONTROL_KEYBOARD:
payload := &message.Keyboard{}
return errors.Wrapf(
utils.Unmarshal(payload, raw, func() error {
return h.controlKeyboard(id, session, payload)
}), "%s failed", header.Event)
// Chat Events // Chat Events
case event.CHAT_MESSAGE: case event.CHAT_MESSAGE:

View File

@ -12,11 +12,6 @@ func (h *MessageHandler) SessionCreated(id string, session types.Session) error
return err return err
} }
// send screen current resolution
if err := h.screenResolution(id, session); err != nil {
return err
}
if session.Admin() { if session.Admin() {
// send screen configurations if admin // send screen configurations if admin
if err := h.screenConfigurations(id, session); err != nil { if err := h.screenConfigurations(id, session); err != nil {
@ -37,6 +32,11 @@ func (h *MessageHandler) SessionConnected(id string, session types.Session) erro
return err return err
} }
// send screen current resolution
if err := h.screenResolution(id, session); err != nil {
return err
}
// tell session there is a host // tell session there is a host
host, ok := h.sessions.GetHost() host, ok := h.sessions.GetHost()
if ok { if ok {

View File

@ -1,45 +0,0 @@
package keycode
import "n.eko.moe/neko/internal/types"
var LEFT_BUTTON = types.Button{
Name: "LEFT",
Code: 0,
Keysym: 1,
}
var CENTER_BUTTON = types.Button{
Name: "CENTER",
Code: 1,
Keysym: 2,
}
var RIGHT_BUTTON = types.Button{
Name: "RIGHT",
Code: 2,
Keysym: 3,
}
var SCROLL_UP_BUTTON = types.Button{
Name: "SCROLL_UP",
Code: 3,
Keysym: 4,
}
var SCROLL_DOWN_BUTTON = types.Button{
Name: "SCROLL_DOWN",
Code: 4,
Keysym: 5,
}
var SCROLL_LEFT_BUTTON = types.Button{
Name: "SCROLL_LEFT",
Code: 5,
Keysym: 6,
}
var SCROLL_RIGHT_BUTTON = types.Button{
Name: "SCROLL_RIGHT",
Code: 6,
Keysym: 7,
}

View File

@ -1,696 +0,0 @@
package keycode
import "n.eko.moe/neko/internal/types"
var BACKSPACE = types.Key{
Name: "BACKSPACE",
Value: "BackSpace",
Code: 8,
Keysym: int(0xff08),
}
var TAB = types.Key{
Name: "TAB",
Value: "Tab",
Code: 9,
Keysym: int(0xFF09),
}
var CLEAR = types.Key{
Name: "CLEAR",
Value: "Clear",
Code: 12,
Keysym: int(0xFF0B),
}
var ENTER = types.Key{
Name: "ENTER",
Value: "Enter",
Code: 13,
Keysym: int(0xFF0D),
}
var SHIFT = types.Key{
Name: "SHIFT",
Value: "Shift",
Code: 16,
Keysym: int(0xFFE1),
}
var CTRL = types.Key{
Name: "CTRL",
Value: "Ctrl",
Code: 17,
Keysym: int(0xFFE3),
}
var ALT = types.Key{
Name: "ALT",
Value: "Alt",
Code: 18,
Keysym: int(0xFFE9),
}
var PAUSE = types.Key{
Name: "PAUSE",
Value: "Pause",
Code: 19,
Keysym: int(0xFF13),
}
var CAPS_LOCK = types.Key{
Name: "CAPS_LOCK",
Value: "Caps Lock",
Code: 20,
Keysym: int(0xFFE5),
}
var ESCAPE = types.Key{
Name: "ESCAPE",
Value: "Escape",
Code: 27,
Keysym: int(0xFF1B),
}
var SPACE = types.Key{
Name: "SPACE",
Value: " ",
Code: 32,
Keysym: int(0x0020),
}
var PAGE_UP = types.Key{
Name: "PAGE_UP",
Value: "Page Up",
Code: 33,
Keysym: int(0xFF55),
}
var PAGE_DOWN = types.Key{
Name: "PAGE_DOWN",
Value: "Page Down",
Code: 34,
Keysym: int(0xFF56),
}
var END = types.Key{
Name: "END",
Value: "End",
Code: 35,
Keysym: int(0xFF57),
}
var HOME = types.Key{
Name: "HOME",
Value: "Home",
Code: 36,
Keysym: int(0xFF50),
}
var LEFT_ARROW = types.Key{
Name: "LEFT_ARROW",
Value: "Left Arrow",
Code: 37,
Keysym: int(0xFF51),
}
var UP_ARROW = types.Key{
Name: "UP_ARROW",
Value: "Up Arrow",
Code: 38,
Keysym: int(0xFF52),
}
var RIGHT_ARROW = types.Key{
Name: "RIGHT_ARROW",
Value: "Right Arrow",
Code: 39,
Keysym: int(0xFF53),
}
var DOWN_ARROW = types.Key{
Name: "DOWN_ARROW",
Value: "Down Arrow",
Code: 40,
Keysym: int(0xFF54),
}
var INSERT = types.Key{
Name: "INSERT",
Value: "Insert",
Code: 45,
Keysym: int(0xFF63),
}
var DELETE = types.Key{
Name: "DELETE",
Value: "Delete",
Code: 46,
Keysym: int(0xFFFF),
}
var KEY_0 = types.Key{
Name: "KEY_0",
Value: "0",
Code: 48,
Keysym: int(0x0030),
}
var KEY_1 = types.Key{
Name: "KEY_1",
Value: "1",
Code: 49,
Keysym: int(0x0031),
}
var KEY_2 = types.Key{
Name: "KEY_2",
Value: "2",
Code: 50,
Keysym: int(0x0032),
}
var KEY_3 = types.Key{
Name: "KEY_3",
Value: "3",
Code: 51,
Keysym: int(0x0033),
}
var KEY_4 = types.Key{
Name: "KEY_4",
Value: "4",
Code: 52,
Keysym: int(0x0034),
}
var KEY_5 = types.Key{
Name: "KEY_5",
Value: "5",
Code: 53,
Keysym: int(0x0035),
}
var KEY_6 = types.Key{
Name: "KEY_6",
Value: "6",
Code: 54,
Keysym: int(0x0036),
}
var KEY_7 = types.Key{
Name: "KEY_7",
Value: "7",
Code: 55,
Keysym: int(0x0037),
}
var KEY_8 = types.Key{
Name: "KEY_8",
Value: "8",
Code: 56,
Keysym: int(0x0038),
}
var KEY_9 = types.Key{
Name: "KEY_9",
Value: "9",
Code: 57,
Keysym: int(0x0039),
}
var KEY_A = types.Key{
Name: "KEY_A",
Value: "a",
Code: 65,
Keysym: int(0x0061),
}
var KEY_B = types.Key{
Name: "KEY_B",
Value: "b",
Code: 66,
Keysym: int(0x0062),
}
var KEY_C = types.Key{
Name: "KEY_C",
Value: "c",
Code: 67,
Keysym: int(0x0063),
}
var KEY_D = types.Key{
Name: "KEY_D",
Value: "d",
Code: 68,
Keysym: int(0x0064),
}
var KEY_E = types.Key{
Name: "KEY_E",
Value: "e",
Code: 69,
Keysym: int(0x0065),
}
var KEY_F = types.Key{
Name: "KEY_F",
Value: "f",
Code: 70,
Keysym: int(0x0066),
}
var KEY_G = types.Key{
Name: "KEY_G",
Value: "g",
Code: 71,
Keysym: int(0x0067),
}
var KEY_H = types.Key{
Name: "KEY_H",
Value: "h",
Code: 72,
Keysym: int(0x0068),
}
var KEY_I = types.Key{
Name: "KEY_I",
Value: "i",
Code: 73,
Keysym: int(0x0069),
}
var KEY_J = types.Key{
Name: "KEY_J",
Value: "j",
Code: 74,
Keysym: int(0x006a),
}
var KEY_K = types.Key{
Name: "KEY_K",
Value: "k",
Code: 75,
Keysym: int(0x006b),
}
var KEY_L = types.Key{
Name: "KEY_L",
Value: "l",
Code: 76,
Keysym: int(0x006c),
}
var KEY_M = types.Key{
Name: "KEY_M",
Value: "m",
Code: 77,
Keysym: int(0x006d),
}
var KEY_N = types.Key{
Name: "KEY_N",
Value: "n",
Code: 78,
Keysym: int(0x006e),
}
var KEY_O = types.Key{
Name: "KEY_O",
Value: "o",
Code: 79,
Keysym: int(0x006f),
}
var KEY_P = types.Key{
Name: "KEY_P",
Value: "p",
Code: 80,
Keysym: int(0x0070),
}
var KEY_Q = types.Key{
Name: "KEY_Q",
Value: "q",
Code: 81,
Keysym: int(0x0071),
}
var KEY_R = types.Key{
Name: "KEY_R",
Value: "r",
Code: 82,
Keysym: int(0x0072),
}
var KEY_S = types.Key{
Name: "KEY_S",
Value: "s",
Code: 83,
Keysym: int(0x0073),
}
var KEY_T = types.Key{
Name: "KEY_T",
Value: "t",
Code: 84,
Keysym: int(0x0074),
}
var KEY_U = types.Key{
Name: "KEY_U",
Value: "u",
Code: 85,
Keysym: int(0x0075),
}
var KEY_V = types.Key{
Name: "KEY_V",
Value: "v",
Code: 86,
Keysym: int(0x0076),
}
var KEY_W = types.Key{
Name: "KEY_W",
Value: "w",
Code: 87,
Keysym: int(0x0077),
}
var KEY_X = types.Key{
Name: "KEY_X",
Value: "x",
Code: 88,
Keysym: int(0x0078),
}
var KEY_Y = types.Key{
Name: "KEY_Y",
Value: "y",
Code: 89,
Keysym: int(0x0079),
}
var KEY_Z = types.Key{
Name: "KEY_Z",
Value: "z",
Code: 90,
Keysym: int(0x007a),
}
var WIN_LEFT = types.Key{
Name: "WIN_LEFT",
Value: "Win Left",
Code: 91,
Keysym: int(0xFFEB),
}
var WIN_RIGHT = types.Key{
Name: "WIN_RIGHT",
Value: "Win Right",
Code: 92,
Keysym: int(0xFF67),
}
var PAD_0 = types.Key{
Name: "PAD_0",
Value: "Num Pad 0",
Code: 96,
Keysym: int(0xFFB0),
}
var PAD_1 = types.Key{
Name: "PAD_1",
Value: "Num Pad 1",
Code: 97,
Keysym: int(0xFFB1),
}
var PAD_2 = types.Key{
Name: "PAD_2",
Value: "Num Pad 2",
Code: 98,
Keysym: int(0xFFB2),
}
var PAD_3 = types.Key{
Name: "PAD_3",
Value: "Num Pad 3",
Code: 99,
Keysym: int(0xFFB3),
}
var PAD_4 = types.Key{
Name: "PAD_4",
Value: "Num Pad 4",
Code: 100,
Keysym: int(0xFFB4),
}
var PAD_5 = types.Key{
Name: "PAD_5",
Value: "Num Pad 5",
Code: 101,
Keysym: int(0xFFB5),
}
var PAD_6 = types.Key{
Name: "PAD_6",
Value: "Num Pad 6",
Code: 102,
Keysym: int(0xFFB6),
}
var PAD_7 = types.Key{
Name: "PAD_7",
Value: "Num Pad 7",
Code: 103,
Keysym: int(0xFFB7),
}
var PAD_8 = types.Key{
Name: "PAD_8",
Value: "Num Pad 8",
Code: 104,
Keysym: int(0xFFB8),
}
var PAD_9 = types.Key{
Name: "PAD_9",
Value: "Num Pad 9",
Code: 105,
Keysym: int(0xFFB9),
}
var MULTIPLY = types.Key{
Name: "MULTIPLY",
Value: "*",
Code: 106,
Keysym: int(0xFFAA),
}
var ADD = types.Key{
Name: "ADD",
Value: "+",
Code: 107,
Keysym: int(0xFFAB),
}
var SUBTRACT = types.Key{
Name: "SUBTRACT",
Value: "-",
Code: 109,
Keysym: int(0xFFAD),
}
var DECIMAL = types.Key{
Name: "DECIMAL",
Value: ".",
Code: 110,
Keysym: int(0xFFAE),
}
var DIVIDE = types.Key{
Name: "DIVIDE",
Value: "/",
Code: 111,
Keysym: int(0xFFAF),
}
var KEY_F1 = types.Key{
Name: "KEY_F1",
Value: "f1",
Code: 112,
Keysym: int(0xFFBE),
}
var KEY_F2 = types.Key{
Name: "KEY_F2",
Value: "f2",
Code: 113,
Keysym: int(0xFFBF),
}
var KEY_F3 = types.Key{
Name: "KEY_F3",
Value: "f3",
Code: 114,
Keysym: int(0xFFC0),
}
var KEY_F4 = types.Key{
Name: "KEY_F4",
Value: "f4",
Code: 115,
Keysym: int(0xFFC1),
}
var KEY_F5 = types.Key{
Name: "KEY_F5",
Value: "f5",
Code: 116,
Keysym: int(0xFFC2),
}
var KEY_F6 = types.Key{
Name: "KEY_F6",
Value: "f6",
Code: 117,
Keysym: int(0xFFC3),
}
var KEY_F7 = types.Key{
Name: "KEY_F7",
Value: "f7",
Code: 118,
Keysym: int(0xFFC4),
}
var KEY_F8 = types.Key{
Name: "KEY_F8",
Value: "f8",
Code: 119,
Keysym: int(0xFFC5),
}
var KEY_F9 = types.Key{
Name: "KEY_F9",
Value: "f9",
Code: 120,
Keysym: int(0xFFC6),
}
var KEY_F10 = types.Key{
Name: "KEY_F10",
Value: "f10",
Code: 121,
Keysym: int(0xFFC7),
}
var KEY_F11 = types.Key{
Name: "KEY_F11",
Value: "f11",
Code: 122,
Keysym: int(0xFFC8),
}
var KEY_F12 = types.Key{
Name: "KEY_F12",
Value: "f12",
Code: 123,
Keysym: int(0xFFC9),
}
var NUM_LOCK = types.Key{
Name: "NUM_LOCK",
Value: "Num Lock",
Code: 144,
Keysym: int(0xFF7F),
}
var SCROLL_LOCK = types.Key{
Name: "SCROLL_LOCK",
Value: "Scroll Lock",
Code: 145,
Keysym: int(0xFF14),
}
var SEMI_COLON = types.Key{
Name: "SEMI_COLON",
Value: ";",
Code: 186,
Keysym: int(0x003b),
}
var EQUAL = types.Key{
Name: "EQUAL",
Value: "=",
Code: 187,
Keysym: int(0x003d),
}
var COMMA = types.Key{
Name: "COMMA",
Value: ",",
Code: 188,
Keysym: int(0x002c),
}
var DASH = types.Key{
Name: "DASH",
Value: "-",
Code: 189,
Keysym: int(0x002d),
}
var PERIOD = types.Key{
Name: "PERIOD",
Value: ".",
Code: 190,
Keysym: int(0x002e),
}
var FORWARD_SLASH = types.Key{
Name: "FORWARD_SLASH",
Value: "/",
Code: 191,
Keysym: int(0x002f),
}
var GRAVE = types.Key{
Name: "GRAVE",
Value: "`",
Code: 192,
Keysym: int(0x0060),
}
var OPEN_BRACKET = types.Key{
Name: "OPEN_BRACKET",
Value: "[",
Code: 219,
Keysym: int(0x005b),
}
var BACK_SLASH = types.Key{
Name: "BACK_SLASH",
Value: "\\",
Code: 220,
Keysym: int(0x005c),
}
var CLOSE_BRAKET = types.Key{
Name: "CLOSE_BRAKET",
Value: "]",
Code: 221,
Keysym: int(0x005d),
}
var SINGLE_QUOTE = types.Key{
Name: "SINGLE_QUOTE",
Value: "'",
Code: 222,
Keysym: int(0x0022),
}

View File

@ -89,17 +89,34 @@ void XScroll(int x, int y) {
} }
void XButton(unsigned int button, int down) { void XButton(unsigned int button, int down) {
if (button != 0) {
Display *display = getXDisplay(); Display *display = getXDisplay();
XTestFakeButtonEvent(display, button, down, CurrentTime); XTestFakeButtonEvent(display, button, down, CurrentTime);
XSync(display, 0); XSync(display, 0);
} }
}
void XKey(unsigned long key, int down) { void XKey(unsigned long key, int down) {
if (key != 0) {
Display *display = getXDisplay(); Display *display = getXDisplay();
KeyCode code = XKeysymToKeycode(display, key); KeyCode code = XKeysymToKeycode(display, key);
// Map non-existing keysyms to new keycodes
if(code == 0) {
int min, max, numcodes;
XDisplayKeycodes(display, &min, &max);
XGetKeyboardMapping(display, min, max-min, &numcodes);
code = (max-min+1)*numcodes;
KeySym keysym_list[numcodes];
for(int i=0;i<numcodes;i++) keysym_list[i] = key;
XChangeKeyboardMapping(display, code, numcodes, keysym_list, 1);
}
XTestFakeKeyEvent(display, code, down, CurrentTime); XTestFakeKeyEvent(display, code, down, CurrentTime);
XSync(display, 0); XSync(display, 0);
} }
}
void XClipboardSet(char *src) { void XClipboardSet(char *src) {
clipboard_c *cb = getClipboard(); clipboard_c *cb = getClipboard();
@ -148,3 +165,10 @@ short XGetScreenRate() {
XRRScreenConfiguration *conf = XRRGetScreenInfo(display, RootWindow(display, 0)); XRRScreenConfiguration *conf = XRRGetScreenInfo(display, RootWindow(display, 0));
return XRRConfigCurrentRate(conf); return XRRConfigCurrentRate(conf);
} }
void SetKeyboardLayout(char *layout) {
// TOOD: refactor, use native API.
char cmd[13] = "setxkbmap ";
strncat(cmd, layout, 2);
system(cmd);
}

View File

@ -1,9 +1,3 @@
// NOTE: I have no fucking clue what I'm doing with this,
// it works, but I am positive I'm doing this very wrong...
// should I be freeing these strings? does go gc them?
// pretty sure this *isn't* thread safe either.... /shrug
// if you know a better way to get this done *please* make a pr <3
package xorg package xorg
/* /*
@ -19,127 +13,18 @@ import (
"sync" "sync"
"time" "time"
"unsafe" "unsafe"
"regexp"
"n.eko.moe/neko/internal/types" "n.eko.moe/neko/internal/types"
"n.eko.moe/neko/internal/xorg/keycode"
) )
var ScreenConfigurations = make(map[int]types.ScreenConfiguration) var ScreenConfigurations = make(map[int]types.ScreenConfiguration)
var debounce = make(map[int]time.Time) var debounce_button = make(map[int]time.Time)
var buttons = make(map[int]types.Button) var debounce_key = make(map[uint64]time.Time)
var keys = make(map[int]types.Key)
var mu = sync.Mutex{} var mu = sync.Mutex{}
func init() { func init() {
keys[keycode.BACKSPACE.Code] = keycode.BACKSPACE
keys[keycode.TAB.Code] = keycode.TAB
keys[keycode.CLEAR.Code] = keycode.CLEAR
keys[keycode.ENTER.Code] = keycode.ENTER
keys[keycode.SHIFT.Code] = keycode.SHIFT
keys[keycode.CTRL.Code] = keycode.CTRL
keys[keycode.ALT.Code] = keycode.ALT
keys[keycode.PAUSE.Code] = keycode.PAUSE
keys[keycode.CAPS_LOCK.Code] = keycode.CAPS_LOCK
keys[keycode.ESCAPE.Code] = keycode.ESCAPE
keys[keycode.SPACE.Code] = keycode.SPACE
keys[keycode.PAGE_UP.Code] = keycode.PAGE_UP
keys[keycode.PAGE_DOWN.Code] = keycode.PAGE_DOWN
keys[keycode.END.Code] = keycode.END
keys[keycode.HOME.Code] = keycode.HOME
keys[keycode.LEFT_ARROW.Code] = keycode.LEFT_ARROW
keys[keycode.UP_ARROW.Code] = keycode.UP_ARROW
keys[keycode.RIGHT_ARROW.Code] = keycode.RIGHT_ARROW
keys[keycode.DOWN_ARROW.Code] = keycode.DOWN_ARROW
keys[keycode.INSERT.Code] = keycode.INSERT
keys[keycode.DELETE.Code] = keycode.DELETE
keys[keycode.KEY_0.Code] = keycode.KEY_0
keys[keycode.KEY_1.Code] = keycode.KEY_1
keys[keycode.KEY_2.Code] = keycode.KEY_2
keys[keycode.KEY_3.Code] = keycode.KEY_3
keys[keycode.KEY_4.Code] = keycode.KEY_4
keys[keycode.KEY_5.Code] = keycode.KEY_5
keys[keycode.KEY_6.Code] = keycode.KEY_6
keys[keycode.KEY_7.Code] = keycode.KEY_7
keys[keycode.KEY_8.Code] = keycode.KEY_8
keys[keycode.KEY_9.Code] = keycode.KEY_9
keys[keycode.KEY_A.Code] = keycode.KEY_A
keys[keycode.KEY_B.Code] = keycode.KEY_B
keys[keycode.KEY_C.Code] = keycode.KEY_C
keys[keycode.KEY_D.Code] = keycode.KEY_D
keys[keycode.KEY_E.Code] = keycode.KEY_E
keys[keycode.KEY_F.Code] = keycode.KEY_F
keys[keycode.KEY_G.Code] = keycode.KEY_G
keys[keycode.KEY_H.Code] = keycode.KEY_H
keys[keycode.KEY_I.Code] = keycode.KEY_I
keys[keycode.KEY_J.Code] = keycode.KEY_J
keys[keycode.KEY_K.Code] = keycode.KEY_K
keys[keycode.KEY_L.Code] = keycode.KEY_L
keys[keycode.KEY_M.Code] = keycode.KEY_M
keys[keycode.KEY_N.Code] = keycode.KEY_N
keys[keycode.KEY_O.Code] = keycode.KEY_O
keys[keycode.KEY_P.Code] = keycode.KEY_P
keys[keycode.KEY_Q.Code] = keycode.KEY_Q
keys[keycode.KEY_R.Code] = keycode.KEY_R
keys[keycode.KEY_S.Code] = keycode.KEY_S
keys[keycode.KEY_T.Code] = keycode.KEY_T
keys[keycode.KEY_U.Code] = keycode.KEY_U
keys[keycode.KEY_V.Code] = keycode.KEY_V
keys[keycode.KEY_W.Code] = keycode.KEY_W
keys[keycode.KEY_X.Code] = keycode.KEY_X
keys[keycode.KEY_Y.Code] = keycode.KEY_Y
keys[keycode.KEY_Z.Code] = keycode.KEY_Z
keys[keycode.WIN_LEFT.Code] = keycode.WIN_LEFT
keys[keycode.WIN_RIGHT.Code] = keycode.WIN_RIGHT
keys[keycode.PAD_0.Code] = keycode.PAD_0
keys[keycode.PAD_1.Code] = keycode.PAD_1
keys[keycode.PAD_2.Code] = keycode.PAD_2
keys[keycode.PAD_3.Code] = keycode.PAD_3
keys[keycode.PAD_4.Code] = keycode.PAD_4
keys[keycode.PAD_5.Code] = keycode.PAD_5
keys[keycode.PAD_6.Code] = keycode.PAD_6
keys[keycode.PAD_7.Code] = keycode.PAD_7
keys[keycode.PAD_8.Code] = keycode.PAD_8
keys[keycode.PAD_9.Code] = keycode.PAD_9
keys[keycode.MULTIPLY.Code] = keycode.MULTIPLY
keys[keycode.ADD.Code] = keycode.ADD
keys[keycode.SUBTRACT.Code] = keycode.SUBTRACT
keys[keycode.DECIMAL.Code] = keycode.DECIMAL
keys[keycode.DIVIDE.Code] = keycode.DIVIDE
keys[keycode.KEY_F1.Code] = keycode.KEY_F1
keys[keycode.KEY_F2.Code] = keycode.KEY_F2
keys[keycode.KEY_F3.Code] = keycode.KEY_F3
keys[keycode.KEY_F4.Code] = keycode.KEY_F4
keys[keycode.KEY_F5.Code] = keycode.KEY_F5
keys[keycode.KEY_F6.Code] = keycode.KEY_F6
keys[keycode.KEY_F7.Code] = keycode.KEY_F7
keys[keycode.KEY_F8.Code] = keycode.KEY_F8
keys[keycode.KEY_F9.Code] = keycode.KEY_F9
keys[keycode.KEY_F10.Code] = keycode.KEY_F10
keys[keycode.KEY_F11.Code] = keycode.KEY_F11
keys[keycode.KEY_F12.Code] = keycode.KEY_F12
keys[keycode.NUM_LOCK.Code] = keycode.NUM_LOCK
keys[keycode.SCROLL_LOCK.Code] = keycode.SCROLL_LOCK
keys[keycode.SEMI_COLON.Code] = keycode.SEMI_COLON
keys[keycode.EQUAL.Code] = keycode.EQUAL
keys[keycode.COMMA.Code] = keycode.COMMA
keys[keycode.DASH.Code] = keycode.DASH
keys[keycode.PERIOD.Code] = keycode.PERIOD
keys[keycode.FORWARD_SLASH.Code] = keycode.FORWARD_SLASH
keys[keycode.GRAVE.Code] = keycode.GRAVE
keys[keycode.OPEN_BRACKET.Code] = keycode.OPEN_BRACKET
keys[keycode.BACK_SLASH.Code] = keycode.BACK_SLASH
keys[keycode.CLOSE_BRAKET.Code] = keycode.CLOSE_BRAKET
keys[keycode.SINGLE_QUOTE.Code] = keycode.SINGLE_QUOTE
buttons[keycode.LEFT_BUTTON.Code] = keycode.LEFT_BUTTON
buttons[keycode.CENTER_BUTTON.Code] = keycode.CENTER_BUTTON
buttons[keycode.RIGHT_BUTTON.Code] = keycode.RIGHT_BUTTON
buttons[keycode.SCROLL_UP_BUTTON.Code] = keycode.SCROLL_UP_BUTTON
buttons[keycode.SCROLL_DOWN_BUTTON.Code] = keycode.SCROLL_DOWN_BUTTON
buttons[keycode.SCROLL_LEFT_BUTTON.Code] = keycode.SCROLL_LEFT_BUTTON
buttons[keycode.SCROLL_RIGHT_BUTTON.Code] = keycode.SCROLL_RIGHT_BUTTON
C.XGetScreenConfigurations() C.XGetScreenConfigurations()
} }
@ -167,80 +52,60 @@ func Scroll(x, y int) {
C.XScroll(C.int(x), C.int(y)) C.XScroll(C.int(x), C.int(y))
} }
func ButtonDown(code int) (*types.Button, error) { func ButtonDown(code int) error {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
button, ok := buttons[code] if _, ok := debounce_button[code]; ok {
if !ok { return fmt.Errorf("debounced button %v", code)
return nil, fmt.Errorf("invalid button %v", code)
} }
if _, ok := debounce[code]; ok { debounce_button[code] = time.Now()
return nil, fmt.Errorf("debounced button %v(%v)", button.Name, code)
C.XButton(C.uint(code), C.int(1))
return nil
} }
debounce[code] = time.Now() func KeyDown(code uint64) error {
C.XButton(C.uint(button.Keysym), C.int(1))
return &button, nil
}
func KeyDown(code int) (*types.Key, error) {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
key, ok := keys[code] if _, ok := debounce_key[code]; ok {
if !ok { return fmt.Errorf("debounced key %v", code)
return nil, fmt.Errorf("invalid key %v", code)
} }
if _, ok := debounce[code]; ok { debounce_key[code] = time.Now()
return nil, fmt.Errorf("debounced key %v(%v)", key.Name, code)
C.XKey(C.ulong(code), C.int(1))
return nil
} }
debounce[code] = time.Now() func ButtonUp(code int) error {
C.XKey(C.ulong(key.Keysym), C.int(1))
return &key, nil
}
func ButtonUp(code int) (*types.Button, error) {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
button, ok := buttons[code] if _, ok := debounce_button[code]; !ok {
if !ok { return fmt.Errorf("debounced button %v", code)
return nil, fmt.Errorf("invalid button %v", code)
} }
if _, ok := debounce[code]; !ok { delete(debounce_button, code)
return nil, fmt.Errorf("debounced button %v(%v)", button.Name, code)
C.XButton(C.uint(code), C.int(0))
return nil
} }
delete(debounce, code) func KeyUp(code uint64) error {
C.XButton(C.uint(button.Keysym), C.int(0))
return &button, nil
}
func KeyUp(code int) (*types.Key, error) {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
key, ok := keys[code] if _, ok := debounce_key[code]; !ok {
if !ok { return fmt.Errorf("debounced key %v", code)
return nil, fmt.Errorf("invalid key %v", code)
} }
if _, ok := debounce[code]; !ok { delete(debounce_key, code)
return nil, fmt.Errorf("debounced key %v(%v)", key.Name, code)
}
delete(debounce, code) C.XKey(C.ulong(code), C.int(0))
return nil
C.XKey(C.ulong(key.Keysym), C.int(0))
return &key, nil
} }
func ReadClipboard() string { func ReadClipboard() string {
@ -264,31 +129,35 @@ func WriteClipboard(data string) {
} }
func ResetKeys() { func ResetKeys() {
for key := range debounce { for code := range debounce_button {
if key < 8 { ButtonUp(code)
ButtonUp(key)
} else {
KeyUp(key)
}
delete(debounce, key) delete(debounce_button, code)
}
for code := range debounce_key {
KeyUp(code)
delete(debounce_key, code)
} }
} }
func CheckKeys(duration time.Duration) { func CheckKeys(duration time.Duration) {
t := time.Now() t := time.Now()
for key, start := range debounce { for code, start := range debounce_button {
if t.Sub(start) < duration { if t.Sub(start) < duration {
continue continue
} }
ButtonUp(code)
if key < 8 { delete(debounce_button, code)
ButtonUp(key)
} else {
KeyUp(key)
} }
for code, start := range debounce_key {
if t.Sub(start) < duration {
continue
}
KeyUp(code)
delete(debounce, key) delete(debounce_key, code)
} }
} }
@ -342,6 +211,20 @@ func GetScreenSize() *types.ScreenSize {
return nil return nil
} }
func SetKeyboardLayout(layout string) {
mu.Lock()
defer mu.Unlock()
if !regexp.MustCompile(`^[a-zA-Z]+$`).MatchString(layout) {
return
}
layoutUnsafe := C.CString(layout)
defer C.free(unsafe.Pointer(layoutUnsafe))
C.SetKeyboardLayout(layoutUnsafe)
}
//export goCreateScreenSize //export goCreateScreenSize
func goCreateScreenSize(index C.int, width C.int, height C.int, mwidth C.int, mheight C.int) { func goCreateScreenSize(index C.int, width C.int, height C.int, mwidth C.int, mheight C.int) {
ScreenConfigurations[int(index)] = types.ScreenConfiguration{ ScreenConfigurations[int(index)] = types.ScreenConfiguration{

View File

@ -38,5 +38,7 @@
void XDisplayClose(void); void XDisplayClose(void);
void XDisplaySet(char *input); void XDisplaySet(char *input);
void SetKeyboardLayout(char *layout);
#endif #endif