From 645caafc8e3c6073c58f213e81359e7a61980201 Mon Sep 17 00:00:00 2001 From: Berenz Date: Wed, 20 Jan 2021 23:16:32 -0500 Subject: [PATCH 1/5] Move method enum out of client class --- src/qz/ws/PrintSocketClient.java | 105 +++---------------------------- src/qz/ws/SocketMethod.java | 93 +++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 96 deletions(-) create mode 100644 src/qz/ws/SocketMethod.java diff --git a/src/qz/ws/PrintSocketClient.java b/src/qz/ws/PrintSocketClient.java index 465bc7810..e234ccf44 100644 --- a/src/qz/ws/PrintSocketClient.java +++ b/src/qz/ws/PrintSocketClient.java @@ -46,93 +46,6 @@ public class PrintSocketClient { //websocket port -> Connection private static final HashMap openConnections = new HashMap<>(); - private enum Method { - PRINTERS_GET_DEFAULT("printers.getDefault", true, "access connected printers"), - PRINTERS_FIND("printers.find", true, "access connected printers"), - PRINTERS_DETAIL("printers.detail", true, "access connected printers"), - PRINTERS_START_LISTENING("printers.startListening", true, "listen for printer status"), - PRINTERS_GET_STATUS("printers.getStatus", false), - PRINTERS_STOP_LISTENING("printers.stopListening", false), - PRINT("print", true, "print to %s"), - - SERIAL_FIND_PORTS("serial.findPorts", true, "access serial ports"), - SERIAL_OPEN_PORT("serial.openPort", true, "open a serial port"), - SERIAL_SEND_DATA("serial.sendData", true, "send data over a serial port"), - SERIAL_CLOSE_PORT("serial.closePort", true, "close a serial port"), - - USB_LIST_DEVICES("usb.listDevices", true, "access USB devices"), - USB_LIST_INTERFACES("usb.listInterfaces", true, "access USB devices"), - USB_LIST_ENDPOINTS("usb.listEndpoints", true, "access USB devices"), - USB_CLAIM_DEVICE("usb.claimDevice", true, "claim a USB device"), - USB_CLAIMED("usb.isClaimed", false, "check USB claim status"), - USB_SEND_DATA("usb.sendData", true, "use a USB device"), - USB_READ_DATA("usb.readData", true, "use a USB device"), - USB_OPEN_STREAM("usb.openStream", true, "use a USB device"), - USB_CLOSE_STREAM("usb.closeStream", false, "use a USB device"), - USB_RELEASE_DEVICE("usb.releaseDevice", false, "release a USB device"), - - HID_LIST_DEVICES("hid.listDevices", true, "access USB devices"), - HID_START_LISTENING("hid.startListening", true, "listen for USB devices"), - HID_STOP_LISTENING("hid.stopListening", false), - HID_CLAIM_DEVICE("hid.claimDevice", true, "claim a USB device"), - HID_CLAIMED("hid.isClaimed", false, "check USB claim status"), - HID_SEND_DATA("hid.sendData", true, "use a USB device"), - HID_READ_DATA("hid.readData", true, "use a USB device"), - HID_SEND_FEATURE_REPORT("hid.sendFeatureReport", true, "use a USB device"), - HID_GET_FEATURE_REPORT("hid.getFeatureReport", true, "use a USB device"), - HID_OPEN_STREAM("hid.openStream", true, "use a USB device"), - HID_CLOSE_STREAM("hid.closeStream", false, "use a USB device"), - HID_RELEASE_DEVICE("hid.releaseDevice", false, "release a USB device"), - - FILE_LIST("file.list", true, "view the filesystem"), - FILE_START_LISTENING("file.startListening", true, "listen for filesystem events"), - FILE_STOP_LISTENING("file.stopListening", false), - FILE_READ("file.read", true, "read the content of a file"), - FILE_WRITE("file.write", true, "write to a file"), - FILE_REMOVE("file.remove", true, "delete a file"), - - NETWORKING_DEVICE("networking.device", true), - NETWORKING_DEVICES("networking.devices", true), - NETWORKING_DEVICE_LEGACY("websocket.getNetworkInfo", true), - GET_VERSION("getVersion", false), - - INVALID("", false); - - - private String callName; - private String dialogPrompt; - private boolean dialogShown; - - Method(String callName, boolean dialogShown) { - this(callName, dialogShown, "access local resources"); - } - - Method(String callName, boolean dialogShown, String dialogPrompt) { - this.callName = callName; - - this.dialogShown = dialogShown; - this.dialogPrompt = dialogPrompt; - } - - public boolean isDialogShown() { - return dialogShown; - } - - public String getDialogPrompt() { - return dialogPrompt; - } - - public static Method findFromCall(String call) { - for(Method m : Method.values()) { - if (m.callName.equals(call)) { - return m; - } - } - - return INVALID; - } - } - @OnWebSocketConnect public void onConnect(Session session) { @@ -266,18 +179,18 @@ private boolean validSignature(Certificate certificate, JSONObject message) thro */ private void processMessage(Session session, JSONObject json, SocketConnection connection, RequestState request) throws JSONException, SerialPortException, DeviceException, IOException, ListenerNotFoundException { String UID = json.optString("uid"); - Method call = Method.findFromCall(json.optString("call")); + SocketMethod call = SocketMethod.findFromCall(json.optString("call")); JSONObject params = json.optJSONObject("params"); if (params == null) { params = new JSONObject(); } - if (call == Method.INVALID && (UID == null || UID.isEmpty())) { + if (call == SocketMethod.INVALID && (UID == null || UID.isEmpty())) { //incorrect message format, likely incompatible qz version session.close(4003, "Connected to incompatible " + Constants.ABOUT_TITLE + " version"); return; } String prompt = call.getDialogPrompt(); - if (call == Method.PRINT) { + if (call == SocketMethod.PRINT) { //special formatting for print dialogs JSONObject pr = params.optJSONObject("printer"); if (pr != null) { @@ -295,7 +208,7 @@ private void processMessage(Session session, JSONObject json, SocketConnection c } // used in usb calls - DeviceOptions dOpts = new DeviceOptions(params, DeviceOptions.DeviceMode.parse(call.callName)); + DeviceOptions dOpts = new DeviceOptions(params, DeviceOptions.DeviceMode.parse(call.getCallName())); //call appropriate methods @@ -428,7 +341,7 @@ private void processMessage(Session session, JSONObject json, SocketConnection c case HID_CLAIM_DEVICE: { if (connection.getDevice(dOpts) == null) { DeviceIO device; - if (call == Method.USB_CLAIM_DEVICE) { + if (call == SocketMethod.USB_CLAIM_DEVICE) { device = new UsbIO(dOpts); } else { if (SystemUtilities.isWindows()) { @@ -464,7 +377,7 @@ private void processMessage(Session session, JSONObject json, SocketConnection c DeviceIO usb = connection.getDevice(dOpts); if (usb != null) { - if (call == Method.HID_SEND_FEATURE_REPORT) { + if (call == SocketMethod.HID_SEND_FEATURE_REPORT) { usb.sendFeatureReport(DeviceUtilities.getDataBytes(params, null), dOpts.getEndpoint()); } else { usb.sendData(DeviceUtilities.getDataBytes(params, null), dOpts.getEndpoint()); @@ -483,8 +396,8 @@ private void processMessage(Session session, JSONObject json, SocketConnection c DeviceIO usb = connection.getDevice(dOpts); if (usb != null) { byte[] response; - - if (call == Method.HID_GET_FEATURE_REPORT) { + + if (call == SocketMethod.HID_GET_FEATURE_REPORT) { response = usb.getFeatureReport(dOpts.getResponseSize(), dOpts.getEndpoint()); } else { response = usb.readData(dOpts.getResponseSize(), dOpts.getEndpoint()); @@ -504,7 +417,7 @@ private void processMessage(Session session, JSONObject json, SocketConnection c } case USB_OPEN_STREAM: case HID_OPEN_STREAM: { - StreamEvent.Stream stream = (call == Method.USB_OPEN_STREAM? StreamEvent.Stream.USB:StreamEvent.Stream.HID); + StreamEvent.Stream stream = (call == SocketMethod.USB_OPEN_STREAM? StreamEvent.Stream.USB:StreamEvent.Stream.HID); UsbUtilities.setupUsbStream(session, UID, connection, dOpts, stream); break; } diff --git a/src/qz/ws/SocketMethod.java b/src/qz/ws/SocketMethod.java new file mode 100644 index 000000000..b29dc9e7f --- /dev/null +++ b/src/qz/ws/SocketMethod.java @@ -0,0 +1,93 @@ +package qz.ws; + +public enum SocketMethod { + PRINTERS_GET_DEFAULT("printers.getDefault", true, "access connected printers"), + PRINTERS_FIND("printers.find", true, "access connected printers"), + PRINTERS_DETAIL("printers.detail", true, "access connected printers"), + PRINTERS_START_LISTENING("printers.startListening", true, "listen for printer status"), + PRINTERS_GET_STATUS("printers.getStatus", false), + PRINTERS_STOP_LISTENING("printers.stopListening", false), + PRINT("print", true, "print to %s"), + + SERIAL_FIND_PORTS("serial.findPorts", true, "access serial ports"), + SERIAL_OPEN_PORT("serial.openPort", true, "open a serial port"), + SERIAL_SEND_DATA("serial.sendData", true, "send data over a serial port"), + SERIAL_CLOSE_PORT("serial.closePort", true, "close a serial port"), + + USB_LIST_DEVICES("usb.listDevices", true, "access USB devices"), + USB_LIST_INTERFACES("usb.listInterfaces", true, "access USB devices"), + USB_LIST_ENDPOINTS("usb.listEndpoints", true, "access USB devices"), + USB_CLAIM_DEVICE("usb.claimDevice", true, "claim a USB device"), + USB_CLAIMED("usb.isClaimed", false, "check USB claim status"), + USB_SEND_DATA("usb.sendData", true, "use a USB device"), + USB_READ_DATA("usb.readData", true, "use a USB device"), + USB_OPEN_STREAM("usb.openStream", true, "use a USB device"), + USB_CLOSE_STREAM("usb.closeStream", false, "use a USB device"), + USB_RELEASE_DEVICE("usb.releaseDevice", false, "release a USB device"), + + HID_LIST_DEVICES("hid.listDevices", true, "access USB devices"), + HID_START_LISTENING("hid.startListening", true, "listen for USB devices"), + HID_STOP_LISTENING("hid.stopListening", false), + HID_CLAIM_DEVICE("hid.claimDevice", true, "claim a USB device"), + HID_CLAIMED("hid.isClaimed", false, "check USB claim status"), + HID_SEND_DATA("hid.sendData", true, "use a USB device"), + HID_READ_DATA("hid.readData", true, "use a USB device"), + HID_SEND_FEATURE_REPORT("hid.sendFeatureReport", true, "use a USB device"), + HID_GET_FEATURE_REPORT("hid.getFeatureReport", true, "use a USB device"), + HID_OPEN_STREAM("hid.openStream", true, "use a USB device"), + HID_CLOSE_STREAM("hid.closeStream", false, "use a USB device"), + HID_RELEASE_DEVICE("hid.releaseDevice", false, "release a USB device"), + + FILE_LIST("file.list", true, "view the filesystem"), + FILE_START_LISTENING("file.startListening", true, "listen for filesystem events"), + FILE_STOP_LISTENING("file.stopListening", false), + FILE_READ("file.read", true, "read the content of a file"), + FILE_WRITE("file.write", true, "write to a file"), + FILE_REMOVE("file.remove", true, "delete a file"), + + NETWORKING_DEVICE("networking.device", true), + NETWORKING_DEVICES("networking.devices", true), + NETWORKING_DEVICE_LEGACY("websocket.getNetworkInfo", true), + GET_VERSION("getVersion", false), + + INVALID("", false); + + + private String callName; + private String dialogPrompt; + private boolean dialogShown; + + SocketMethod(String callName, boolean dialogShown) { + this(callName, dialogShown, "access local resources"); + } + + SocketMethod(String callName, boolean dialogShown, String dialogPrompt) { + this.callName = callName; + + this.dialogShown = dialogShown; + this.dialogPrompt = dialogPrompt; + } + + public boolean isDialogShown() { + return dialogShown; + } + + public String getDialogPrompt() { + return dialogPrompt; + } + + public static SocketMethod findFromCall(String call) { + for(SocketMethod m : SocketMethod.values()) { + if (m.callName.equals(call)) { + return m; + } + } + + return INVALID; + } + + public String getCallName() { + return callName; + } + +} From 24d8427825e2c5e065c695e02ed7de5f5629883d Mon Sep 17 00:00:00 2001 From: Berenz Date: Fri, 22 Jan 2021 20:34:06 -0500 Subject: [PATCH 2/5] Initial tcp feature support --- js/qz-tray.js | 63 +++++ sample.html | 421 +++++++++++++++++------------ src/qz/communication/SocketIO.java | 76 ++++++ src/qz/utils/SocketUtilities.java | 39 +++ src/qz/ws/PrintSocketClient.java | 32 ++- src/qz/ws/SocketConnection.java | 21 +- src/qz/ws/SocketMethod.java | 4 + 7 files changed, 479 insertions(+), 177 deletions(-) create mode 100644 src/qz/communication/SocketIO.java create mode 100644 src/qz/utils/SocketUtilities.java diff --git a/js/qz-tray.js b/js/qz-tray.js index 3817a1051..0c1ca824b 100644 --- a/js/qz-tray.js +++ b/js/qz-tray.js @@ -1606,6 +1606,69 @@ var qz = (function() { } }, + //fixme - naming?? + /** + * Calls related to interaction with communication sockets. + * @namespace qz.socket + */ + socket: { + /** + * Opens a network port for sending and receiving data. + * + * @param {string} host The connection hostname. + * @param {number} port The connection port number. + * + * @memberof qz.socket + */ + open: function(host, port) { + var params = { + host: host, + port: port + }; + return _qz.websocket.dataPromise("socket.open", params); + }, + + /** + * @param {string} host The connection hostname. + * @param {number} port The connection port number. + * + * @memberof qz.socket + */ + close: function(host, port) { + var params = { + host: host, + port: port + }; + return _qz.websocket.dataPromise("socket.close", params); + }, + + /** + * Send data over an open socket. + * + * @param {string} host The connection hostname. + * @param {number} port The connection port number. + * @param {string|Object} data Data to be sent over the port. + * @param {string} [data.type='PLAIN'] Valid values [PLAIN] + * @param {string} data.data Data to be sent over the port. + * + * @memberof qz.socket + */ + sendData: function(host, port, data) { + if (typeof data !== 'object') { + data = { + data: data, + type: "PLAIN" + }; + } + + var params = { + host: host, + port: port, + data: data + }; + return _qz.websocket.dataPromise("socket.sendData", params); + } + }, /** * Calls related to interaction with USB devices. diff --git a/sample.html b/sample.html index 56dc642f7..4d002a130 100755 --- a/sample.html +++ b/sample.html @@ -111,6 +111,7 @@

Printer

+ @@ -774,6 +775,58 @@

Serial

+
+

Socket

+
+ +
+
+
+
+
+ + +
+ +
+
+
+
+ +
+
+
+
+ +
+ Options + +
+
+
+ + +
+ +
+ + +
+
+
+
+ + +
+
+
+
+
+
+
+
+
+

USB


@@ -1243,65 +1296,65 @@

Options

//Alternate method 2 - direct resolve("-----BEGIN CERTIFICATE-----\n" + - "MIIFAzCCAuugAwIBAgICEAIwDQYJKoZIhvcNAQEFBQAwgZgxCzAJBgNVBAYTAlVT\n" + - "MQswCQYDVQQIDAJOWTEbMBkGA1UECgwSUVogSW5kdXN0cmllcywgTExDMRswGQYD\n" + - "VQQLDBJRWiBJbmR1c3RyaWVzLCBMTEMxGTAXBgNVBAMMEHF6aW5kdXN0cmllcy5j\n" + - "b20xJzAlBgkqhkiG9w0BCQEWGHN1cHBvcnRAcXppbmR1c3RyaWVzLmNvbTAeFw0x\n" + - "NTAzMTkwMjM4NDVaFw0yNTAzMTkwMjM4NDVaMHMxCzAJBgNVBAYTAkFBMRMwEQYD\n" + - "VQQIDApTb21lIFN0YXRlMQ0wCwYDVQQKDAREZW1vMQ0wCwYDVQQLDAREZW1vMRIw\n" + - "EAYDVQQDDAlsb2NhbGhvc3QxHTAbBgkqhkiG9w0BCQEWDnJvb3RAbG9jYWxob3N0\n" + - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtFzbBDRTDHHmlSVQLqjY\n" + - "aoGax7ql3XgRGdhZlNEJPZDs5482ty34J4sI2ZK2yC8YkZ/x+WCSveUgDQIVJ8oK\n" + - "D4jtAPxqHnfSr9RAbvB1GQoiYLxhfxEp/+zfB9dBKDTRZR2nJm/mMsavY2DnSzLp\n" + - "t7PJOjt3BdtISRtGMRsWmRHRfy882msBxsYug22odnT1OdaJQ54bWJT5iJnceBV2\n" + - "1oOqWSg5hU1MupZRxxHbzI61EpTLlxXJQ7YNSwwiDzjaxGrufxc4eZnzGQ1A8h1u\n" + - "jTaG84S1MWvG7BfcPLW+sya+PkrQWMOCIgXrQnAsUgqQrgxQ8Ocq3G4X9UvBy5VR\n" + - "CwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdl\n" + - "bmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUpG420UhvfwAFMr+8vf3pJunQ\n" + - "gH4wHwYDVR0jBBgwFoAUkKZQt4TUuepf8gWEE3hF6Kl1VFwwDQYJKoZIhvcNAQEF\n" + - "BQADggIBAFXr6G1g7yYVHg6uGfh1nK2jhpKBAOA+OtZQLNHYlBgoAuRRNWdE9/v4\n" + - "J/3Jeid2DAyihm2j92qsQJXkyxBgdTLG+ncILlRElXvG7IrOh3tq/TttdzLcMjaR\n" + - "8w/AkVDLNL0z35shNXih2F9JlbNRGqbVhC7qZl+V1BITfx6mGc4ayke7C9Hm57X0\n" + - "ak/NerAC/QXNs/bF17b+zsUt2ja5NVS8dDSC4JAkM1dD64Y26leYbPybB+FgOxFu\n" + - "wou9gFxzwbdGLCGboi0lNLjEysHJBi90KjPUETbzMmoilHNJXw7egIo8yS5eq8RH\n" + - "i2lS0GsQjYFMvplNVMATDXUPm9MKpCbZ7IlJ5eekhWqvErddcHbzCuUBkDZ7wX/j\n" + - "unk/3DyXdTsSGuZk3/fLEsc4/YTujpAjVXiA1LCooQJ7SmNOpUa66TPz9O7Ufkng\n" + - "+CoTSACmnlHdP7U9WLr5TYnmL9eoHwtb0hwENe1oFC5zClJoSX/7DRexSJfB7YBf\n" + - "vn6JA2xy4C6PqximyCPisErNp85GUcZfo33Np1aywFv9H+a83rSUcV6kpE/jAZio\n" + - "5qLpgIOisArj1HTM6goDWzKhLiR/AeG3IJvgbpr9Gr7uZmfFyQzUjvkJ9cybZRd+\n" + - "G8azmpBBotmKsbtbAU/I/LVk8saeXznshOVVpDRYtVnjZeAneso7\n" + - "-----END CERTIFICATE-----\n" + - "--START INTERMEDIATE CERT--\n" + - "-----BEGIN CERTIFICATE-----\n" + - "MIIFEjCCA/qgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgawxCzAJBgNVBAYTAlVT\n" + - "MQswCQYDVQQIDAJOWTESMBAGA1UEBwwJQ2FuYXN0b3RhMRswGQYDVQQKDBJRWiBJ\n" + - "bmR1c3RyaWVzLCBMTEMxGzAZBgNVBAsMElFaIEluZHVzdHJpZXMsIExMQzEZMBcG\n" + - "A1UEAwwQcXppbmR1c3RyaWVzLmNvbTEnMCUGCSqGSIb3DQEJARYYc3VwcG9ydEBx\n" + - "emluZHVzdHJpZXMuY29tMB4XDTE1MDMwMjAwNTAxOFoXDTM1MDMwMjAwNTAxOFow\n" + - "gZgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOWTEbMBkGA1UECgwSUVogSW5kdXN0\n" + - "cmllcywgTExDMRswGQYDVQQLDBJRWiBJbmR1c3RyaWVzLCBMTEMxGTAXBgNVBAMM\n" + - "EHF6aW5kdXN0cmllcy5jb20xJzAlBgkqhkiG9w0BCQEWGHN1cHBvcnRAcXppbmR1\n" + - "c3RyaWVzLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTDgNLU\n" + - "iohl/rQoZ2bTMHVEk1mA020LYhgfWjO0+GsLlbg5SvWVFWkv4ZgffuVRXLHrwz1H\n" + - "YpMyo+Zh8ksJF9ssJWCwQGO5ciM6dmoryyB0VZHGY1blewdMuxieXP7Kr6XD3GRM\n" + - "GAhEwTxjUzI3ksuRunX4IcnRXKYkg5pjs4nLEhXtIZWDLiXPUsyUAEq1U1qdL1AH\n" + - "EtdK/L3zLATnhPB6ZiM+HzNG4aAPynSA38fpeeZ4R0tINMpFThwNgGUsxYKsP9kh\n" + - "0gxGl8YHL6ZzC7BC8FXIB/0Wteng0+XLAVto56Pyxt7BdxtNVuVNNXgkCi9tMqVX\n" + - "xOk3oIvODDt0UoQUZ/umUuoMuOLekYUpZVk4utCqXXlB4mVfS5/zWB6nVxFX8Io1\n" + - "9FOiDLTwZVtBmzmeikzb6o1QLp9F2TAvlf8+DIGDOo0DpPQUtOUyLPCh5hBaDGFE\n" + - "ZhE56qPCBiQIc4T2klWX/80C5NZnd/tJNxjyUyk7bjdDzhzT10CGRAsqxAnsjvMD\n" + - "2KcMf3oXN4PNgyfpbfq2ipxJ1u777Gpbzyf0xoKwH9FYigmqfRH2N2pEdiYawKrX\n" + - "6pyXzGM4cvQ5X1Yxf2x/+xdTLdVaLnZgwrdqwFYmDejGAldXlYDl3jbBHVM1v+uY\n" + - "5ItGTjk+3vLrxmvGy5XFVG+8fF/xaVfo5TW5AgMBAAGjUDBOMB0GA1UdDgQWBBSQ\n" + - "plC3hNS56l/yBYQTeEXoqXVUXDAfBgNVHSMEGDAWgBQDRcZNwPqOqQvagw9BpW0S\n" + - "BkOpXjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAJIO8SiNr9jpLQ\n" + - "eUsFUmbueoxyI5L+P5eV92ceVOJ2tAlBA13vzF1NWlpSlrMmQcVUE/K4D01qtr0k\n" + - "gDs6LUHvj2XXLpyEogitbBgipkQpwCTJVfC9bWYBwEotC7Y8mVjjEV7uXAT71GKT\n" + - "x8XlB9maf+BTZGgyoulA5pTYJ++7s/xX9gzSWCa+eXGcjguBtYYXaAjjAqFGRAvu\n" + - "pz1yrDWcA6H94HeErJKUXBakS0Jm/V33JDuVXY+aZ8EQi2kV82aZbNdXll/R6iGw\n" + - "2ur4rDErnHsiphBgZB71C5FD4cdfSONTsYxmPmyUb5T+KLUouxZ9B0Wh28ucc1Lp\n" + - "rbO7BnjW\n" + - "-----END CERTIFICATE-----\n"); + "MIIFAzCCAuugAwIBAgICEAIwDQYJKoZIhvcNAQEFBQAwgZgxCzAJBgNVBAYTAlVT\n" + + "MQswCQYDVQQIDAJOWTEbMBkGA1UECgwSUVogSW5kdXN0cmllcywgTExDMRswGQYD\n" + + "VQQLDBJRWiBJbmR1c3RyaWVzLCBMTEMxGTAXBgNVBAMMEHF6aW5kdXN0cmllcy5j\n" + + "b20xJzAlBgkqhkiG9w0BCQEWGHN1cHBvcnRAcXppbmR1c3RyaWVzLmNvbTAeFw0x\n" + + "NTAzMTkwMjM4NDVaFw0yNTAzMTkwMjM4NDVaMHMxCzAJBgNVBAYTAkFBMRMwEQYD\n" + + "VQQIDApTb21lIFN0YXRlMQ0wCwYDVQQKDAREZW1vMQ0wCwYDVQQLDAREZW1vMRIw\n" + + "EAYDVQQDDAlsb2NhbGhvc3QxHTAbBgkqhkiG9w0BCQEWDnJvb3RAbG9jYWxob3N0\n" + + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtFzbBDRTDHHmlSVQLqjY\n" + + "aoGax7ql3XgRGdhZlNEJPZDs5482ty34J4sI2ZK2yC8YkZ/x+WCSveUgDQIVJ8oK\n" + + "D4jtAPxqHnfSr9RAbvB1GQoiYLxhfxEp/+zfB9dBKDTRZR2nJm/mMsavY2DnSzLp\n" + + "t7PJOjt3BdtISRtGMRsWmRHRfy882msBxsYug22odnT1OdaJQ54bWJT5iJnceBV2\n" + + "1oOqWSg5hU1MupZRxxHbzI61EpTLlxXJQ7YNSwwiDzjaxGrufxc4eZnzGQ1A8h1u\n" + + "jTaG84S1MWvG7BfcPLW+sya+PkrQWMOCIgXrQnAsUgqQrgxQ8Ocq3G4X9UvBy5VR\n" + + "CwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdl\n" + + "bmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUpG420UhvfwAFMr+8vf3pJunQ\n" + + "gH4wHwYDVR0jBBgwFoAUkKZQt4TUuepf8gWEE3hF6Kl1VFwwDQYJKoZIhvcNAQEF\n" + + "BQADggIBAFXr6G1g7yYVHg6uGfh1nK2jhpKBAOA+OtZQLNHYlBgoAuRRNWdE9/v4\n" + + "J/3Jeid2DAyihm2j92qsQJXkyxBgdTLG+ncILlRElXvG7IrOh3tq/TttdzLcMjaR\n" + + "8w/AkVDLNL0z35shNXih2F9JlbNRGqbVhC7qZl+V1BITfx6mGc4ayke7C9Hm57X0\n" + + "ak/NerAC/QXNs/bF17b+zsUt2ja5NVS8dDSC4JAkM1dD64Y26leYbPybB+FgOxFu\n" + + "wou9gFxzwbdGLCGboi0lNLjEysHJBi90KjPUETbzMmoilHNJXw7egIo8yS5eq8RH\n" + + "i2lS0GsQjYFMvplNVMATDXUPm9MKpCbZ7IlJ5eekhWqvErddcHbzCuUBkDZ7wX/j\n" + + "unk/3DyXdTsSGuZk3/fLEsc4/YTujpAjVXiA1LCooQJ7SmNOpUa66TPz9O7Ufkng\n" + + "+CoTSACmnlHdP7U9WLr5TYnmL9eoHwtb0hwENe1oFC5zClJoSX/7DRexSJfB7YBf\n" + + "vn6JA2xy4C6PqximyCPisErNp85GUcZfo33Np1aywFv9H+a83rSUcV6kpE/jAZio\n" + + "5qLpgIOisArj1HTM6goDWzKhLiR/AeG3IJvgbpr9Gr7uZmfFyQzUjvkJ9cybZRd+\n" + + "G8azmpBBotmKsbtbAU/I/LVk8saeXznshOVVpDRYtVnjZeAneso7\n" + + "-----END CERTIFICATE-----\n" + + "--START INTERMEDIATE CERT--\n" + + "-----BEGIN CERTIFICATE-----\n" + + "MIIFEjCCA/qgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgawxCzAJBgNVBAYTAlVT\n" + + "MQswCQYDVQQIDAJOWTESMBAGA1UEBwwJQ2FuYXN0b3RhMRswGQYDVQQKDBJRWiBJ\n" + + "bmR1c3RyaWVzLCBMTEMxGzAZBgNVBAsMElFaIEluZHVzdHJpZXMsIExMQzEZMBcG\n" + + "A1UEAwwQcXppbmR1c3RyaWVzLmNvbTEnMCUGCSqGSIb3DQEJARYYc3VwcG9ydEBx\n" + + "emluZHVzdHJpZXMuY29tMB4XDTE1MDMwMjAwNTAxOFoXDTM1MDMwMjAwNTAxOFow\n" + + "gZgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOWTEbMBkGA1UECgwSUVogSW5kdXN0\n" + + "cmllcywgTExDMRswGQYDVQQLDBJRWiBJbmR1c3RyaWVzLCBMTEMxGTAXBgNVBAMM\n" + + "EHF6aW5kdXN0cmllcy5jb20xJzAlBgkqhkiG9w0BCQEWGHN1cHBvcnRAcXppbmR1\n" + + "c3RyaWVzLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTDgNLU\n" + + "iohl/rQoZ2bTMHVEk1mA020LYhgfWjO0+GsLlbg5SvWVFWkv4ZgffuVRXLHrwz1H\n" + + "YpMyo+Zh8ksJF9ssJWCwQGO5ciM6dmoryyB0VZHGY1blewdMuxieXP7Kr6XD3GRM\n" + + "GAhEwTxjUzI3ksuRunX4IcnRXKYkg5pjs4nLEhXtIZWDLiXPUsyUAEq1U1qdL1AH\n" + + "EtdK/L3zLATnhPB6ZiM+HzNG4aAPynSA38fpeeZ4R0tINMpFThwNgGUsxYKsP9kh\n" + + "0gxGl8YHL6ZzC7BC8FXIB/0Wteng0+XLAVto56Pyxt7BdxtNVuVNNXgkCi9tMqVX\n" + + "xOk3oIvODDt0UoQUZ/umUuoMuOLekYUpZVk4utCqXXlB4mVfS5/zWB6nVxFX8Io1\n" + + "9FOiDLTwZVtBmzmeikzb6o1QLp9F2TAvlf8+DIGDOo0DpPQUtOUyLPCh5hBaDGFE\n" + + "ZhE56qPCBiQIc4T2klWX/80C5NZnd/tJNxjyUyk7bjdDzhzT10CGRAsqxAnsjvMD\n" + + "2KcMf3oXN4PNgyfpbfq2ipxJ1u777Gpbzyf0xoKwH9FYigmqfRH2N2pEdiYawKrX\n" + + "6pyXzGM4cvQ5X1Yxf2x/+xdTLdVaLnZgwrdqwFYmDejGAldXlYDl3jbBHVM1v+uY\n" + + "5ItGTjk+3vLrxmvGy5XFVG+8fF/xaVfo5TW5AgMBAAGjUDBOMB0GA1UdDgQWBBSQ\n" + + "plC3hNS56l/yBYQTeEXoqXVUXDAfBgNVHSMEGDAWgBQDRcZNwPqOqQvagw9BpW0S\n" + + "BkOpXjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAJIO8SiNr9jpLQ\n" + + "eUsFUmbueoxyI5L+P5eV92ceVOJ2tAlBA13vzF1NWlpSlrMmQcVUE/K4D01qtr0k\n" + + "gDs6LUHvj2XXLpyEogitbBgipkQpwCTJVfC9bWYBwEotC7Y8mVjjEV7uXAT71GKT\n" + + "x8XlB9maf+BTZGgyoulA5pTYJ++7s/xX9gzSWCa+eXGcjguBtYYXaAjjAqFGRAvu\n" + + "pz1yrDWcA6H94HeErJKUXBakS0Jm/V33JDuVXY+aZ8EQi2kV82aZbNdXll/R6iGw\n" + + "2ur4rDErnHsiphBgZB71C5FD4cdfSONTsYxmPmyUb5T+KLUouxZ9B0Wh28ucc1Lp\n" + + "rbO7BnjW\n" + + "-----END CERTIFICATE-----\n"); }); qz.security.setSignatureAlgorithm("SHA512"); // Since 2.1 @@ -1377,17 +1430,17 @@

Options

var info = data[i]; if (i == 0) { - list += "
  • " + + list += "
  • " + " Hostname: " + info.hostname + "" + "
  • " + "
  • " + " Username: " + info.username + "" - "
  • "; + ""; } list += "
  • " + - " Interface: " + (info.name || "UNKNOWN") + (info.id ? " (" + info.id + ")" : "") + - "
      " + listItems(info) + "
    " + - "
  • "; + " Interface: " + (info.name || "UNKNOWN") + (info.id ? " (" + info.id + ")" : "") + + "
      " + listItems(info) + "
    " + + ""; } pinMessage("Network details:
      " + list + "
    "); @@ -1426,11 +1479,11 @@

    Options

    var list = ''; for(var i = 0; i < data.length; i++) { list += "
  • " + (data[i].default ? "* " : "") + data[i].name + "
      " + - "
    • Driver: " + data[i].driver + "
    • " + - "
    • Density: " + data[i].density + "dpi
    • " + - "
    • Connection: " + data[i].connection + "
    • " + - (data[i].trays ? "
    • Trays: " + data[i].trays + "
    • " : "") + - "
  • "; + "
  • Driver: " + data[i].driver + "
  • " + + "
  • Density: " + data[i].density + "dpi
  • " + + "
  • Connection: " + data[i].connection + "
  • " + + (data[i].trays ? "
  • Trays: " + data[i].trays + "
  • " : "") + + ""; } pinMessage("Printer details:
      " + list + "
    "); @@ -1550,82 +1603,82 @@

    Options

    { type: 'raw', format: 'command', flavor: 'base64', data: 'Ck4KcTYwOQpRMjAzLDI2CkI1LDI2LDAsMUEsMyw3LDE1MixCLCIxMjM0IgpBMzEwLDI2LDAsMywx' + - 'LDEsTiwiU0tVIDAwMDAwIE1GRyAwMDAwIgpBMzEwLDU2LDAsMywxLDEsTiwiUVogUFJJTlQgQVBQ' + - 'TEVUIgpBMzEwLDg2LDAsMywxLDEsTiwiVEVTVCBQUklOVCBTVUNDRVNTRlVMIgpBMzEwLDExNiww' + - 'LDMsMSwxLE4sIkZST00gU0FNUExFLkhUTUwiCkEzMTAsMTQ2LDAsMywxLDEsTiwiUVpJTkRVU1RS' + - 'SUVTLkNPTSIKR1cxNTAsMzAwLDMyLDEyOCz/////////6SSSX///////////////////////////' + - '//////////6UlUqX////////////////////////////////////8kqkpKP/////////////////' + - '//////////////////6JUpJSVf//////////////////////////////////9KpKVVU+////////' + - '//////////////////////////8KSSlJJf5/////////////////////////////////9KUqpVU/' + - '/7////////////////////////////////9KqUkokf//P///////////////////////////////' + - '+VKUqpZP//+P///////////////////////////////ElKUlSf///9f/////////////////////' + - '////////+ipSkqin////y/////////////////////////////+lVUpUlX/////r////////////' + - '/////////////////qlJKUql/////+n////////////////////////////BFKVKUl//////8v//' + - '/////////////////////////zVSlKUp///////0f//////////////////////////wiSlSUpf/' + - '//////q///////////////////////////KqlJUpV///////+R//////////////////////////' + - '4UlKSpSX///////9T/////////6L///////////////BKlKpSqP///////1X////////0qg/23/V' + - 'VVVVVVf//8CSlJKklf///////kv///////+pS0/JP8AAAAAAB///wFSlSSpV///////+pf//////' + - '/pUoq+qfwAAAAAAH//+AClSqpUT///////9S///////8pJUlkr+AAAAAAA///4AFJSSSUv//////' + - '/yl///////KVUpTUv8AAAAAAH///gBKSqlVU////////lX//////6UkqoiU/wAAAAAA///+ABKpJ' + - 'Uko////////JH//////UpIiqlJ/AAAAAAD///wACkSUpJX///////6q//////6pVVSqiv4AAAAAA' + - 'f///AAJVVIqpP///////pI//////pSVtSSq/wAAAAAD///8AAJSlVJVf///////Sp/////8Sq//U' + - 'qL/ttttoAP///wAAUpVSpJ///////+pT/////qkn//UlH/////AB////AABKUSpSX///////5Sn/' + - '///+lJ//+pS/////4AP///8AABKUkpVP///////ylP////1Kv//+qr/////AA////4AAKVVJUl//' + - '/////+lKf////KS///8kv////8AH////gAAKSSpJR///////9Kq////9Kv///5Kf////gAf///+A' + - 'AAUlUqov///////1JT////lS////qn////8AD////4AABKpKSqf///////Skj///+kr////JH///' + - '/wAf////wAACkqUlK///////8pKv///ypf///9V////+AD/////AAAFKUVSj///////wqlP///JT' + - '////yR////wAP////8AAAFKqkpv///////JSlf//9Sv////U/////AB/////4AAAVIpKRf//////' + - '+ElV///pS////8of///4AP/////gAAASZVKr///////4qkj///Sn////0v////AA//////AAABUS' + - 'VJH///////glJn//8pP////KH///8AH/////+AAACtUlVf//////+ClRP//qV////9K////gA///' + - '///4AAACEpJK///////8BSqf/+lX////yr///8AD//////wAAAVUqVH///////gUlU//5Rf////R' + - 'P///gAf//////gAAApKqTP//////8AVSV//pU////6qf//+AD//////+AAAAqkki//////8AEpVL' + - '/+qP////1L///wAP//////4AAACSVVB/////+AFUpKX/9KP////Sv//+AB///////AAAAEqSgH//' + - '//+ACkpSUv/lV////6k///4AP//////+AAAAUlSgf////gAJKRUpf/ST////1J///AA///////4A' + - 'AAAVJVB////gAtVFUpV/8lX///+Vf//4AH///////gAAABKSSD///wASSVVJSR/1Vf///8kf//gA' + - '///////+AAAABVUof//4AElUpKqqv/SL////1L//8AD///////4AAAABJJQ//8AFVJKVKSSP+qj/' + - '///Kv//gAf///////gAAAAKSpT/+ACkqSlKUkqf5Rf///6S//+AD///////+AAAAAKqpP/ABJKVS' + - 'klKqU/xUf///qp//wAP///////4AAAAAkko+gASVKUlVKlKX/VK///9Sf/+AB////////gAAAACp' + - 'UrgAKqVKVJKSlKf+Sl///0kf/4AP///////+AAAAABSVIAFJUlKqSUpKV/0pX//8qr//AA//////' + - '//8AAAAACklACSopKSVUqVKX/qpH//okv/4AH////////gAAAAAVVKBUpUqUkkpKSk//SSv/xVK/' + - '/AAAAAAD////AAAAAAJKWSUpVKVVUqVSp/+qqH9SlR/8AAAAAAH///4AAAAABSUklJSSlJJKUkpf' + - '/8klQFSo//gAAAAAA////wAAAAABVKqlUkqlSqkqqU//6pUqkkof8AAAAAAB/r//AAAAAAElEpSK' + - 'qSlSSpJKL//pUqpVKr/wAAAAAAP8v/8AAAAAAJLKUqkkpSqkqSVf//yUkpKSv+AAAAAAAfqf/wAA' + - 'AAAAVClKVVUoklUqqp///UpKVVS/wAAAAAAD+S//AAAAAAAlpSkkkpVKkpKSX///JVKTpR+AAAAA' + - 'AAH9X/8AAAAAABRUpVJUqqSpSUlf///SSk/Sv4AAAAAAA/y//wAAAAAAFSVUlSUkUkpUqr////VS' + - 'v9S/AAAAAAAB/3//AAAAAAAFUkpSlJMqqUpJP////13/pT////////////8AAAAAAAEpJSlSqUkk' + - 'pVS////////Un////////////wAAAAAABJVSlSpUqpUpJX///////8q/////////////gAAAAAAC' + - 'pSqkkpKSUpSSP///////5L////////////+AAAAAAACSkVVKSklKpVV///////+SX///////////' + - '/4AAAAAAAFSqJKlSqqiVSX///////9U/////////////gAAAAAAASpVSlSkklVJU////////yr//' + - '//////////+AAAAAAAAkpJSklKpKSUp////////kn////////////4AAAAAAABJSqlKqkqUqVf//' + - '/////5K/////////////gAAAAAAACpUlKpJKUqlI////////1L////////////+AAAAAAAAFSVKS' + - 'SqkpFKX////////SX////////////4AAAAAAAAiklKlSSpTKKv///////9U/////////////wAAA' + - 'AAAABSpSlSqlSiVJ////////pV/////////////AAAAAAAAVUpSkklSlUqX////////Uv///////' + - '/////8AAAAAAAAkqUpVJJSqpVf///////8pf////////////4AAAAAAAFJKUpKqUpJUT////////' + - '4r/////////////wAAAAAAAKqVKVKUqSSVX///////+Uv/////////////gAAAAAAASUlKSkpKql' + - 'S////////+qf/////////////AAAAAAAEkpKUlUpJJCn////////iH///////////wAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4B+A8AH/AAAAA' + - 'AAAAAAAAAAAAAA//AAfwD4H4HwAAf/4H4DwB//gAAAAAAAAAAAAAAAAAD/+AB/APgfgfAAB//wfw' + - 'PAf/+AAAAAAAAAAAAAgAAAAP/8AH8AfB+D4AAH//B/g8D//4AAAAAAAAAAAADwAAAA//4A/4B8H4' + - 'PgAAfB+H+DwP4HgAAAAAAAAAAAAPwAAAD4fgD/gHw/w+AAB8D4f8PB+AGAAAAAAAAAAAAA/wAAAP' + - 'g+Af/AfD/D4AAHwPh/48HwAAAAAAAAAAAAAAB/4AAA+D4B98A+P8PAAAfA+Hvjw+AAAAAAAAAAAA' + - 'AAAB/4AAD4PgH3wD4/x8AAB8H4e/PD4AAAAAAAAAAAAAAAB/8AAPh8A+PgPn/nwAAH//B5+8Pg/4' + - 'AH/j/x/4/8f+AA/8AA//wD4+A+eefAAAf/4Hj7w+D/gAf+P/H/j/x/4AA/wAD/+APj4B5554AAB/' + - '/AeP/D4P+AB/4/8f+P/H/gAD/AAP/wB8HwH3nvgAAH/wB4f8Pw/4AH/j/x/4/8f+AA/8AA//AH//' + - 'Af+f+AAAfAAHg/wfAPgAAAAAAAAAAAAAf/AAD5+A//+B/w/4AAB8AAeD/B+A+AAAAAAAAAAAAAH/' + - 'gAAPj8D//4D/D/AAAHwAB4H8H+D4AAAAAAAAAAAAB/4AAA+H4P//gP8P8AAAfAAHgPwP//gAAAAA' + - 'AAAAAAAP8AAAD4fh+A/A/w/wAAB8AAeA/Af/+AAAAAAAAAAAAA/AAAAPg/HwB8B+B+AAAHwAB4B8' + - 'Af/4AAAAAAAAAAAADwAAAA+B+fAHwH4H4AAAfAAHgHwAf4AAAAAAAAAAAAAIAAAAD4H/8Afgfgfg' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAAAAAAAClAxLDEK' + 'LDEsTiwiU0tVIDAwMDAwIE1GRyAwMDAwIgpBMzEwLDU2LDAsMywxLDEsTiwiUVogUFJJTlQgQVBQ' + + 'TEVUIgpBMzEwLDg2LDAsMywxLDEsTiwiVEVTVCBQUklOVCBTVUNDRVNTRlVMIgpBMzEwLDExNiww' + + 'LDMsMSwxLE4sIkZST00gU0FNUExFLkhUTUwiCkEzMTAsMTQ2LDAsMywxLDEsTiwiUVpJTkRVU1RS' + + 'SUVTLkNPTSIKR1cxNTAsMzAwLDMyLDEyOCz/////////6SSSX///////////////////////////' + + '//////////6UlUqX////////////////////////////////////8kqkpKP/////////////////' + + '//////////////////6JUpJSVf//////////////////////////////////9KpKVVU+////////' + + '//////////////////////////8KSSlJJf5/////////////////////////////////9KUqpVU/' + + '/7////////////////////////////////9KqUkokf//P///////////////////////////////' + + '+VKUqpZP//+P///////////////////////////////ElKUlSf///9f/////////////////////' + + '////////+ipSkqin////y/////////////////////////////+lVUpUlX/////r////////////' + + '/////////////////qlJKUql/////+n////////////////////////////BFKVKUl//////8v//' + + '/////////////////////////zVSlKUp///////0f//////////////////////////wiSlSUpf/' + + '//////q///////////////////////////KqlJUpV///////+R//////////////////////////' + + '4UlKSpSX///////9T/////////6L///////////////BKlKpSqP///////1X////////0qg/23/V' + + 'VVVVVVf//8CSlJKklf///////kv///////+pS0/JP8AAAAAAB///wFSlSSpV///////+pf//////' + + '/pUoq+qfwAAAAAAH//+AClSqpUT///////9S///////8pJUlkr+AAAAAAA///4AFJSSSUv//////' + + '/yl///////KVUpTUv8AAAAAAH///gBKSqlVU////////lX//////6UkqoiU/wAAAAAA///+ABKpJ' + + 'Uko////////JH//////UpIiqlJ/AAAAAAD///wACkSUpJX///////6q//////6pVVSqiv4AAAAAA' + + 'f///AAJVVIqpP///////pI//////pSVtSSq/wAAAAAD///8AAJSlVJVf///////Sp/////8Sq//U' + + 'qL/ttttoAP///wAAUpVSpJ///////+pT/////qkn//UlH/////AB////AABKUSpSX///////5Sn/' + + '///+lJ//+pS/////4AP///8AABKUkpVP///////ylP////1Kv//+qr/////AA////4AAKVVJUl//' + + '/////+lKf////KS///8kv////8AH////gAAKSSpJR///////9Kq////9Kv///5Kf////gAf///+A' + + 'AAUlUqov///////1JT////lS////qn////8AD////4AABKpKSqf///////Skj///+kr////JH///' + + '/wAf////wAACkqUlK///////8pKv///ypf///9V////+AD/////AAAFKUVSj///////wqlP///JT' + + '////yR////wAP////8AAAFKqkpv///////JSlf//9Sv////U/////AB/////4AAAVIpKRf//////' + + '+ElV///pS////8of///4AP/////gAAASZVKr///////4qkj///Sn////0v////AA//////AAABUS' + + 'VJH///////glJn//8pP////KH///8AH/////+AAACtUlVf//////+ClRP//qV////9K////gA///' + + '///4AAACEpJK///////8BSqf/+lX////yr///8AD//////wAAAVUqVH///////gUlU//5Rf////R' + + 'P///gAf//////gAAApKqTP//////8AVSV//pU////6qf//+AD//////+AAAAqkki//////8AEpVL' + + '/+qP////1L///wAP//////4AAACSVVB/////+AFUpKX/9KP////Sv//+AB///////AAAAEqSgH//' + + '//+ACkpSUv/lV////6k///4AP//////+AAAAUlSgf////gAJKRUpf/ST////1J///AA///////4A' + + 'AAAVJVB////gAtVFUpV/8lX///+Vf//4AH///////gAAABKSSD///wASSVVJSR/1Vf///8kf//gA' + + '///////+AAAABVUof//4AElUpKqqv/SL////1L//8AD///////4AAAABJJQ//8AFVJKVKSSP+qj/' + + '///Kv//gAf///////gAAAAKSpT/+ACkqSlKUkqf5Rf///6S//+AD///////+AAAAAKqpP/ABJKVS' + + 'klKqU/xUf///qp//wAP///////4AAAAAkko+gASVKUlVKlKX/VK///9Sf/+AB////////gAAAACp' + + 'UrgAKqVKVJKSlKf+Sl///0kf/4AP///////+AAAAABSVIAFJUlKqSUpKV/0pX//8qr//AA//////' + + '//8AAAAACklACSopKSVUqVKX/qpH//okv/4AH////////gAAAAAVVKBUpUqUkkpKSk//SSv/xVK/' + + '/AAAAAAD////AAAAAAJKWSUpVKVVUqVSp/+qqH9SlR/8AAAAAAH///4AAAAABSUklJSSlJJKUkpf' + + '/8klQFSo//gAAAAAA////wAAAAABVKqlUkqlSqkqqU//6pUqkkof8AAAAAAB/r//AAAAAAElEpSK' + + 'qSlSSpJKL//pUqpVKr/wAAAAAAP8v/8AAAAAAJLKUqkkpSqkqSVf//yUkpKSv+AAAAAAAfqf/wAA' + + 'AAAAVClKVVUoklUqqp///UpKVVS/wAAAAAAD+S//AAAAAAAlpSkkkpVKkpKSX///JVKTpR+AAAAA' + + 'AAH9X/8AAAAAABRUpVJUqqSpSUlf///SSk/Sv4AAAAAAA/y//wAAAAAAFSVUlSUkUkpUqr////VS' + + 'v9S/AAAAAAAB/3//AAAAAAAFUkpSlJMqqUpJP////13/pT////////////8AAAAAAAEpJSlSqUkk' + + 'pVS////////Un////////////wAAAAAABJVSlSpUqpUpJX///////8q/////////////gAAAAAAC' + + 'pSqkkpKSUpSSP///////5L////////////+AAAAAAACSkVVKSklKpVV///////+SX///////////' + + '/4AAAAAAAFSqJKlSqqiVSX///////9U/////////////gAAAAAAASpVSlSkklVJU////////yr//' + + '//////////+AAAAAAAAkpJSklKpKSUp////////kn////////////4AAAAAAABJSqlKqkqUqVf//' + + '/////5K/////////////gAAAAAAACpUlKpJKUqlI////////1L////////////+AAAAAAAAFSVKS' + + 'SqkpFKX////////SX////////////4AAAAAAAAiklKlSSpTKKv///////9U/////////////wAAA' + + 'AAAABSpSlSqlSiVJ////////pV/////////////AAAAAAAAVUpSkklSlUqX////////Uv///////' + + '/////8AAAAAAAAkqUpVJJSqpVf///////8pf////////////4AAAAAAAFJKUpKqUpJUT////////' + + '4r/////////////wAAAAAAAKqVKVKUqSSVX///////+Uv/////////////gAAAAAAASUlKSkpKql' + + 'S////////+qf/////////////AAAAAAAEkpKUlUpJJCn////////iH///////////wAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4B+A8AH/AAAAA' + + 'AAAAAAAAAAAAAA//AAfwD4H4HwAAf/4H4DwB//gAAAAAAAAAAAAAAAAAD/+AB/APgfgfAAB//wfw' + + 'PAf/+AAAAAAAAAAAAAgAAAAP/8AH8AfB+D4AAH//B/g8D//4AAAAAAAAAAAADwAAAA//4A/4B8H4' + + 'PgAAfB+H+DwP4HgAAAAAAAAAAAAPwAAAD4fgD/gHw/w+AAB8D4f8PB+AGAAAAAAAAAAAAA/wAAAP' + + 'g+Af/AfD/D4AAHwPh/48HwAAAAAAAAAAAAAAB/4AAA+D4B98A+P8PAAAfA+Hvjw+AAAAAAAAAAAA' + + 'AAAB/4AAD4PgH3wD4/x8AAB8H4e/PD4AAAAAAAAAAAAAAAB/8AAPh8A+PgPn/nwAAH//B5+8Pg/4' + + 'AH/j/x/4/8f+AA/8AA//wD4+A+eefAAAf/4Hj7w+D/gAf+P/H/j/x/4AA/wAD/+APj4B5554AAB/' + + '/AeP/D4P+AB/4/8f+P/H/gAD/AAP/wB8HwH3nvgAAH/wB4f8Pw/4AH/j/x/4/8f+AA/8AA//AH//' + + 'Af+f+AAAfAAHg/wfAPgAAAAAAAAAAAAAf/AAD5+A//+B/w/4AAB8AAeD/B+A+AAAAAAAAAAAAAH/' + + 'gAAPj8D//4D/D/AAAHwAB4H8H+D4AAAAAAAAAAAAB/4AAA+H4P//gP8P8AAAfAAHgPwP//gAAAAA' + + 'AAAAAAAP8AAAD4fh+A/A/w/wAAB8AAeA/Af/+AAAAAAAAAAAAA/AAAAPg/HwB8B+B+AAAHwAB4B8' + + 'Af/4AAAAAAAAAAAADwAAAA+B+fAHwH4H4AAAfAAHgHwAf4AAAAAAAAAAAAAIAAAAD4H/8Afgfgfg' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAClAxLDEK' } ]; @@ -1814,21 +1867,21 @@

    Options

    format: 'html', flavor: 'plain', data: '' + - '' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - '
    ' + - '

    * QZ Tray HTML Sample Print *

    ' + - ' Version: ' + qzVersion + '
    ' + - ' Source: https://qz.io/' + - '
    ' + - ' ' + - '
    ' + - '' + - '', + '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + '
    ' + + '

    * QZ Tray HTML Sample Print *

    ' + + ' Version: ' + qzVersion + '
    ' + + ' Source: https://qz.io/' + + '
    ' + + ' ' + + '
    ' + + '' + + '', options: opts } ]; @@ -1901,6 +1954,26 @@

    Options

    } + // Socket // + function openSocket() { + qz.socket.open($("#socketHost").val(), $("#socketPort").val()).then(function() { + displayMessage("Socket opened"); + }).catch(displayError); + } + + function sendSocketData() { + qz.socket.sendData($("#socketHost").val(), $("#socketPort").val(), $("#socketData").val()).then(function(response) { + displayMessage("Response: " + response + "
    "); + }).catch(displayError); + } + + function closeSocket() { + qz.socket.close($("#socketHost").val(), $("#socketPort").val()).then(function() { + displayMessage("Socket closed"); + }).catch(displayError); + } + + /// USB /// function listUsbDevices() { qz.usb.listDevices(true).then(function(data) { @@ -1910,9 +1983,9 @@

    Options

    if (device.hub) { list += "USB Hub"; } list += "

    " + - " VendorID: 0x" + device.vendorId + "" + - usbButton(["usbVendor", "usbProduct"], [device.vendorId, device.productId]) + "
    " + - " ProductID: 0x" + device.productId + "
    "; + " VendorID: 0x" + device.vendorId + "" + + usbButton(["usbVendor", "usbProduct"], [device.vendorId, device.productId]) + "
    " + + " ProductID: 0x" + device.productId + "
    "; if (device.manufacturer) { list += " Manufacturer: " + device.manufacturer + "
    "; } if (device.product) { list += " Product: " + device.product + "
    "; } @@ -2041,15 +2114,15 @@

    Options

    var device = data[i]; list += "

    " + - " VendorID: 0x" + device.vendorId + "" + - usbButton(["hidVendor", "hidProduct", "hidUsagePage", "hidSerial"], - [device.vendorId, device.productId, device.usagePage, device.serial]) + "
    " + - " ProductID: 0x" + device.productId + "
    " + - (device.usagePage ? " Usage Page: 0x" + device.usagePage + "
    " : "") + - (device.serial ? " Serial: " + device.serial + "
    " : "") + - (device.manufacturer ? " Manufacturer: " + device.manufacturer + "
    " : "") + - (device.product ? " Product: " + device.product + "
    " : "") + - "


    "; + " VendorID: 0x" + device.vendorId + "" + + usbButton(["hidVendor", "hidProduct", "hidUsagePage", "hidSerial"], + [device.vendorId, device.productId, device.usagePage, device.serial]) + "
    " + + " ProductID: 0x" + device.productId + "
    " + + (device.usagePage ? " Usage Page: 0x" + device.usagePage + "
    " : "") + + (device.serial ? " Serial: " + device.serial + "
    " : "") + + (device.manufacturer ? " Manufacturer: " + device.manufacturer + "
    " : "") + + (device.product ? " Product: " + device.product + "
    " : "") + + "


    "; } pinMessage("Available hid devices:
    " + list); @@ -2545,8 +2618,8 @@

    Options

    qz.file.setFileCallbacks(function(streamEvent) { if (streamEvent.type !== 'ERROR') { var text = "
    File IO Event
    " + - "File: " + streamEvent.file + "
    " + - "Event:" + streamEvent.eventType + "
    "; + "File: " + streamEvent.file + "
    " + + "Event:" + streamEvent.eventType + "
    "; if (streamEvent.fileData) { text += "Data:
    " + streamEvent.fileData.replace(/\r?\n/g, "
    ") + "
    "; diff --git a/src/qz/communication/SocketIO.java b/src/qz/communication/SocketIO.java new file mode 100644 index 000000000..6ffe19980 --- /dev/null +++ b/src/qz/communication/SocketIO.java @@ -0,0 +1,76 @@ +package qz.communication; + +import org.apache.commons.lang3.ArrayUtils; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import qz.utils.DeviceUtilities; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.util.ArrayList; + +public class SocketIO { + + private static final Logger log = LoggerFactory.getLogger(SocketIO.class); + + private String host; + private int port; + + private Socket socket; + private DataOutputStream dataOut; + private DataInputStream dataIn; + + public SocketIO(String host, int port) { + this.host = host; + this.port = port; + } + + public boolean open() throws IOException { + socket = new Socket(host, port); + dataOut = new DataOutputStream(socket.getOutputStream()); + dataIn = new DataInputStream(socket.getInputStream()); + + return socket.isConnected(); + } + + public String sendData(JSONObject params) throws JSONException, IOException { + log.debug("Sending data over [{}:{}]", host, port); + dataOut.write(DeviceUtilities.getDataBytes(params, null)); //fixme - charset from options + dataOut.flush(); + + return processSocketResponse(); + } + + //fixme - this is blocking, use a socketchannel?? + public String processSocketResponse() throws IOException { + byte[] response = new byte[1024]; + ArrayList fullResponse = new ArrayList<>(); + do { + int size = dataIn.read(response); + for(int i = 0; i < size; i++) { + fullResponse.add(response[i]); + } + } + while(dataIn.available() > 0); + + return new String(ArrayUtils.toPrimitive(fullResponse.toArray(new Byte[0]))); //fixme - charset from options + } + + public void close() throws IOException { + dataOut.close(); + socket.close(); + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + +} diff --git a/src/qz/utils/SocketUtilities.java b/src/qz/utils/SocketUtilities.java new file mode 100644 index 000000000..b204ea538 --- /dev/null +++ b/src/qz/utils/SocketUtilities.java @@ -0,0 +1,39 @@ +package qz.utils; + +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.eclipse.jetty.websocket.api.Session; +import qz.communication.SocketIO; +import qz.ws.PrintSocketClient; +import qz.ws.SocketConnection; + +import java.io.IOException; + +public class SocketUtilities { + + public static void setupSocket(final Session session, String UID, SocketConnection connection, JSONObject params) throws JSONException { + final String host = params.getString("host"); + final int port = params.getInt("port"); + final String location = String.format("%s:%s", host, port); + + if (connection.getNetworkSocket(location) != null) { + PrintSocketClient.sendError(session, UID, String.format("Socket [%s] is already open", location)); + return; + } + + try { + final SocketIO protocol = new SocketIO(host, port); + + if (protocol.open()) { + connection.addNetworkSocket(location, protocol); + PrintSocketClient.sendResult(session, UID, null); + } else { + PrintSocketClient.sendError(session, UID, String.format("Unable to open socket [%s]", location)); + } + } + catch(IOException e) { + PrintSocketClient.sendError(session, UID, e); + } + } + +} diff --git a/src/qz/ws/PrintSocketClient.java b/src/qz/ws/PrintSocketClient.java index e234ccf44..f4df43f1f 100644 --- a/src/qz/ws/PrintSocketClient.java +++ b/src/qz/ws/PrintSocketClient.java @@ -300,6 +300,34 @@ private void processMessage(Session session, JSONObject json, SocketConnection c break; } + //fixme - naming?? + case SOCKET_OPEN_PORT: + SocketUtilities.setupSocket(session, UID, connection, params); + break; + case SOCKET_SEND_DATA: { + String location = String.format("%s:%s", params.optString("host"), params.optInt("port")); + SocketIO socket = connection.getNetworkSocket(location); + if (socket != null) { + String reply = socket.sendData(params); + sendResult(session, UID, reply); + } else { + sendError(session, UID, String.format("Socket [%s] is not open.", location)); + } + break; + } + case SOCKET_CLOSE_PORT: { + String location = String.format("%s:%s", params.optString("host"), params.optInt("port")); + SocketIO socket = connection.getNetworkSocket(location); + if (socket != null) { + socket.close(); + connection.removeNetworkSocket(location); + sendResult(session, UID, null); + } else { + sendError(session, UID, String.format("Socket [%s] is not open.", location)); + } + break; + } + case USB_LIST_DEVICES: sendResult(session, UID, UsbUtilities.getUsbDevicesJSON(params.getBoolean("includeHubs"))); break; @@ -372,7 +400,7 @@ private void processMessage(Session session, JSONObject json, SocketConnection c break; } case USB_SEND_DATA: - case HID_SEND_FEATURE_REPORT : + case HID_SEND_FEATURE_REPORT: case HID_SEND_DATA: { DeviceIO usb = connection.getDevice(dOpts); if (usb != null) { @@ -391,7 +419,7 @@ private void processMessage(Session session, JSONObject json, SocketConnection c break; } case USB_READ_DATA: - case HID_GET_FEATURE_REPORT : + case HID_GET_FEATURE_REPORT: case HID_READ_DATA: { DeviceIO usb = connection.getDevice(dOpts); if (usb != null) { diff --git a/src/qz/ws/SocketConnection.java b/src/qz/ws/SocketConnection.java index 86afc7cd2..0352ced3e 100644 --- a/src/qz/ws/SocketConnection.java +++ b/src/qz/ws/SocketConnection.java @@ -5,8 +5,8 @@ import org.slf4j.LoggerFactory; import qz.auth.Certificate; import qz.communication.*; -import qz.printer.status.StatusSession; import qz.printer.status.StatusMonitor; +import qz.printer.status.StatusSession; import qz.utils.FileWatcher; import java.io.IOException; @@ -25,6 +25,8 @@ public class SocketConnection { // serial port -> open SerialIO private final HashMap openSerialPorts = new HashMap<>(); + // socket 'host:port' -> open ProtocolIO + private final HashMap openNetworkSockets = new HashMap<>(); // absolute path -> open file listener private final HashMap openFiles = new HashMap<>(); @@ -59,6 +61,19 @@ public void removeSerialPort(String port) { } + public void addNetworkSocket(String location, SocketIO io) { + openNetworkSockets.put(location, io); + } + + public SocketIO getNetworkSocket(String location) { + return openNetworkSockets.get(location); + } + + public void removeNetworkSocket(String location) { + openNetworkSockets.remove(location); + } + + public boolean isDeviceListening() { return deviceListener != null; } @@ -141,6 +156,10 @@ public synchronized void disconnect() throws SerialPortException, DeviceExceptio sio.close(); } + for(SocketIO pio : openNetworkSockets.values()) { + pio.close(); + } + for(DeviceIO dio : openDevices.values()) { dio.setStreaming(false); dio.close(); diff --git a/src/qz/ws/SocketMethod.java b/src/qz/ws/SocketMethod.java index b29dc9e7f..1887fc47f 100644 --- a/src/qz/ws/SocketMethod.java +++ b/src/qz/ws/SocketMethod.java @@ -14,6 +14,10 @@ public enum SocketMethod { SERIAL_SEND_DATA("serial.sendData", true, "send data over a serial port"), SERIAL_CLOSE_PORT("serial.closePort", true, "close a serial port"), + SOCKET_OPEN_PORT("socket.open", true, "open a socket"), + SOCKET_SEND_DATA("socket.sendData", true, "send data over a socket"), + SOCKET_CLOSE_PORT("socket.close", true, "close a socket"), + USB_LIST_DEVICES("usb.listDevices", true, "access USB devices"), USB_LIST_INTERFACES("usb.listInterfaces", true, "access USB devices"), USB_LIST_ENDPOINTS("usb.listEndpoints", true, "access USB devices"), From fb11ee3989c1985b217f8d92fc28bfe35144f68a Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Tue, 2 Feb 2021 12:20:44 -0500 Subject: [PATCH 3/5] Remove fixmes, Settle on "socket" naming --- js/qz-tray.js | 1 - src/qz/ws/PrintSocketClient.java | 2 -- 2 files changed, 3 deletions(-) diff --git a/js/qz-tray.js b/js/qz-tray.js index 0c1ca824b..e1469c056 100644 --- a/js/qz-tray.js +++ b/js/qz-tray.js @@ -1606,7 +1606,6 @@ var qz = (function() { } }, - //fixme - naming?? /** * Calls related to interaction with communication sockets. * @namespace qz.socket diff --git a/src/qz/ws/PrintSocketClient.java b/src/qz/ws/PrintSocketClient.java index f4df43f1f..51e12dc93 100644 --- a/src/qz/ws/PrintSocketClient.java +++ b/src/qz/ws/PrintSocketClient.java @@ -299,8 +299,6 @@ private void processMessage(Session session, JSONObject json, SocketConnection c } break; } - - //fixme - naming?? case SOCKET_OPEN_PORT: SocketUtilities.setupSocket(session, UID, connection, params); break; From 6193a37b24071ab4095cc5e951cecf8af4b9d814 Mon Sep 17 00:00:00 2001 From: Berenz Date: Thu, 11 Feb 2021 20:15:20 -0500 Subject: [PATCH 4/5] Change socket model to use streams for listening support --- js/qz-tray.js | 42 +++++++++++++++++++++-- sample.html | 14 ++++++-- src/qz/communication/SerialOptions.java | 2 +- src/qz/communication/SocketIO.java | 18 ++++++---- src/qz/utils/SocketUtilities.java | 44 +++++++++++++++++++++++-- src/qz/ws/PrintSocketClient.java | 7 ++-- src/qz/ws/StreamEvent.java | 2 +- 7 files changed, 108 insertions(+), 21 deletions(-) diff --git a/js/qz-tray.js b/js/qz-tray.js index e1469c056..e120457bf 100644 --- a/js/qz-tray.js +++ b/js/qz-tray.js @@ -57,7 +57,7 @@ var qz = (function() { //stream types streams: { - serial: 'SERIAL', usb: 'USB', hid: 'HID', printer: 'PRINTER', file: 'FILE' + serial: 'SERIAL', usb: 'USB', hid: 'HID', printer: 'PRINTER', file: 'FILE', socket: 'SOCKET' }, @@ -296,6 +296,9 @@ var qz = (function() { _qz.serial.callSerial(JSON.parse(returned.event)); break; + case _qz.streams.socket: + _qz.socket.callSocket(JSON.parse(returned.event)); + break; case _qz.streams.usb: if (!returned.event) { returned.event = JSON.stringify({ vendorId: returned.key[0], productId: returned.key[1], output: returned.data }); @@ -472,6 +475,22 @@ var qz = (function() { }, + socket: { + /** List of functions called when receiving data from network socket connection. */ + socketCallbacks: [], + /** Calls all functions registered to listen for network socket events. */ + callSocket: function(socketEvent) { + if (Array.isArray(_qz.socket.socketCallbacks)) { + for(var i = 0; i < _qz.socket.socketCallbacks.length; i++) { + _qz.socket.socketCallbacks[i](socketEvent); + } + } else { + _qz.socket.socketCallbacks(socketEvent); + } + } + }, + + usb: { /** List of functions called when receiving data from usb connection. */ usbCallbacks: [], @@ -1616,13 +1635,16 @@ var qz = (function() { * * @param {string} host The connection hostname. * @param {number} port The connection port number. + * @param {Object} [options] Network socket configuration. + * @param {string} [options.encoding='UTF-8'] Character set for communications. * * @memberof qz.socket */ - open: function(host, port) { + open: function(host, port, options) { var params = { host: host, - port: port + port: port, + options: options }; return _qz.websocket.dataPromise("socket.open", params); }, @@ -1666,6 +1688,20 @@ var qz = (function() { data: data }; return _qz.websocket.dataPromise("socket.sendData", params); + }, + + /** + * List of functions called for any response from open network sockets. + * Event data will contain {string} host and {number} port for all types. + * For RECEIVE types, {string} response. + * For ERROR types, {string} exception. + * + * @param {Function|Array} calls Single or array of Function({Object} eventData) calls. + * + * @memberof qz.socket + */ + setSocketCallbacks: function(calls) { + _qz.socket.socketCallbacks = calls; } }, diff --git a/sample.html b/sample.html index 4d002a130..370334bcf 100755 --- a/sample.html +++ b/sample.html @@ -1962,9 +1962,7 @@

    Options

    } function sendSocketData() { - qz.socket.sendData($("#socketHost").val(), $("#socketPort").val(), $("#socketData").val()).then(function(response) { - displayMessage("Response: " + response + "
    "); - }).catch(displayError); + qz.socket.sendData($("#socketHost").val(), $("#socketPort").val(), $("#socketData").val()).catch(displayError); } function closeSocket() { @@ -2566,6 +2564,16 @@

    Options

    } }); + qz.socket.setSocketCallbacks(function(socketEvent) { + if (socketEvent.type !== 'ERROR') { + console.log('Socket', socketEvent.host, socketEvent.port, 'received response', socketEvent.response); + displayMessage("Received output from network socket [" + socketEvent.host + ":" + socketEvent.port + "]: " + socketEvent.response + ""); + } else { + console.log(socketEvent.exception); + displayMessage("Received error from network socket [" + socketEvent.host + ":" + socketEvent.port + "]: " + socketEvent.exception + "", 'alert-error'); + } + }); + qz.usb.setUsbCallbacks(function(streamEvent) { var vendor = streamEvent.vendorId; var product = streamEvent.productId; diff --git a/src/qz/communication/SerialOptions.java b/src/qz/communication/SerialOptions.java index 83855785d..65476c062 100644 --- a/src/qz/communication/SerialOptions.java +++ b/src/qz/communication/SerialOptions.java @@ -70,7 +70,7 @@ public SerialOptions(JSONObject serialOpts, boolean isOpening) { } if (!serialOpts.isNull("encoding")) { - try { portSettings.encoding =Charset.forName(serialOpts.getString("encoding")); } + try { portSettings.encoding = Charset.forName(serialOpts.getString("encoding")); } catch(JSONException e) { LoggerUtilities.optionWarn(log, "string", "encoding", serialOpts.opt("encoding")); } } } diff --git a/src/qz/communication/SocketIO.java b/src/qz/communication/SocketIO.java index 6ffe19980..69e5d0213 100644 --- a/src/qz/communication/SocketIO.java +++ b/src/qz/communication/SocketIO.java @@ -11,6 +11,7 @@ import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; +import java.nio.charset.Charset; import java.util.ArrayList; public class SocketIO { @@ -19,14 +20,16 @@ public class SocketIO { private String host; private int port; + private Charset encoding; private Socket socket; private DataOutputStream dataOut; private DataInputStream dataIn; - public SocketIO(String host, int port) { + public SocketIO(String host, int port, Charset encoding) { this.host = host; this.port = port; + this.encoding = encoding; } public boolean open() throws IOException { @@ -37,15 +40,16 @@ public boolean open() throws IOException { return socket.isConnected(); } - public String sendData(JSONObject params) throws JSONException, IOException { + public boolean isOpen() { + return socket.isConnected(); + } + + public void sendData(JSONObject params) throws JSONException, IOException { log.debug("Sending data over [{}:{}]", host, port); - dataOut.write(DeviceUtilities.getDataBytes(params, null)); //fixme - charset from options + dataOut.write(DeviceUtilities.getDataBytes(params, encoding)); dataOut.flush(); - - return processSocketResponse(); } - //fixme - this is blocking, use a socketchannel?? public String processSocketResponse() throws IOException { byte[] response = new byte[1024]; ArrayList fullResponse = new ArrayList<>(); @@ -57,7 +61,7 @@ public String processSocketResponse() throws IOException { } while(dataIn.available() > 0); - return new String(ArrayUtils.toPrimitive(fullResponse.toArray(new Byte[0]))); //fixme - charset from options + return new String(ArrayUtils.toPrimitive(fullResponse.toArray(new Byte[0])), encoding); } public void close() throws IOException { diff --git a/src/qz/utils/SocketUtilities.java b/src/qz/utils/SocketUtilities.java index b204ea538..842a2b80c 100644 --- a/src/qz/utils/SocketUtilities.java +++ b/src/qz/utils/SocketUtilities.java @@ -3,14 +3,21 @@ import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; import org.eclipse.jetty.websocket.api.Session; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import qz.communication.SocketIO; import qz.ws.PrintSocketClient; import qz.ws.SocketConnection; +import qz.ws.StreamEvent; import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; public class SocketUtilities { + private static final Logger log = LoggerFactory.getLogger(SocketUtilities.class); + public static void setupSocket(final Session session, String UID, SocketConnection connection, JSONObject params) throws JSONException { final String host = params.getString("host"); final int port = params.getInt("port"); @@ -21,11 +28,42 @@ public static void setupSocket(final Session session, String UID, SocketConnecti return; } + //TODO - move to dedicated options class? + Charset encoding = StandardCharsets.UTF_8; + if (!params.isNull("encoding")) { + try { encoding = Charset.forName(params.getString("encoding")); } + catch(JSONException e) { LoggerUtilities.optionWarn(log, "string", "encoding", params.opt("encoding")); } + } + try { - final SocketIO protocol = new SocketIO(host, port); + final SocketIO socket = new SocketIO(host, port, encoding); + + if (socket.open()) { + connection.addNetworkSocket(location, socket); + + new Thread(() -> { + StreamEvent event = new StreamEvent(StreamEvent.Stream.SOCKET, StreamEvent.Type.RECEIVE) + .withData("host", host).withData("port", port); + + try { + while(socket.isOpen()) { + String response = socket.processSocketResponse(); + + if (response != null) { + log.debug("Received socket response: {}", response); + PrintSocketClient.sendStream(session, event.withData("response", response)); + } + } + } + catch(IOException e) { + StreamEvent eventErr = new StreamEvent(StreamEvent.Stream.SOCKET, StreamEvent.Type.ERROR) + .withData("host", host).withData("port", port).withException(e); + PrintSocketClient.sendStream(session, eventErr); + } + + try { Thread.sleep(100); } catch(Exception ignore) {} + }).start(); - if (protocol.open()) { - connection.addNetworkSocket(location, protocol); PrintSocketClient.sendResult(session, UID, null); } else { PrintSocketClient.sendError(session, UID, String.format("Unable to open socket [%s]", location)); diff --git a/src/qz/ws/PrintSocketClient.java b/src/qz/ws/PrintSocketClient.java index 51e12dc93..eb6f2f002 100644 --- a/src/qz/ws/PrintSocketClient.java +++ b/src/qz/ws/PrintSocketClient.java @@ -299,6 +299,7 @@ private void processMessage(Session session, JSONObject json, SocketConnection c } break; } + case SOCKET_OPEN_PORT: SocketUtilities.setupSocket(session, UID, connection, params); break; @@ -306,8 +307,8 @@ private void processMessage(Session session, JSONObject json, SocketConnection c String location = String.format("%s:%s", params.optString("host"), params.optInt("port")); SocketIO socket = connection.getNetworkSocket(location); if (socket != null) { - String reply = socket.sendData(params); - sendResult(session, UID, reply); + socket.sendData(params); + sendResult(session, UID, null); } else { sendError(session, UID, String.format("Socket [%s] is not open.", location)); } @@ -656,7 +657,7 @@ public static void sendResult(Session session, String messageUID, Object returnV */ public static void sendError(Session session, String messageUID, Exception ex) { String message = ex.getMessage(); - if (message == null || message.equals("")) { + if (message == null || message.isEmpty()) { message = ex.getClass().getSimpleName(); } diff --git a/src/qz/ws/StreamEvent.java b/src/qz/ws/StreamEvent.java index 01560bc78..908a392ad 100644 --- a/src/qz/ws/StreamEvent.java +++ b/src/qz/ws/StreamEvent.java @@ -8,7 +8,7 @@ public class StreamEvent { public enum Stream { - SERIAL, USB, HID, PRINTER, FILE + SERIAL, USB, HID, PRINTER, FILE, SOCKET } public enum Type { From b699d5d908578a15a3970cb6cd1c078617ff5676 Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Tue, 16 Mar 2021 15:17:13 -0400 Subject: [PATCH 5/5] Add disclaimer about Socket API usage. --- sample.html | 1 + 1 file changed, 1 insertion(+) diff --git a/sample.html b/sample.html index 370334bcf..fa514b592 100755 --- a/sample.html +++ b/sample.html @@ -777,6 +777,7 @@

    Serial

    Socket

    +

    Socket API is experimental; API subject to change.