Feature/kasm 4608 linux printing (#74)

* Add support for relaying unix sockets

* KASM-4608 Add document printing using the printer socket relay

---------

Co-authored-by: Lauri Kasanen <cand@gmx.com>
Co-authored-by: mattmcclaskey <matt@kasmweb.com>
This commit is contained in:
Mariusz Marciniak 2023-08-03 20:06:27 +02:00 committed by GitHub
parent 2e10cdf12d
commit d135f05932
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 169 additions and 0 deletions

63
core/output/printer.js Normal file
View File

@ -0,0 +1,63 @@
const PACKETS = {
DOCUMENT_START: 0,
DOCUMENT_CHUNK: 1,
DOCUMENT_END: 2
};
const printDocument = async (data) => {
const iframe = document.createElement("iframe");
iframe.style.display = "none";
document.body.appendChild(iframe);
iframe.onload = () => {
setTimeout(() => {
iframe.focus();
iframe.contentWindow.print();
}, 1);
};
const blob = new Blob([new Uint8Array(data)], { type: "application/pdf" });
iframe.src = URL.createObjectURL(blob);
}
export default (rfb) => {
let documentSize = 0;
let downloadedSize = 0;
let documentData = [];
const processRelayData = (payload) => {
const array = Array.from(payload);
const buffer = new Uint8Array(array).buffer;
const packetData = new DataView(buffer);
const packetId = packetData.getUint32(0, false);
switch (packetId) {
case PACKETS.DOCUMENT_START:
documentSize = packetData.getUint32(4, false);
downloadedSize = 0;
console.log(`Downloading document for printing (${documentSize}B)`);
break;
case PACKETS.DOCUMENT_CHUNK:
let chunkSize = packetData.getUint32(4, false);
let chunkData = new Uint8Array(buffer, 8);
downloadedSize += chunkSize;
documentData.push(...chunkData);
console.log(`Downloading document for printing (${downloadedSize}/${documentSize}B)`);
break;
case PACKETS.DOCUMENT_END:
console.log(`Downloaded document for printing (${downloadedSize}/${documentSize}B)`);
printDocument(documentData);
downloadedSize = 0;
documentSize = 0;
break;
default:
console.error(`Unknown packet id: ${packetId}`);
break;
}
}
rfb.subscribeUnixRelay("printer", processRelayData);
}

View File

@ -20,6 +20,7 @@ import Display from "./display.js";
import Inflator from "./inflator.js"; import Inflator from "./inflator.js";
import Deflator from "./deflator.js"; import Deflator from "./deflator.js";
import Keyboard from "./input/keyboard.js"; import Keyboard from "./input/keyboard.js";
import initializePrinterRelay from "./output/printer.js";
import GestureHandler from "./input/gesturehandler.js"; import GestureHandler from "./input/gesturehandler.js";
import Cursor from "./util/cursor.js"; import Cursor from "./util/cursor.js";
import Websock from "./websock.js"; import Websock from "./websock.js";
@ -981,6 +982,16 @@ export default class RFB extends EventTargetMixin {
RFB.messages.requestStats(this._sock); RFB.messages.requestStats(this._sock);
} }
subscribeUnixRelay(name, processRelayFn) {
this._unixRelays = this._unixRelays || {};
this._unixRelays[name] = processRelayFn;
RFB.messages.sendSubscribeUnixRelay(this._sock, name);
}
sendUnixRelayData(name, payload) {
RFB.messages.sendUnixRelay(this._sock, name, payload);
}
// ===== PRIVATE METHODS ===== // ===== PRIVATE METHODS =====
_setLastActive() { _setLastActive() {
@ -2536,6 +2547,10 @@ export default class RFB extends EventTargetMixin {
RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fbWidth, this._fbHeight); RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fbWidth, this._fbHeight);
this._updateConnectionState('connected'); this._updateConnectionState('connected');
//Register pipe based extensions
initializePrinterRelay(this);
return true; return true;
} }
@ -3083,6 +3098,12 @@ export default class RFB extends EventTargetMixin {
case 181: // KASM UDP upgrade case 181: // KASM UDP upgrade
return this._handleUdpUpgrade(); return this._handleUdpUpgrade();
case 182: // KASM unix relay subscription
return this._handleSubscribeUnixRelay();
case 183: // KASM unix relay data
return this._handleUnixRelay();
case 248: // ServerFence case 248: // ServerFence
return this._handleServerFenceMsg(); return this._handleServerFenceMsg();
@ -3211,6 +3232,36 @@ export default class RFB extends EventTargetMixin {
}.bind(this)); }.bind(this));
} }
_handleSubscribeUnixRelay() {
if (this._sock.rQwait("SubscribeUnixRelay header", 2, 1)) { return false; }
let status = this._sock.rQshift8();
let len = this._sock.rQshift8();
if (this._sock.rQwait("SubscribeUnixRelay message", len, 3)) { return false; }
const payload = this._sock.rQshiftStr(len);
if (status) {
console.log("Unix relay subscription succeeded");
} else {
console.log("Unix relay subscription failed, " + payload);
}
}
_handleUnixRelay() {
if (this._sock.rQwait("UnixRelay header", 1, 1)) { return false; }
let namelen = this._sock.rQshift8();
if (this._sock.rQwait("UnixRelay name", namelen, 2)) { return false; }
const name = this._sock.rQshiftStr(namelen);
if (this._sock.rQwait("UnixRelay len", 4, 2 + namelen)) { return false; }
let len = this._sock.rQshift32();
if (this._sock.rQwait("UnixRelay data", len, 6 + namelen)) { return false; }
const payload = this._sock.rQshiftBytes(len);
const processRelay = this._unixRelays[name];
processRelay && processRelay(payload);
}
_framebufferUpdate() { _framebufferUpdate() {
if (this._FBU.rects === 0) { if (this._FBU.rects === 0) {
if (this._sock.rQwait("FBU header", 3, 1)) { return false; } if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
@ -3952,6 +4003,61 @@ RFB.messages = {
} }
}, },
sendSubscribeUnixRelay(sock, name) {
const buff = sock._sQ;
const offset = sock._sQlen;
buff[offset] = 182; // msg-type
buff[offset + 1] = name.length; // len
for (let i = 0; i < name.length; i++) {
buff[offset + 2 + i] = name.charCodeAt(i);
}
sock._sQlen += 2 + name.length;
sock.flush();
},
sendUnixRelay(sock, name, data) {
const buff = sock._sQ;
let offset = sock._sQlen;
buff[offset++] = 183; // msg-type
buff[offset++] = name.length; // len
for (let i = 0; i < name.length; i++) {
buff[offset++] = name.charCodeAt(i);
}
let length = data.length;
Log.Info('Sent unix relay data len ' + length);
buff[offset++] = length >> 24;
buff[offset++] = length >> 16;
buff[offset++] = length >> 8;
buff[offset++] = length;
sock._sQlen += 2 + name.length + 4;
// We have to keep track of from where in the data we begin creating the
// buffer for the flush in the next iteration.
let dataOffset = 0;
let remaining = data.length;
while (remaining > 0) {
let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen));
for (let i = 0; i < flushSize; i++) {
buff[sock._sQlen + i] = data[dataOffset + i];
}
sock._sQlen += flushSize;
sock.flush();
remaining -= flushSize;
dataOffset += flushSize;
}
},
setDesktopSize(sock, width, height, id, flags) { setDesktopSize(sock, width, height, id, flags) {
const buff = sock._sQ; const buff = sock._sQ;
const offset = sock._sQlen; const offset = sock._sQlen;