diff --git a/C#Client/TutorRemoteClient.exe b/C#Client/TutorRemoteClient.exe
new file mode 100644
index 0000000..7b1af29
Binary files /dev/null and b/C#Client/TutorRemoteClient.exe differ
diff --git a/JS/index.html b/JS/index.html
index a047418..903134c 100644
--- a/JS/index.html
+++ b/JS/index.html
@@ -4,294 +4,31 @@
TutorRemote
-
-
-
-
-
-
-
-
-
-
-
+
+
-
+
TutorRemote
-
Connecting...
+
Output Token:
Your Token is: undefined
+
Connecting...
-
+
Send Text
-
-
Input Keyboard Layout
-
Disable mapping
-
Ansi
-
German
-
+
+
+
+
+
+
+
+
+
+
diff --git a/JS/remote.js b/JS/remote.js
new file mode 100644
index 0000000..307576e
--- /dev/null
+++ b/JS/remote.js
@@ -0,0 +1,224 @@
+var server = "ws://_server_:9160/";
+
+var socket;
+var conState;
+var dest = "";
+
+var lastReq = 0;
+
+var sCharMap = {};
+sCharMap["8"] = "Bksp";
+sCharMap["9"] = "Tab";
+sCharMap["13"] = "Enter";
+sCharMap["27"] = "Esc";
+sCharMap["33"] = "PgUp";
+sCharMap["34"] = "PgDn";
+sCharMap["35"] = "End";
+sCharMap["36"] = "Home";
+sCharMap["37"] = "Left";
+sCharMap["38"] = "Up";
+sCharMap["39"] = "Right";
+sCharMap["40"] = "Down";
+sCharMap["45"] = "Ins";
+sCharMap["46"] = "Del";
+
+
+function isCombo(event){
+ return (event.ctrlKey && !event.altKey) || (!event.ctrlKey && event.altKey) ||
+ (
+ (event.charCode == undefined || event.charCode == 0) &&
+ (event.key == undefined || event.key.length > 1) &&
+ (""+event.keyCode) in sCharMap
+ );
+}
+function sendKey(event){
+ if(event.target.id == "textinput" || event.target.id == "outputT")
+ return;
+
+ //document.getElementById("textinput").value += "\nKey: "+isCombo(event)+" "+event.keyCode+" "+event.charCode+" "+event.key+" "+event.keyIdentifier;
+
+ if(isCombo(event)) // goes to sendCombo anyway
+ return;
+
+ //prevent browser interpretation of the keypresses in some cases
+ event.preventDefault();
+
+ var kString = "";
+ var tString = "";
+ if(event.key != undefined){
+ tString = "key";
+ if(event.key.length == 1){
+ var ch = event.key.charCodeAt(0).toString(16);
+ kString = ("U+" + "0000".substring(ch.length) + ch).toUpperCase();
+ }
+ else{
+ return; // yes, that's a single-Key-Combo :(
+ }
+ }else if(event.charCode != undefined && event.charCode != 0){
+ tString = "charCode";
+ var ch = event.charCode.toString(16);
+ kString = ("U+" + "0000".substring(ch.length) + ch).toUpperCase();
+ }else{
+ console.log("Could not get Key Type");
+ return;
+ }
+ console.log("Key: "+tString+": "+kString);
+ send("CHAR: " + kString);
+}
+function sendCombo(event){
+ if(event.target.id == "textinput" || event.target.id == "outputT")
+ return;
+
+ //document.getElementById("textinput").value += "\nCmb: "+isCombo(event)+" "+event.keyCode+" "+event.charCode+" "+event.key+" "+event.keyIdentifier;
+
+ if(!isCombo(event)) // goes to sendKey anyway
+ return;
+
+ //prevent browser interpretation of the keypresses in some cases
+ event.preventDefault();
+
+ var kString = "";
+ var tString = "";
+ if(""+event.keyCode in sCharMap){
+ tString = "map";
+ kString = sCharMap[""+event.keyCode];
+ }else if(event.ctrlKey || event.metaKey){
+ tString = "ctrl";
+ switch(String.fromCharCode(event.keyCode)){
+ case 'C': kString = 'Copy'; break;
+ case 'X': kString = 'Cut'; break;
+ case 'V': kString = 'Paste'; break;
+ case 'Z': kString = 'Back'; break;
+ case 'Y': kString = 'Forward'; break;
+ case 'A': kString = 'All'; break;
+ case 'N': kString = 'New'; break;
+ default: return;
+ }
+ }else if(event.altKey){
+ tString = "alt";
+ switch(String.fromCharCode(event.keyCode)){
+ default: return;
+ }
+ }else{
+ console.log("Could not get Key Type");
+ return;
+ }
+ var mod = (event.ctrlKey?"[CTRL]":"") + (event.shiftKey?"[SHFT]":"");
+
+ console.log("Combo: "+tString+": "+mod+kString);
+ send("CHAR: " + mod + " " + kString);
+}
+function sendFallback(){
+ if(document.getElementById("keyboardFetcher").value == "")
+ return;
+ //document.getElementById("textinput").value += "\nFallback!";
+ send("TEXT: " + document.getElementById("keyboardFetcher").value);
+ document.getElementById("keyboardFetcher").value = "";
+}
+function sendCursor(cur){
+ send("CHAR: " + sCharMap[""+(37+cur)]);
+}
+function sendBksp(cur){
+ send("CHAR: " + sCharMap["8"]);
+}
+function setUp(){
+ conState = document.getElementById("conState");
+ var timeout = null;
+
+ socket = new WebSocket(server, "tutorremote");
+ socket.onopen = function (event) {
+ socket.send("INPUTINIT");
+ conState.className = "searching";
+ conState.innerHTML = "Connecting to Output Token";
+ testToken();
+ };
+ socket.onerror = function (error) {
+ conState.className = "error";
+ conState.innerHTML = "Error... Connection failed";
+ };
+ socket.onclose = function (event) {
+ conState.className = "closed";
+ conState.innerHTML = "Connection closed";
+ };
+ socket.onmessage = function (event) {
+ var ctrl = event.data.split(": ");
+ switch(ctrl[1]){
+ case "TOKN": updateToken(ctrl[2]); break;
+ case "TICK":
+ window.clearTimeout(timeout);
+ addClass(document.getElementById('keyboardFetcher'), 'active');
+ timeout = window.setTimeout(function(){
+ conState.className = "ready";
+ conState.innerHTML = "Token OK!";
+ rmClass(document.getElementById('keyboardFetcher'), 'active');
+ }, 2000)
+ conState.className = "active";
+ conState.innerHTML = "Active! You can type now!";
+ rmClass(document.getElementById('outputT'), 'needed');
+ break;
+ case "DING":
+ conState.className = "ready";
+ conState.innerHTML = "Token OK";
+ rmClass(document.getElementById('outputT'), 'needed');
+ console.log(ctrl[2]);
+ break;
+ case "NOPE":
+ conState.className = "searching";
+ conState.innerHTML = "Connecting to Output Token";
+ addClass(document.getElementById('outputT'), 'needed');
+ console.log("NOPE: "+ctrl[2]);
+ break;
+ default: break;
+ }
+ //console.log(" --------- " + event.data);
+ };
+}
+function close(){
+ socket.close();
+ console.log("end");
+}
+function updateToken(token){
+ document.getElementById("tokenField").innerHTML = token;
+}
+
+function testToken(){
+ dest = document.getElementById('outputT').value.toUpperCase();
+ send("TEST: Blablub");
+ console.log("TEST");
+ conState.className = "searching";
+ conState.innerHTML = "Connecting to Output Token";
+ addClass(document.getElementById('outputT'), 'needed');
+}
+function requestActivation(){
+ var time = new Date().getTime();
+ if(time - lastReq < 5000) return;
+ lastReq = time;
+ send("SNMA: Senpai Notice Me Already!");
+ console.log("Senpai Notice Me Already!");
+ conState.innerHTML = "Requesting Activation...";
+}
+function sendText(){
+ send("TEXT: " + document.getElementById("textinput").value);
+ console.log("TEXT");
+ document.getElementById("textinput").value = "";
+}
+function sendReset(){
+ send("RESETMOD");
+ console.log("RESETMOD");
+}
+function send(msg){
+ socket.send(dest+": "+msg);
+ addClass(conState, "send");
+ window.setTimeout(function(){rmClass(conState, 'send');}, 10);
+}
+
+function addClass(el, cl){
+ if(el.className.indexOf(cl) >= 0) return;
+ el.className += " "+cl;
+}
+function rmClass(el, cl){
+ el.className = el.className.replace(cl, '').trim();
+}
+
+window.onload = setUp;
+window.onunload = close;
diff --git a/JS/style.css b/JS/style.css
new file mode 100644
index 0000000..25df7b3
--- /dev/null
+++ b/JS/style.css
@@ -0,0 +1,143 @@
+h1, h3 {
+ text-align: center;
+}
+h3 {
+ margin-top: 2em;
+}
+
+div.floatEnd {
+ clear: both;
+}
+div#content {
+ margin: 2%;
+ margin-top: 3em;
+ text-align: center;
+}
+
+div#output, div#token, div#conState {
+ box-sizing: border-box;
+ margin: 0 2%;
+ padding: 1em .5em;
+ width: 30%;
+
+ text-align: center;
+ vertical-align: middle;
+ border: 1px solid #888;
+ border-radius: .3em;
+}
+div#output {
+ float: left;
+ background-color: #eee;
+}
+div#output #outputT {
+ display: inline-block;
+ box-sizing: border-box;
+ width: 5em;
+ height: 2em;
+ padding: 0 .5em;
+ text-align: center;
+ font-size: inherit;
+}
+div#output #outputT.needed {
+ background-color: #faa;
+}
+div#token {
+ float: right;
+ background-color: #eee;
+}
+div#token #tokenField {
+ display: inline-block;
+ box-sizing: border-box;
+ padding: .5em .5em;
+ height: 2em;
+ text-align: center;
+ background-color: #fafafa;
+}
+div#conState {
+ margin-left: auto;
+ margin-right: auto;
+
+ padding: 1.5em .6em;
+ line-height: 1em;
+
+ cursor: pointer;
+
+ -webkit-transition: border-color .2s;
+ transition: border-color .2s;
+}
+
+div#conState.connect {
+ background-color: #fda;
+}
+div#conState.searching {
+ background-color: #ffa;
+}
+div#conState.ready {
+ background-color: #dfa;
+}
+div#conState.active {
+ background-color: #afa;
+}
+div#conState.error {
+ background-color: #faa;
+}
+div#conState.closed {
+ background-color: #aaa;
+}
+
+div#conState.send {
+ border-color: #ff0;
+ -webkit-transition: border-color 0s;
+ transition: border-color 0s;
+}
+
+textarea#textinput {
+ box-sizing: border-box;
+ width: 100%;
+}
+input#keyboardFetcher {
+ box-sizing: border-box;
+ text-align: center;
+ width: 100%;
+}
+input#keyboardFetcher.active {
+ background-color: #efe;
+}
+div#lowerC {
+ display: inline-block;
+ margin: 1em 0;
+}
+div#lowerC > div{
+ vertical-align: middle;
+ display: inline-block;
+}
+div#lowerC > button{
+ vertical-align: middle;
+ display: inline-block;
+}
+button#sendText {
+ display: block;
+ padding: .5em 2em;
+}
+
+div#cursor {
+}
+div#cursor button, button#bksp {
+ background-color: transparent;
+ border: none;
+ font-weight: bold;
+ font-size: 1.5em;
+}
+div#cursor button:active, button#bksp:active {
+ color: #680;
+}
+div#cursor #up, div#cursor #down {
+ display: block;
+ width: 100%;
+ margin: -.2em 0 -.15em;
+}
+div#cursor #left, div#cursor #right {
+ display: inline-block;
+ padding: 0 0.5em;
+ margin: 0 0;
+}
\ No newline at end of file
diff --git a/Server/TutorRemote.hs b/Server/TutorRemote.hs
index f53feb3..dadf3e6 100644
--- a/Server/TutorRemote.hs
+++ b/Server/TutorRemote.hs
@@ -1,4 +1,4 @@
-module Main where
+module Main where
import Control.Exception (finally)
import Control.Monad (forM_, forever, when, liftM)
@@ -14,24 +14,41 @@ import qualified Network.WebSockets as WS
type Token = String
-type Client = (Either Token Token, WS.Connection)
+data InOut = Input | Output deriving (Read, Show, Eq)
+
+type Client = (Token, InOut, WS.Connection)
+tokenOf :: Client -> Token
+tokenOf (t,_,_) = t
--(Clientlist, Filtertoken)
-type ServerState = ([Client], Maybe Token)
+type ServerState = [Client]
newServerState :: ServerState
-newServerState = ([], Nothing)
+newServerState = []
+
+tokenLength = 6
removeClient :: Client -> ServerState -> ServerState
-removeClient (tkn, _) (cs, filtr) = (filter ((/= tkn) . fst) cs, filtr)
+removeClient (tkn, _, _) cs = filter ((/= tkn) . tokenOf) cs
-sendToReceivers :: T.Text -> ServerState -> IO ()
-sendToReceivers message (cs, filtr) = forM_ cs (\(tkn, conn) ->when (isRight tkn) (WS.sendTextData conn message))
+sendToReceivers :: Token -> Token -> InOut -> T.Text -> ServerState -> IO ()
+sendToReceivers to from inout message cs = forM_ cs (\(tkn, io, conn) -> when (inout /= io && tkn == to) (WS.sendTextData conn (T.pack $ from ++ ": " ++ T.unpack message)))
-main :: IO ()
-main = do
- state <- newMVar newServerState
- WS.runServer "0.0.0.0" 9160 $ application state
+consList = ['F','H','K','L','M','P','R','S','T','W','X','Z']
+vocList = ['A','E','I','O','U']
+
+makeToken l = makeToken' True l
+ where makeToken' c (x:xs) = if c then consList !! x : makeToken' False xs
+ else vocList !! (x `mod` length vocList) : makeToken' True xs
+
+getNewToken state = do
+ cs <- readMVar state
+ tokGen <- liftM (randomRs (0, length consList - 1)) getStdGen
+ let newToken = makeToken tokGen
+ let short = take tokenLength newToken
+ case findIndex (\(t,_,_) -> short == t) cs of
+ (Just _) -> getNewToken state
+ (Nothing) -> return short
application :: MVar ServerState -> WS.ServerApp
application state pending = do
@@ -41,36 +58,36 @@ application state pending = do
if msg /= T.pack "OUTPUTINIT" && msg /= T.pack "INPUTINIT"
then WS.sendTextData conn (T.pack "Wrong opening statement" :: T.Text)
else do
- newToken <- getNewToken state (msg == T.pack "OUTPUTINIT")
- let client = ((if msg == T.pack "OUTPUTINIT" then Right else Left) newToken,conn)
- flip finally (disconnect client) $ do
- modifyMVar_ state (\(cs, filtr) -> return (client:cs, filtr) )
- WS.sendTextData conn (T.pack ("TOKN: " ++ newToken))
- putStrLn ((show.fst) client ++ " connected")
- (_, filtr) <- readMVar state
- when ((isRight.fst) client) (WS.sendTextData conn (T.pack ("FLUP: " ++ fromMaybe "" filtr)))
+ newToken <- getNewToken state
+ let client = (newToken, (if msg == T.pack "OUTPUTINIT" then Output else Input), conn)
+ flip finally (disconnect client) $
+ do
+ modifyMVar_ state (\cs -> return (client:cs) )
+ WS.sendTextData conn (T.pack ((take tokenLength $ repeat '0') ++ ": TOKN: " ++ newToken))
+ putStrLn ((show.tokenOf) client ++ " connected")
relay conn state client
where
- disconnect client = modifyMVar state (\s -> let s' = removeClient client s in return (s', s')) >> putStrLn ((show.fst) client ++ " disconnected")
- getNewToken state right = do
- (cs, filtr) <- readMVar state
- newToken <- liftM (randomRs ('A','Z')) getStdGen
- let short = take 4 newToken
- case findIndex (\(t,c) -> (if right then Right else Left) short == t) cs of
- (Just _) -> getNewToken state right
- (Nothing) -> return short
+ disconnect client = modifyMVar state (\s -> let s' = removeClient client s in return (s', s')) >> putStrLn ((show.tokenOf) client ++ " disconnected")
+
relay :: WS.Connection -> MVar ServerState -> Client -> IO ()
-relay conn state (tkn, _) = forever $ do
+relay conn state (tkn, inout, _) = forever $ do
msg <- WS.receiveData conn
- (putStrLn. T.unpack) msg
- case tkn of
- (Left tkn) -> do
- (cls, filtr) <- readMVar state
- when (maybe True (tkn ==) filtr) (sendToReceivers msg (cls, filtr))
- (Right tkn) -> when (T.pack "FLUP: " `T.isPrefixOf` msg) ( do
- let newFltr = T.drop 6 msg
- modifyMVar_ state (\(cs, filtr) -> return (cs, if T.length newFltr > 0 then (Just. T.unpack) newFltr else Nothing))
- (cls, filtr) <- readMVar state
- sendToReceivers msg (filter ((/=)(Right tkn).fst) cls, filtr)
- )
+ cls <- readMVar state
+ let (to, t1) = T.breakOn (T.pack ": ") msg
+ let tot = T.unpack to
+ let text = T.drop 2 t1
+ putStrLn (tkn ++ " -> " ++ tot ++ " : " ++ (T.unpack text))
+ if any (\(tkn, io, conn) -> (inout /= io && tkn == tot)) cls then
+ sendToReceivers tot tkn inout text cls
+ else
+ WS.sendTextData conn (T.pack ((take tokenLength $ repeat '0') ++ ": NOPE: No Endpoint"))
+
+
+
+
+
+main :: IO ()
+main = do
+ state <- newMVar newServerState
+ WS.runServer "0.0.0.0" 9160 $ application state
\ No newline at end of file