diff --git a/README.md b/README.md index ddc2ae4..fe0df4e 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,9 @@ HamClock Remote Access - access HamClock remotely This is currently in early development. + +## Client +The client is in the `client` directory. + +## Server +The server is in the `server` directory. diff --git a/app.py b/app.py deleted file mode 100644 index 09edec9..0000000 --- a/app.py +++ /dev/null @@ -1,11 +0,0 @@ -from flask import Flask, request, Response -import os -import requests -import base64 -#import hcapi -app = Flask(__name__) - -@app.route('/client') -def client(): - with open('client.html') as f: - return f.read() diff --git a/client.html b/client.html deleted file mode 100644 index 3b32914..0000000 --- a/client.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - HamClock - - - - -
-

HamClock Remote Access

-
-
- - -
-
- - - - diff --git a/client/client.css b/client/client.css new file mode 100644 index 0000000..7347177 --- /dev/null +++ b/client/client.css @@ -0,0 +1,48 @@ +body { + background-color: black; + color: white; + font-family: sans-serif; + font-size: 16pt; +} +input { + background-color: #222222; + border: none; + color: white; + font-size: 16pt; + text-align: left !important; +} +button { + background-color: #00FF00; + border: none; + font-size: 16pt; +} +.loginline { + padding: 3px; +} +form { + float: left; +} +form * { + text-align: right; +} +#errorbox { + font-size: 200%; + text-align: center +} +#holder { + margin: 0; + padding: 0; + width: 100vw; + height: 100vh; + text-align: center +} +#holder * { + margin: 0 auto; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} +#errorbox * { + position: static; +} diff --git a/client/client.html b/client/client.html new file mode 100644 index 0000000..845b287 --- /dev/null +++ b/client/client.html @@ -0,0 +1,22 @@ + + + + HamClock + + + +
+

HamClock Remote Access

+
+
+
+
+
+
+ + + + diff --git a/client/client.js b/client/client.js new file mode 100644 index 0000000..adeae91 --- /dev/null +++ b/client/client.js @@ -0,0 +1,122 @@ +function resize() { + let bodyWidth = window.innerWidth + let maxWidth = Math.min(bodyWidth - 60, 800) + let bodyHeight = window.innerHeight + let maxHeight = Math.min(bodyHeight - 60, 480) + let equivalentHeight = maxWidth * 0.6 + if (equivalentHeight >= maxHeight) { + displayHeight = Math.round(maxHeight) + displayWidth = Math.round(maxHeight * (5/3)) + } else { + displayWidth = Math.round(maxWidth) + displayHeight = Math.round(maxWidth * 0.6) + } + canvas.style.width = displayWidth + 'px' + canvas.style.height = displayHeight + 'px' + errorbox.style.width = displayWidth + 'px' + errorbox.style.height = displayHeight + 'px' +} + + +function setup() { + ws = new WebSocket(localStorage.getItem('url')) + ws.onmessage = function(e) { + window.m = e + let type, pos, x, y, data, img, code, msg + type = e.data.split('%')[0] + if ( type == 'pic' ) { + pos = e.data.split('%')[1] + x = Number(pos.split('x')[0]) + y = Number(pos.split('x')[1]) + data = e.data.split('%')[2] + img = new Image() + img.src = data + img.addEventListener("load", function(){ + ctx.drawImage(img,x,y) + }) + errorbox.style.display = 'none' + canvas.style.display = 'block' + } else if ( type == 'err' ) { + code = e.data.split('%')[1] + msg = e.data.split('%')[2] + errorbox.textContent = msg + errorbox.style.display = 'block' + canvas.style.display = 'none' + console.error('Server reported error: ' + code + ': ' + msg) + if (code[0] == '*') { + errorbox.innerHTML = msg + '
Please refresh page' + ws.onclose = function(e){} + ws.onerror = function(e){} + } + } else if (type == 'ack') { + ws.send('ack') + } + } + ws.onerror = function(e){ + errorbox.innerHTML = 'Connection lost
Please refresh page' + errorbox.style.display = 'block' + canvas.style.display = 'none' + } + ws.onclose = function(e){ + errorbox.innerHTML = 'Connection lost
Please refresh page' + errorbox.style.display = 'block' + canvas.style.display = 'none' + } + setInterval(function(){ws.send('ack')}, 3000) +} + + +function touch(e) { + click_timeout = setTimeout(function() { + is_short = false + }, 1000) +} + + +function release(e) { + clearTimeout(click_timeout) + x = e.layerX + y = e.layerY + w = displayWidth + length = is_short?0:1 + ws.send('touch ' + localStorage.getItem('password') + ' ' + x + ' ' + y + ' ' + w + ' ' + length) +} + + +function connect(e) { + e.preventDefault() + holder.style.display = 'block' + login.style.display = 'none' + localStorage.setItem('url', url_el.value) + localStorage.setItem('password', password_el.value) + setup() +} + + +let canvas = document.querySelector('canvas') +let errorbox = document.querySelector('#errorbox') + +let login = document.querySelector('#login') +let holder = document.querySelector('#holder') + +let url_el = document.querySelector('#url') +let password_el = document.querySelector('#password') +let start_button = document.querySelector('#start') + +let is_short = true +let click_timeout + +let ctx = canvas.getContext("2d") + +let displayWidth, displayHeight + +let ws + + +resize() +window.onresize = resize + +canvas.onmousedown = touch +canvas.onmouseup = release + +start.onclick = connect diff --git a/crops.json b/server/crops.json similarity index 100% rename from crops.json rename to server/crops.json diff --git a/server/hcapi.py b/server/hcapi.py new file mode 100644 index 0000000..a4a5a9b --- /dev/null +++ b/server/hcapi.py @@ -0,0 +1,41 @@ +import os +import requests +import threading +import json +import time + +def get_img(): + os.system('xwd -root -silent | convert xwd:- img.bmp') + + with open('crops.json') as f: + crops = json.load(f) + + threads = [] + for crop in crops: + threads.append(threading.Thread(target=os.system, args=(f'convert img.bmp -crop {crop} pieces/{crop.split("+", 1)[1].replace("+", "x")}.jpg',))) + threads[-1].start() + for thread in threads: + thread.join() + + os.system("md5sum -c oldlist 2>/dev/null | grep FAILED > newlist") + changed = [] + with open('newlist') as f: + lines = f.readlines() + for line in lines: + changed.append(line.split(": ")[0].split('.')[0].split('/')[1]) + os.system("md5sum pieces/* > oldlist") + return changed + +def get_full_img(): + os.system('xwd -root -silent | convert xwd:- full.jpg') + return 'full.jpg' + +def touch(x, y, w, is_long): + x = round(800 * x / w) + y = round(800 * y / w) + if is_long: + os.system(f'xdotool mousemove {x} {y} mousedown 1') + time.sleep(2) + os.system(f'xdotool mouseup 1') + else: + os.system(f'xdotool mousemove {x} {y} click 1') diff --git a/hcapi.py b/server/ohcapi.py similarity index 88% rename from hcapi.py rename to server/ohcapi.py index f1c89e8..54e3edb 100644 --- a/hcapi.py +++ b/server/ohcapi.py @@ -11,11 +11,6 @@ def get_img(): os.system('convert full_img.bmp -resize 800x480 img.bmp') - #crops = [] - #for n in range(10): - # for m in range(6): - # crops.append(f'80x80+{n*80}+{m*80}') - #print(crops) with open('crops.json') as f: crops = json.load(f) @@ -47,7 +42,7 @@ def get_full_img(): return 'full.jpg' -def touch(x, y, w): +def touch(x, y, w, is_long): x = round(800 * x / w) y = round(800 * y / w) - requests.get(f'http://localhost:8080/set_touch?x={x}&y={y}&hold={0}') + requests.get(f'http://localhost:8080/set_touch?x={x}&y={y}&hold={1 if is_long else 0}') diff --git a/wss.py b/server/wss.py similarity index 66% rename from wss.py rename to server/wss.py index c024e1c..f981c07 100644 --- a/wss.py +++ b/server/wss.py @@ -33,21 +33,33 @@ class Client: self.server = server self.queue = queue.Queue() self.items = {} + self.lock = threading.Lock() self.good = True def send(self, item, name): - self.items[name] = item + with self.lock: + self.items[name] = item def ack(self): - self.server.send_message(self.client, 'ack') - self.good = False + with self.lock: + try: + self.server.send_message(self.client, 'ack') + self.good = False + except BrokenPipeError: + pass def cycle(self): - while not self.good: - pass - for name, item in self.items.items(): - self.server.send_message(self.client, item) - del self.items[name] + with self.lock: + try: + while not self.good: + pass + for name, item in list(self.items.items()): + if not self.good: + break + self.server.send_message(self.client, item) + del self.items[name] + except BrokenPipeError: + pass def run(self): while True: @@ -55,9 +67,7 @@ class Client: clients = {} def do_img(imgname): - #server.send_message_to_all(img(imgname)) for client in clients.values(): - print(client) client.send(img(imgname), imgname) @@ -70,6 +80,12 @@ def img(imgname): return response def new_client(client, server): + if clients: + server.send_message(client, 'err%*inuse%Server already in use') + client['handler'].send_text("", opcode=0x8) + return + + clients[client['id']] = Client(client, server) try: imgname = hcapi.get_full_img() except Exception as e: @@ -91,17 +107,24 @@ def do_touch(client, server, message): if action == 'ack': clients[client['id']].good = True else: - _, password, x, y, w = message.split(' ') + _, password, x, y, w, is_long = message.split(' ') if password == 'password': - x, y, w = int(x), int(y), int(w) - hcapi.touch(x, y, w) + x, y, w, is_long = int(x), int(y), int(w), bool(is_long) + hcapi.touch(x, y, w, is_long) else: clients[client['id']].send(f'badpass', 'BADPASS') +def client_left(client, server): + clients[client['id']].good = False + del clients[client['id']] + def do_cycles(): while True: - cycle() - time.sleep(0.2) + if clients: + cycle() + time.sleep(0.4) + else: + time.sleep(1) tmp = tempfile.mkdtemp(prefix="HCRA-") try: @@ -112,6 +135,7 @@ try: threading.Thread(target=do_cycles).start() server.set_fn_new_client(new_client) server.set_fn_message_received(do_touch) + server.set_fn_client_left(client_left) server.run_forever() finally: os.chdir('/')