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