From 70131428edce8d7c6476a902d015b30b78e5f862 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Sat, 3 Dec 2022 16:12:14 +0100 Subject: Version 1.1: Added QR Code --- Makefile | 5 +++- debian/changelog | 7 ++++++ debian/control | 2 +- html/index.html | 4 ++++ html/whiteboard.css | 41 ++++++++++++++++++++++++++++++++ html/whiteboard.js | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++ qrcode.cpp | 32 +++++++++++++++++++++++++ qrcode.h | 5 ++++ whiteboard.cpp | 16 +++++++++++++ 9 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 qrcode.cpp create mode 100644 qrcode.h diff --git a/Makefile b/Makefile index a387f1c..e0507bf 100755 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ endif LIBS=-lfcgi -lboost_filesystem INCLUDES=-I. -HEADERS=file.h config.h +HEADERS=file.h config.h qrcode.h SOURCES=$(HEADERS:.h=.cpp) OBJECTS=$(HEADERS:.h=.o) TARGETS=whiteboard.fcgi @@ -59,6 +59,9 @@ LIBS+= \ -lstdc++fs endif +CXXFLAGS+=$(shell pkg-config --cflags qrcodegencpp Magick++ fmt) +LIBS+=$(shell pkg-config --libs qrcodegencpp Magick++ fmt) + build: $(TARGETS) all: build diff --git a/debian/changelog b/debian/changelog index 3db9796..1ea5c87 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +whiteboard (1.1) unstable; urgency=medium + + * Compress Javascript + * Add QR Code + + -- Roland Reichwein Sat, 03 Dec 2022 16:10:06 +0100 + whiteboard (1.0) unstable; urgency=medium * Initial release diff --git a/debian/control b/debian/control index 0729cbb..ec9b915 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: whiteboard Section: web Priority: optional Maintainer: Roland Reichwein -Build-Depends: debhelper (>= 12), libboost-all-dev | libboost1.71-all-dev, clang | g++-9, uglifyjs, python3-pkg-resources, htmlmin, cleancss, libfcgi-dev, libqrcodegencpp-dev, libmagick++-dev +Build-Depends: debhelper (>= 12), libboost-all-dev | libboost1.71-all-dev, clang | g++-9, uglifyjs, python3-pkg-resources, htmlmin, cleancss, libfcgi-dev, libqrcodegencpp-dev, libmagick++-dev, pkg-config, libfmt-dev Standards-Version: 4.5.0 Homepage: http://www.reichwein.it/whiteboard/ diff --git a/html/index.html b/html/index.html index 04522d7..e8a07be 100644 --- a/html/index.html +++ b/html/index.html @@ -10,12 +10,16 @@ +

Whiteboard



+

Reichwein.IT Whiteboard by https://www.reichwein.it
diff --git a/html/whiteboard.css b/html/whiteboard.css index 5f804c1..2af2a92 100644 --- a/html/whiteboard.css +++ b/html/whiteboard.css @@ -32,6 +32,37 @@ textarea { resize: none; } +.qrwindow { + position: fixed; + top: 50%; + left: 50%; + width: 400px; + height: 400px; + margin-top: -200px; + margin-left: -200px; + background-color: #FFFFFF; + opacity: 1; + z-index: 10; + border-width: 3px; + border-style: solid; + border-color: #FFFFFF; + padding: 10pt; + box-sizing: border-box; +} + +.qrcode { + width: 374px; + height: 374px; + + image-rendering: optimizeSpeed; /* */ + image-rendering: -moz-crisp-edges; /* Firefox */ + image-rendering: -o-crisp-edges; /* Opera */ + image-rendering: -webkit-optimize-contrast; /* Chrome (and Safari) */ + image-rendering: pixelated; /* Chrome as of 2019 */ + image-rendering: optimize-contrast; /* CSS3 Proposed */ + -ms-interpolation-mode: nearest-neighbor; /* IE8+ */ +} + .mobile { width: 300px; border-width: 80px 15px 80px 15px; @@ -67,6 +98,16 @@ img.banner { } @media only screen and (min-width: 1px) and (max-width: 630px) { + +.qrwindow { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + margin: 0; +} + } @media only screen and (min-width: 631px) and (max-width: 950px) { diff --git a/html/whiteboard.js b/html/whiteboard.js index d9f0904..c6fb657 100644 --- a/html/whiteboard.js +++ b/html/whiteboard.js @@ -62,6 +62,16 @@ class AdjustingTimer { var timer = new AdjustingTimer(); +function showQRWindow() +{ + document.getElementById("qrwindow").style.display = 'block'; +} + +function hideQRWindow() +{ + document.getElementById("qrwindow").style.display = 'none'; +} + function init_board() { var xhr = new XMLHttpRequest(); @@ -124,6 +134,16 @@ function init_board() { xhr.send(xmlDocument); //set_status("Please wait while server prepares " + filename + " ..."); + + document.getElementById("qrwindow").onclick = function() { + hideQRWindow(); + } + + document.onkeydown = function(evt) { + if (evt.key == "Escape") { + hideQRWindow(); + } + } } function get_id() @@ -300,3 +320,51 @@ function checkupdate() { //set_status("Please wait while server prepares " + filename + " ..."); } +function on_qrcode() +{ + var xhr = new XMLHttpRequest(); + + // run on data received back + xhr.onreadystatechange = function() { + if (this.readyState == 3) { + //set_status("Please wait while downloading " + filename + " ..."); + return; + } + if (this.readyState != 4) { + return; + } + if (this.status != 200) { + //set_status("Server Error while retrieving " + filename + ", status: " + this.status + " " + this.statusText); + return; + } + + if (this.getResponseHeader("Content-Type") == "image/png") { + var blob = new Blob([this.response], {type: 'image/png'}); + var url = URL.createObjectURL(blob); + var img = document.getElementById("qrcode"); + img.src = url; + showQRWindow(); + } + + //set_status(""); // OK + } + + var parser = new DOMParser(); + var xmlDocument = parser.parseFromString("", "text/xml"); + + var requestElement = xmlDocument.getElementsByTagName("request")[0]; + + var commandElement = xmlDocument.createElement("command"); + commandElement.appendChild(document.createTextNode("qrcode")); + requestElement.appendChild(commandElement); + + var idElement = xmlDocument.createElement("url"); + idElement.appendChild(document.createTextNode(document.location)); + requestElement.appendChild(idElement); + + xhr.open("POST", "whiteboard.fcgi", true); + xhr.setRequestHeader("Content-type", "text/xml"); + xhr.responseType = 'blob'; + xhr.send(xmlDocument); +} + diff --git a/qrcode.cpp b/qrcode.cpp new file mode 100644 index 0000000..cd5fab8 --- /dev/null +++ b/qrcode.cpp @@ -0,0 +1,32 @@ +#include "qrcode.h" + +#include + +#include +#include + +using namespace qrcodegen; +using namespace Magick; + +std::string getQRCode(const std::string& data) +{ + QrCode qrc {QrCode::encodeText(data.c_str(), QrCode::Ecc::MEDIUM)}; + + int size {qrc.getSize()}; + + Image image(fmt::format("{0}x{0}", size).c_str(), "white"); + image.type(GrayscaleType); + //image.size(fmt::format("{0}x{0}", size)); + + for (int x = 0; x < size; x++) { + for (int y = 0; y < size; y++) { + image.pixelColor(x, y, qrc.getModule(x, y) ? "black" : "white"); + } + } + + image.magick("PNG"); + + Blob blob; + image.write(&blob); + return std::string{(char*)blob.data(), blob.length()}; +} diff --git a/qrcode.h b/qrcode.h new file mode 100644 index 0000000..f6546f0 --- /dev/null +++ b/qrcode.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::string getQRCode(const std::string& data); diff --git a/whiteboard.cpp b/whiteboard.cpp index b3367cf..8df6d73 100644 --- a/whiteboard.cpp +++ b/whiteboard.cpp @@ -18,8 +18,11 @@ #include #include +#include + #include "config.h" #include "file.h" +#include "qrcode.h" namespace pt = boost::property_tree; using namespace std::string_literals; @@ -95,6 +98,8 @@ int main(void) Config config; data_path = config.getDataPath(); + Magick::InitializeMagick(NULL); // for qrcode.cpp + int result = FCGX_Init(); if (result != 0) { // error on init fprintf(stderr, "Error: FCGX_Init()\n"); @@ -174,6 +179,17 @@ int main(void) } else if (command == "newid") { FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out); FCGX_PutS(generate_id().c_str(), request.out); + } else if (command == "qrcode") { + std::string url{xml.get("request.url")}; + + if (url.size() > 1000) + throw std::runtime_error("URL too big"); + + std::string pngdata {getQRCode(url)}; + + FCGX_PutS("Content-Type: image/png\r\n", request.out); + FCGX_FPrintF(request.out, "Content-Length: %d\r\n\r\n", pngdata.size()); + FCGX_PutStr(pngdata.c_str(), pngdata.size(), request.out); } else { throw std::runtime_error("Bad command: "s + command); } -- cgit v1.2.3