From 210e40920c72b5da33130798f4aba26fbfaba59c Mon Sep 17 00:00:00 2001 From: Anton Thomasson Date: Sun, 1 Dec 2019 20:27:00 +0100 Subject: [PATCH] Actually add old project files --- qml/pages/ChoiceSetting.qml | 70 ++++ qml/pages/InputDialog.qml | 52 +++ qml/pages/IntegerInputDialog.qml | 43 +++ qml/pages/IntegerSetting.qml | 50 +++ qml/pages/PrinterPage.qml | 130 ++++++++ qml/pages/RangeSetting.qml | 30 ++ qml/pages/Setting.qml | 21 ++ qml/pages/printer.svg | 56 ++++ qml/pages/utils.js | 45 +++ rpm/harbour-seaprint.spec | 67 ++++ src/bytestream.cpp | 553 +++++++++++++++++++++++++++++++ src/bytestream.h | 189 +++++++++++ src/ippdiscovery.cpp | 184 ++++++++++ src/ippdiscovery.h | 29 ++ src/ippmsg.cpp | 278 ++++++++++++++++ src/ippmsg.h | 84 +++++ src/ippprinter.cpp | 120 +++++++ src/ippprinter.h | 46 +++ 18 files changed, 2047 insertions(+) create mode 100644 qml/pages/ChoiceSetting.qml create mode 100644 qml/pages/InputDialog.qml create mode 100644 qml/pages/IntegerInputDialog.qml create mode 100644 qml/pages/IntegerSetting.qml create mode 100644 qml/pages/PrinterPage.qml create mode 100644 qml/pages/RangeSetting.qml create mode 100644 qml/pages/Setting.qml create mode 100644 qml/pages/printer.svg create mode 100644 qml/pages/utils.js create mode 100644 rpm/harbour-seaprint.spec create mode 100644 src/bytestream.cpp create mode 100644 src/bytestream.h create mode 100644 src/ippdiscovery.cpp create mode 100644 src/ippdiscovery.h create mode 100644 src/ippmsg.cpp create mode 100644 src/ippmsg.h create mode 100644 src/ippprinter.cpp create mode 100644 src/ippprinter.h diff --git a/qml/pages/ChoiceSetting.qml b/qml/pages/ChoiceSetting.qml new file mode 100644 index 0000000..1a7e01c --- /dev/null +++ b/qml/pages/ChoiceSetting.qml @@ -0,0 +1,70 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +Setting { + property var choices + + function prettifyChoice(name, value) + { + switch(name) { + case "print-quality": + switch(value) { + case 3: + return "draft"; + case 4: + return "normal"; + case 5: + return "high"; + default: + return "unknown quality "+value + } + case "orientation-requested": + switch(value) { + case 3: + return "portrait"; + case 4: + return "landscape"; + case 5: + return "reverse landscape"; + case 6: + return "reverse portrait"; + default: + return "unknown orientation "+value + } + case "printer-resolution": + + var units = ""; + if(value.units==3) { + units="dpi"; + } else if (units==4){ + units="dots/cm" + } + return ""+value.x+"x"+value.y+units; + } + return value; + } + ValueButton { + enabled: valid + anchors.verticalCenter: parent.verticalCenter + label: prettyName + value: prettifyChoice(name, choice ? choice : default_choice) + onClicked: parent.clicked() + } + + property var menu: ContextMenu { + id: menu + Repeater { + model: choices + MenuItem { + text: prettifyChoice(name, choices[index]) + onClicked: + { + choice = choices[index]; + } + } + } + + } + + +} diff --git a/qml/pages/InputDialog.qml b/qml/pages/InputDialog.qml new file mode 100644 index 0000000..5dbfcdb --- /dev/null +++ b/qml/pages/InputDialog.qml @@ -0,0 +1,52 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import seaprint.ippprinter 1.0 + +Dialog { + id: dialog + + property string value + property string title + canAccept: label.text != label._default + + Column { + width: parent.width + + DialogHeader { } + + TextField { + id: valueField + width: parent.width + placeholderText: "192.168.1.1/ipp/print" + + label: title + } + Label { + id: label + x: Theme.paddingLarge + property string _default: "No printer found" + text: _default + } + IppPrinter { + id: printer + url: valueField.text + onAttrsChanged: { + if(printer.attrs["printer-name"]) { + label.text = "Found: "+printer.attrs["printer-name"].value + } + else + { + label.text = label._default + } + + } + } + } + + onDone: { + if (result == DialogResult.Accepted) { + value = valueField.text + } + } + +} diff --git a/qml/pages/IntegerInputDialog.qml b/qml/pages/IntegerInputDialog.qml new file mode 100644 index 0000000..414bf2a --- /dev/null +++ b/qml/pages/IntegerInputDialog.qml @@ -0,0 +1,43 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +Dialog { + id: dialog + + property int value + property int min; + property int max; + property string title + canAccept: valueField.acceptableInput + + Component.onCompleted: + { + + console.log("adasdsa", title) + } + + Column { + width: parent.width + + DialogHeader { } + + TextField { + id: valueField + validator: IntValidator{bottom: min; top: max;} + width: parent.width + placeholderText: ""+min+"-"+max + label: title + focus: true + labelVisible: true + inputMethodHints: Qt.ImhDigitsOnly + + } + } + + onDone: { + if (result == DialogResult.Accepted) { + value = valueField.text + } + } + +} diff --git a/qml/pages/IntegerSetting.qml b/qml/pages/IntegerSetting.qml new file mode 100644 index 0000000..ad06862 --- /dev/null +++ b/qml/pages/IntegerSetting.qml @@ -0,0 +1,50 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +Setting { + + property int low + property int high + + ValueButton { + enabled: valid + anchors.verticalCenter: parent.verticalCenter + label: prettyName + value: choice ? choice : default_choice + onClicked: parent.clicked() + } + + property var menu: ContextMenu { + id: menu + MenuItem { + Slider + { + minimumValue: low + maximumValue: high < 100 ? high : 100 + width: parent.width + stepSize: 1 + value: choice + onValueChanged: + { + choice = value; + } + } + IconButton + { + anchors.right: parent.right + icon.source: "image://theme/icon-s-edit" + onClicked: {var dialog = pageStack.push(Qt.resolvedUrl("IntegerInputDialog.qml"), + {value: choice, title: prettyName, + min: low, max: high}); + dialog.accepted.connect(function() { + choice = dialog.value; + }) + } + } + + } + + } + + +} diff --git a/qml/pages/PrinterPage.qml b/qml/pages/PrinterPage.qml new file mode 100644 index 0000000..7b4e0a0 --- /dev/null +++ b/qml/pages/PrinterPage.qml @@ -0,0 +1,130 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import "utils.js" as Utils + +Page { + id: page + property var printer + property var jobParams: new Object(); + property string selectedFile + + Component.onCompleted: { + console.log(JSON.stringify(printer.attrs)) + } + + Connections { + target: printer + onJobAttrsChanged: { + notifier.notify(printer.jobAttrs["job-state-message"].value) + } + } + + // To enable PullDownMenu, place our content in a SilicaFlickable + SilicaFlickable { + anchors.fill: parent + + // PullDownMenu and PushUpMenu must be declared in SilicaFlickable, SilicaListView or SilicaGridView + PullDownMenu { + MenuItem { + text: qsTr("Print") + onClicked: { + console.log(JSON.stringify(jobParams)) + printer.print(jobParams, page.selectedFile) + + } + } + } + + ListModel { + id:mod + ListElement {name: "sides"; prettyName: "Sides"; tag: 0x23} + ListElement {name: "copies"; prettyName: "Copies"; tag: 0x21} +// ListElement {name: "page-ranges"; prettyName: "Page range"; tag: 0x33} + ListElement {name: "print-color-mode"; prettyName: "Color mode"; tag: 0x23} +// ListElement {name: "orientation-requested"; prettyName: "Orientation"; tag: 0x23} + ListElement {name: "print-quality"; prettyName: "Quality"; tag: 0x23} + // Bleh, can't create the json object with another object as value, for whatever reason + ListElement {name: "printer-resolution"; prettyName: "Resolution"; tag: 0x32} + } + + SilicaListView { + id: listView + model: mod + clip: true + + anchors.fill: parent + width: parent.width + header: PageHeader { + id: pageHeader + title: printer.attrs["printer-name"].value + description: selectedFile + + } + + + + delegate: ListItem { + id: delegate + property alias loaderItem: loader.item + + openMenuOnPressAndHold: false + + Loader { + id: loader + anchors.fill: parent + property var menu + } + + Component.onCompleted: { + console.log("handling", tag, name, prettyName, JSON.stringify(printer.attrs[name+"-supported"]), JSON.stringify(printer.attrs[name+"-default"])) + switch(tag) { + case 0x21: + loader.setSource("IntegerSetting.qml", + {name: name, + prettyName: prettyName, + tag: tag, + low: printer.attrs[name+"-supported"].value.low, + high: printer.attrs[name+"-supported"].value.high, + default_choice: printer.attrs[name+"-default"].value + }) + break + case 0x33: + loader.setSource("RangeSetting.qml", + {name: name, + prettyName: prettyName, + valid: false, //TODO + tag: 0x33 // integer-range + }) + break + case 0x32: + case 0x23: + loader.setSource("ChoiceSetting.qml", + {name: name, + prettyName: prettyName, + tag: tag, + choices: printer.attrs[name+"-supported"].value, + default_choice: printer.attrs[name+"-default"].value + }) + break + } + } + + onLoaderItemChanged: { + menu = loaderItem.menu + loaderItem.clicked.connect(function() { + openMenu() + }) + loaderItem.choiceMade.connect(function(tag, choice) { + console.log("choice changed", tag, JSON.stringify(choice)) + jobParams[name] = {tag: tag, value: choice}; + console.log(JSON.stringify(jobParams)); + }) + } + + } + VerticalScrollDecorator {} + } + + } + +} diff --git a/qml/pages/RangeSetting.qml b/qml/pages/RangeSetting.qml new file mode 100644 index 0000000..02f232c --- /dev/null +++ b/qml/pages/RangeSetting.qml @@ -0,0 +1,30 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +Setting { + + //TODO + + property int low + property int high + property int choice_low + property int choice_high + + ValueButton { + enabled: valid + anchors.verticalCenter: parent.verticalCenter + label: prettyName + value: choice ? choice : default_choice + onClicked: parent.clicked() + } + + property var menu: ContextMenu { + id: menu + MenuItem { + + } + + } + + +} diff --git a/qml/pages/Setting.qml b/qml/pages/Setting.qml new file mode 100644 index 0000000..1c272b8 --- /dev/null +++ b/qml/pages/Setting.qml @@ -0,0 +1,21 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +Item { + anchors.verticalCenter: parent.verticalCenter + + property string name + property string prettyName + property int tag + property bool valid: true + + property var choice + property var default_choice + + signal clicked() + signal choiceMade(int tag, var choice) + onChoiceChanged: choiceMade(tag, choice) + + property var menu + +} diff --git a/qml/pages/printer.svg b/qml/pages/printer.svg new file mode 100644 index 0000000..3d9cb25 --- /dev/null +++ b/qml/pages/printer.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qml/pages/utils.js b/qml/pages/utils.js new file mode 100644 index 0000000..e7738c5 --- /dev/null +++ b/qml/pages/utils.js @@ -0,0 +1,45 @@ +function supported_formats(printer) +{ + return _formats(printer).supported; +} + +function format_filters(printer) +{ + return _formats(printer).filters; +} + +function _formats(printer) +{ + var formats = printer.attrs["document-format-supported"].value; + var supported = []; + var filters = []; + if(has(formats, "application/pdf")) + { + supported.push("PDF"); + filters.push("*.pdf"); + } + if(has(formats, "image/jpeg")) + { + supported.push("JPEG"); + filters.push("*.jpg"); + filters.push("*.jpeg"); + } + + return {supported: supported.join(" "), filters: filters}; +} + +function has(arrayish, what) +{ + for(var i in arrayish) + { + if(arrayish[i] == what) + return true + } + return false +} + +function can_print(printer, fileName) +{ + // Move to ippprinter? + return format_filters(printer).indexOf("*."+fileName.split('.').pop()) != -1; +} diff --git a/rpm/harbour-seaprint.spec b/rpm/harbour-seaprint.spec new file mode 100644 index 0000000..36871e8 --- /dev/null +++ b/rpm/harbour-seaprint.spec @@ -0,0 +1,67 @@ +# +# Do NOT Edit the Auto-generated Part! +# Generated by: spectacle version 0.27 +# + +Name: harbour-seaprint + +# >> macros +# << macros + +Summary: SeaPrint +Version: 0.1 +Release: 1 +Group: Qt/Qt +License: LICENSE +URL: http://example.org/ +Source0: %{name}-%{version}.tar.bz2 +Source100: harbour-seaprint.yaml +Requires: sailfishsilica-qt5 >= 0.10.9 +BuildRequires: pkgconfig(sailfishapp) >= 1.0.2 +BuildRequires: pkgconfig(Qt5Core) +BuildRequires: pkgconfig(Qt5Qml) +BuildRequires: pkgconfig(Qt5Quick) +BuildRequires: desktop-file-utils + +%description +Short description of my Sailfish OS Application + + +%prep +%setup -q -n %{name}-%{version} + +# >> setup +# << setup + +%build +# >> build pre +# << build pre + +%qmake5 + +make %{?_smp_mflags} + +# >> build post +# << build post + +%install +rm -rf %{buildroot} +# >> install pre +# << install pre +%qmake5_install + +# >> install post +# << install post + +desktop-file-install --delete-original \ + --dir %{buildroot}%{_datadir}/applications \ + %{buildroot}%{_datadir}/applications/*.desktop + +%files +%defattr(-,root,root,-) +%{_bindir} +%{_datadir}/%{name} +%{_datadir}/applications/%{name}.desktop +%{_datadir}/icons/hicolor/*/apps/%{name}.png +# >> files +# << files diff --git a/src/bytestream.cpp b/src/bytestream.cpp new file mode 100644 index 0000000..4cf97cd --- /dev/null +++ b/src/bytestream.cpp @@ -0,0 +1,553 @@ +#include "bytestream.h" +#include +#include +#include +using namespace std; + +Bytestream::Bytestream() +{ + _size = 0; + _pos = 0; + _noOfNextBytes = 0; + _noOfNextBytesValid = false; + _endianness = BigEndian; +} + +Bytestream::Bytestream(size_t len) +{ + _size = len; + _data = new uint8_t[_size]; + memset(_data, 0, _size); + _pos = 0; + _noOfNextBytes = 0; + _noOfNextBytesValid = false; + _endianness = BigEndian; +} + +Bytestream::Bytestream(const void* data, size_t len) +{ + _size = len; + _data = new uint8_t[_size]; + memcpy(_data, data, _size); + _pos = 0; + _noOfNextBytes = 0; + _noOfNextBytesValid = false; + _endianness = BigEndian; +} +Bytestream::Bytestream(const void* data, size_t len, Endianness e) +{ + _size = len; + _data = new uint8_t[_size]; + memcpy(_data, data, _size); + _pos = 0; + _noOfNextBytes = 0; + _noOfNextBytesValid = false; + _endianness = e; +} + +Bytestream::Bytestream(const Bytestream& rhs) +{ + _size = rhs._size; + _data = new uint8_t[_size]; + memcpy(_data, rhs._data, _size); + _pos = rhs._pos; + _noOfNextBytes = 0; + _noOfNextBytesValid = false; + _endianness = rhs._endianness; +} + +Bytestream::~Bytestream() +{ + if(_size != 0) + { + delete _data; + } +} + +bool Bytestream::operator==(const Bytestream& other) const +{ + if(_size != other.size()) + { + return false; + } + return memcmp(_data, other.raw(), _size) == 0; +} +bool Bytestream::operator!=(const Bytestream& other) const +{ + if(_size != other.size()) + { + return true; + } + return memcmp(_data, other.raw(), _size) != 0; +} + +Bytestream& Bytestream::operator=(const Bytestream& other) +{ + if(_size != 0) + { + delete _data; + } + _pos = other.pos(); + _size = other.size(); + _data = new uint8_t[_size]; + memcpy(_data, other.raw(), _size); + return *this; +} + +template +T bswap(T u) +{ + uint8_t* const p = reinterpret_cast(&u); + for (size_t i = 0; i < sizeof(T) / 2; i++) + { + std::swap(p[i], p[sizeof(T) - i - 1]); + } + return u; +} + +#define GET(type, shorthand, len) GET_(type##len##_t, shorthand##len, len) +#define GET_(type, shortType, len) \ + type Bytestream::get##shortType() \ + {type tmp; getBytes(&tmp, sizeof(type));\ + if(needsSwap()){tmp=bswap(tmp);} return tmp;} + +GET(uint, U, 8) +GET(uint, U, 16) +GET(uint, U, 32) +GET(uint, U, 64) +GET(int, S, 8) +GET(int, S, 16) +GET(int, S, 32) +GET(int, S, 64) +GET(float, F, 32) +GET(float, F, 64) + +std::string Bytestream::getString() +{ + if(!_noOfNextBytesValid) + { + throw invalid_argument("No length given"); + } + char* cs = new char[_noOfNextBytes+1]; + cs[_noOfNextBytes] = 0; + getBytes(cs, _noOfNextBytes); + string s = std::string(cs, _noOfNextBytes); + delete cs; + return s; +} +Bytestream Bytestream::getBytestream() +{ + if(!_noOfNextBytesValid) + { + throw invalid_argument("No length given"); + } + uint8_t* cs = new uint8_t[_noOfNextBytes]; + getBytes(cs, _noOfNextBytes); + Bytestream other = Bytestream(cs, _noOfNextBytes); + delete cs; + return other; +} +std::string Bytestream::getString(size_t len) +{ + if(_noOfNextBytesValid && len != _noOfNextBytes) + { + throw logic_error("Desired lengths does not match"); + } + else if(!_noOfNextBytesValid) + { + setNoOfNextBytes(len); + } + return getString(); +} +Bytestream Bytestream::getBytestream(size_t len) +{ + if(!_noOfNextBytesValid && len != _noOfNextBytes) + { + throw logic_error("Desired lengths does not match"); + } + setNoOfNextBytes(len); + return getBytestream(); +} + +void Bytestream::getBytes(void* cs, size_t len) + { + _before(len); + memcpy(cs, &(_data[_pos]), len); + _after(len); +} + + +#define PEEK(type, shorthand, len) PEEK_(type##len##_t, shorthand##len, len) +#define PEEK_(type, shortType, len) \ + type Bytestream::peek##shortType() \ + {type tmp; getBytes(&tmp, sizeof(type));\ + if(needsSwap()){tmp=bswap(tmp);} (*this) -= sizeof(type); return tmp;} + +PEEK(uint, U, 8) +PEEK(uint, U, 16) +PEEK(uint, U, 32) +PEEK(uint, U, 64) +PEEK(int, S, 8) +PEEK(int, S, 16) +PEEK(int, S, 32) +PEEK(int, S, 64) +PEEK(float, F, 32) +PEEK(float, F, 64) + +std::string Bytestream::peekString() +{ + if(!_noOfNextBytesValid) + { + throw invalid_argument("No length given"); + } + char* cs = new char[_noOfNextBytes+1]; + cs[_noOfNextBytes] = 0; + getBytes(cs, _noOfNextBytes); + string s = std::string(cs, _noOfNextBytes); + delete cs; + (*this) -= _noOfNextBytes; + return s; +} +Bytestream Bytestream::peekBytestream() +{ + if(!_noOfNextBytesValid) + { + throw invalid_argument("No length given"); + } + uint8_t* cs = new uint8_t[_noOfNextBytes]; + getBytes(cs, _noOfNextBytes); + Bytestream other = Bytestream(cs, _noOfNextBytes); + delete cs; + (*this) -= _noOfNextBytes; + return other; +} +std::string Bytestream::peekString(size_t len) +{ + if(_noOfNextBytesValid && len != _noOfNextBytes) + { + throw logic_error("Desired lengths does not match"); + } + else if(!_noOfNextBytesValid) + { + setNoOfNextBytes(len); + } + return peekString(); +} +Bytestream Bytestream::peekBytestream(size_t len) +{ + if(!_noOfNextBytesValid && len != _noOfNextBytes) + { + throw logic_error("Desired lengths does not match"); + } + setNoOfNextBytes(len); + return peekBytestream(); +} + +#define NEXT(type, shorthand, len) NEXT_(type##len##_t, shorthand##len) +#define NEXT_(type, shortType) \ + bool Bytestream::next##shortType(type u) \ + {if(u == get##shortType())\ + {return true;} \ + else\ + {(*this) -= sizeof(type);\ + return false;}} + +NEXT(uint, U, 8) +NEXT(uint, U, 16) +NEXT(uint, U, 32) +NEXT(uint, U, 64) +NEXT(int, S, 8) +NEXT(int, S, 16) +NEXT(int, S, 32) +NEXT(int, S, 64) +NEXT(float, F, 32) +NEXT(float, F, 64) + +bool Bytestream::nextString(const std::string& s) +{ + if(_noOfNextBytesValid && getNoOfNextBytes() != s.length()) + { + throw logic_error("Desired length does not match const length"); + } + else if(!_noOfNextBytesValid) + { + setNoOfNextBytes(s.length()); + } + + size_t noOfNextBytes = getNoOfNextBytes(); + + if(noOfNextBytes > remaining()) + { + invalidateNoOfNextBytes(); + return false; + } + + if(getString() == s) + { + return true; + } + else + { + (*this) -= noOfNextBytes; + return false; + } +} +bool Bytestream::nextBytestream(const Bytestream& other) +{ + if(_noOfNextBytesValid && getNoOfNextBytes() != other.size()) + { + throw logic_error("Desired length does not match const length"); + } + else if(!_noOfNextBytesValid) + { + setNoOfNextBytes(other.size()); + } + + size_t noOfNextBytes = getNoOfNextBytes(); + + if(noOfNextBytes > remaining()) + { + invalidateNoOfNextBytes(); + return false; + } + + if(getBytestream() == other) + { + return true; + } + else + { + (*this) -= noOfNextBytes; + return false; + } +} + +#define PUT(type, shorthand, len) PUT_(type##len##_t, shorthand##len, len) +#define PUT_(type, shortType, len) \ + void Bytestream::put##shortType(type u) \ + {if(needsSwap()){u=bswap(u);} \ + putBytes(&u, sizeof(u));} + +PUT(uint, U, 8) +PUT(uint, U, 16) +PUT(uint, U, 32) +PUT(uint, U, 64) +PUT(int, S, 8) +PUT(int, S, 16) +PUT(int, S, 32) +PUT(int, S, 64) +PUT(float, F, 32) +PUT(float, F, 64) + +void Bytestream::putString(const std::string& s) +{ + putBytes(s.c_str(), s.length()); +} +void Bytestream::putBytestream(const Bytestream& other) +{ + putBytes(other.raw(), other.size()); +} + +void Bytestream::putBytes(const void* c, size_t len) +{ + uint8_t* old = _data; + _data = new uint8_t[_size+len]; + + if (_size != 0) + { + memcpy(_data, old, _size); + delete old; + } + memcpy((_data+_size), c, len); + _size += len; +} + +void Bytestream::setNoOfNextBytes(size_t n) +{ + _noOfNextBytes = n; + _noOfNextBytesValid = true; +} + +void Bytestream::invalidateNoOfNextBytes() +{ + _noOfNextBytes = 0; + _noOfNextBytesValid = false; +} + + +void Bytestream::_before(size_t bytesToRead) +{ + if(bytesToRead > remaining()) + { + invalidateNoOfNextBytes(); + throw out_of_range("Tried to read past end"); + } +} + +void Bytestream::_after(size_t bytesRead) +{ + _pos += bytesRead; + _noOfNextBytesValid = false; +} + +Bytestream Bytestream::operator[](size_t i) +{ + Bytestream tmp(_data+i, _size-i); + return tmp; +} + +Bytestream& Bytestream::operator+=(size_t i) +{ + if((_pos+i) > _size) + { + invalidateNoOfNextBytes(); + throw out_of_range("Tried to address data past end"); + } + _pos += i; + return *this; +} + +Bytestream& Bytestream::operator-=(size_t i) +{ + _pos -= i; + return *this; +} + +Bytestream& Bytestream::operator/(int i) +{ + setNoOfNextBytes(i); + return *this; +} + +#define PUTOP(type, shorthand, len) PUTOP_(type##len##_t, shorthand##len) +#define PUTOP_(type, shortType) \ + Bytestream& Bytestream::operator<<(const type& u) \ + {put##shortType(u); return *this;} + +PUTOP(uint, U, 8) +PUTOP(uint, U, 16) +PUTOP(uint, U, 32) +PUTOP(uint, U, 64) +PUTOP(int, S, 8) +PUTOP(int, S, 16) +PUTOP(int, S, 32) +PUTOP(int, S, 64) +PUTOP(float, F, 32) +PUTOP(float, F, 64) + +Bytestream& Bytestream::operator<<(const std::string& s) +{ + putString(s); + return *this; +} +Bytestream& Bytestream::operator<<(const Bytestream& other) +{ + putBytestream(other); + return *this; +} + +#define GETOP(type, shorthand, len) GETOP_(type##len##_t, shorthand##len) +#define GETOP_(type, shortType) \ + Bytestream& Bytestream::operator>>(type& u) \ + {u = get##shortType(); return *this;} + +GETOP(uint, U, 8) +GETOP(uint, U, 16) +GETOP(uint, U, 32) +GETOP(uint, U, 64) +GETOP(int, S, 8) +GETOP(int, S, 16) +GETOP(int, S, 32) +GETOP(int, S, 64) +GETOP(float, F, 32) +GETOP(float, F, 64) + +Bytestream& Bytestream::operator>>(std::string& s) +{ + s = getString(); + return *this; +} +Bytestream& Bytestream::operator>>(Bytestream& other) +{ + other = getBytestream(); + return *this; +} + +#define GETOP_CONST(type, shorthand, len) \ + GETOP_CONST_(type##len##_t, shorthand##len) +#define GETOP_CONST_(type, shortType) \ + Bytestream& Bytestream::operator>>(const type& u) \ + {type v = get##shortType();\ + if(u!=v) {(*this) -= sizeof(type);\ + throw Badmatch("Does not match const", v, u);}\ + else{return *this;}} + +GETOP_CONST(uint, U, 8) +GETOP_CONST(uint, U, 16) +GETOP_CONST(uint, U, 32) +GETOP_CONST(uint, U, 64) +GETOP_CONST(int, S, 8) +GETOP_CONST(int, S, 16) +GETOP_CONST(int, S, 32) +GETOP_CONST(int, S, 64) +GETOP_CONST(float, F, 32) +GETOP_CONST(float, F, 64) + +Bytestream& Bytestream::operator>>(const std::string& s) +{ + if (_noOfNextBytesValid && getNoOfNextBytes() != s.length()) + { + throw logic_error("Desired length does not match const length"); + } + else if(!_noOfNextBytesValid) + { + setNoOfNextBytes(s.length()); + } + std::string sv = getString(); + if(sv != s) + { + (*this) -= s.length(); + throw Badmatch("Does not match const", sv, s); + } + return *this; +} + +#define NEXTOP(type, shorthand, len) NEXTOP_(type##len##_t, shorthand##len) +#define NEXTOP_(type, shortType) \ + bool Bytestream::operator>>=(const type& u) \ + {return next##shortType(u);} + +NEXTOP(uint, U, 8) +NEXTOP(uint, U, 16) +NEXTOP(uint, U, 32) +NEXTOP(uint, U, 64) +NEXTOP(int, S, 8) +NEXTOP(int, S, 16) +NEXTOP(int, S, 32) +NEXTOP(int, S, 64) +NEXTOP(float, F, 32) +NEXTOP(float, F, 64) +bool Bytestream::operator>>=(const std::string& s) +{ + return nextString(s); +} +bool Bytestream::operator>>=(const Bytestream& other) +{ + return nextBytestream(other); +} + +#if __BYTE_ORDER == __LITTLE_ENDIAN +bool Bytestream::needsSwap() +{ + return _endianness != Endianness::NativeEndian + && _endianness != Endianness::LittleEndian; +} +#elif __BYTE_ORDER == __BIG_ENDIAN +bool Bytestream::needsSwap() +{ + return _endianness != Endianness::NativeEndian + && _endianness != Endianness::BigEndian; +} +#else +#error +#endif diff --git a/src/bytestream.h b/src/bytestream.h new file mode 100644 index 0000000..709aea8 --- /dev/null +++ b/src/bytestream.h @@ -0,0 +1,189 @@ +#ifndef BYTESTREAM_H +#define BYTESTREAM_H +#include +#include +#include +#ifndef __STDC_IEC_559__ +#error "Double must be IEEE 754" +#endif +#define float32_t float +#define float64_t double +class Bytestream +{ +public: + + class Badmatch : public std::invalid_argument::invalid_argument + { + public: + Badmatch(std::string s, std::string v, std::string u) : + invalid_argument(s+": "+v+" != "+u) {} + template + Badmatch(std::string s, T v, T u) : + invalid_argument(s+": "+std::to_string(v)+" != "+std::to_string(u)) {} + + }; + + enum Endianness { + NativeEndian, + BigEndian, + LittleEndian + }; + + Bytestream(); + Bytestream(size_t len); + Bytestream(const void* data, size_t len); + Bytestream(const void* data, size_t len, Endianness e); + Bytestream(const Bytestream& rhs); + ~Bytestream(); + + bool operator==(const Bytestream& other) const; + bool operator!=(const Bytestream& other) const; + + Bytestream& operator=(const Bytestream& other); + + uint8_t* raw() const {return _data;} + size_t size() const {return _size;} + size_t pos() const {return _pos;} + size_t remaining() const {return _size - _pos;} + bool atEnd() const {return _pos >= _size;} + void setPos(size_t pos) {_pos = pos;} + Endianness getEndianness() {return _endianness;} + void setEndianness(Endianness e) {_endianness = e;} + + uint8_t getU8(); + uint16_t getU16(); + uint32_t getU32(); + uint64_t getU64(); + int8_t getS8(); + int16_t getS16(); + int32_t getS32(); + int64_t getS64(); + float32_t getF32(); + float64_t getF64(); + std::string getString(); + Bytestream getBytestream(); + std::string getString(size_t len); + Bytestream getBytestream(size_t len); + void getBytes(void* cs, size_t len); + + uint8_t peekU8(); + uint16_t peekU16(); + uint32_t peekU32(); + uint64_t peekU64(); + int8_t peekS8(); + int16_t peekS16(); + int32_t peekS32(); + int64_t peekS64(); + float32_t peekF32(); + float64_t peekF64(); + std::string peekString(); + Bytestream peekBytestream(); + std::string peekString(size_t len); + Bytestream peekBytestream(size_t len); + + bool nextU8(uint8_t); + bool nextU16(uint16_t); + bool nextU32(uint32_t); + bool nextU64(uint64_t); + bool nextS8(int8_t); + bool nextS16(int16_t); + bool nextS32(int32_t); + bool nextS64(int64_t); + bool nextF32(float32_t); + bool nextF64(float64_t); + bool nextString(const std::string& bts); + bool nextBytestream(const Bytestream& bts); + + void putU8(uint8_t); + void putU16(uint16_t); + void putU32(uint32_t); + void putU64(uint64_t); + void putS8(int8_t); + void putS16(int16_t); + void putS32(int32_t); + void putS64(int64_t); + void putF32(float32_t); + void putF64(float64_t); + void putString(const std::string&); + void putBytestream(const Bytestream&); + void putBytes(const void* c, size_t len); + + void setNoOfNextBytes(size_t n); + void invalidateNoOfNextBytes(); + size_t getNoOfNextBytes() {return _noOfNextBytes;} + bool noOfNextBytesValid() const {return _noOfNextBytesValid;} + + Bytestream operator[](size_t i); + Bytestream& operator+=(size_t i); + Bytestream& operator-=(size_t i); + + Bytestream& operator/(int i); + + Bytestream& operator<<(const uint8_t& u); + Bytestream& operator<<(const uint16_t& u); + Bytestream& operator<<(const uint32_t& u); + Bytestream& operator<<(const uint64_t& u); + Bytestream& operator<<(const int8_t& u); + Bytestream& operator<<(const int16_t& u); + Bytestream& operator<<(const int32_t& u); + Bytestream& operator<<(const int64_t& u); + Bytestream& operator<<(const float32_t& u); + Bytestream& operator<<(const float64_t& u); + Bytestream& operator<<(const std::string& s); + Bytestream& operator<<(const Bytestream& other); + + Bytestream& operator>>(uint8_t& u); + Bytestream& operator>>(uint16_t& u); + Bytestream& operator>>(uint32_t& u); + Bytestream& operator>>(uint64_t& u); + Bytestream& operator>>(int8_t& u); + Bytestream& operator>>(int16_t& u); + Bytestream& operator>>(int32_t& u); + Bytestream& operator>>(int64_t& u); + Bytestream& operator>>(float32_t& u); + Bytestream& operator>>(float64_t& u); + Bytestream& operator>>(std::string& s); + Bytestream& operator>>(Bytestream& other); + + Bytestream& operator>>(const uint8_t& u); + Bytestream& operator>>(const uint16_t& u); + Bytestream& operator>>(const uint32_t& u); + Bytestream& operator>>(const uint64_t& u); + Bytestream& operator>>(const int8_t& u); + Bytestream& operator>>(const int16_t& u); + Bytestream& operator>>(const int32_t& u); + Bytestream& operator>>(const int64_t& u); + Bytestream& operator>>(const float32_t& u); + Bytestream& operator>>(const float64_t& u); + Bytestream& operator>>(const std::string& s); + + bool operator>>=(const uint8_t& u); + bool operator>>=(const uint16_t& u); + bool operator>>=(const uint32_t& u); + bool operator>>=(const uint64_t& u); + bool operator>>=(const int8_t& u); + bool operator>>=(const int16_t& u); + bool operator>>=(const int32_t& u); + bool operator>>=(const int64_t& u); + bool operator>>=(const float32_t& u); + bool operator>>=(const float64_t& u); + bool operator>>=(const std::string& s); + bool operator>>=(const Bytestream& other); + + +private: + uint8_t* _data; + size_t _size; + size_t _pos; + size_t _noOfNextBytes; + bool _noOfNextBytesValid; + + Endianness _endianness; + + bool needsSwap(); + + void _after(size_t bytesRead); + void _before(size_t bytesToRead); +}; + +#endif diff --git a/src/ippdiscovery.cpp b/src/ippdiscovery.cpp new file mode 100644 index 0000000..bd0839e --- /dev/null +++ b/src/ippdiscovery.cpp @@ -0,0 +1,184 @@ +#include "ippdiscovery.h" +#define A 1 +#define PTR 12 +#define TXT 16 +#define AAAA 28 +#define SRV 33 + +void put_addr(Bytestream& bts, QStringList addr) +{ + for(int i = 0; i < addr.length(); i++) + { + QString elem = addr[i]; + bts << (quint8)elem.size() << elem.toStdString(); + } + bts << (quint8)0; +} + +QStringList get_addr(Bytestream& bts) +{ + QStringList addr; + while(true) + { + if(bts.nextU8(0)) + { + break; + } + else if ((bts.peekU8()&0xc0)==0xc0) + { + quint16 ref = bts.getU16() & 0x0fff; + Bytestream tmp = bts; + tmp.setPos(ref); + addr += get_addr(tmp); + break; + } + else + { + std::string elem; + bts/bts.getU8() >> elem; + addr.append(QString(elem.c_str())); + } + } + return addr; +} + +IppDiscovery::IppDiscovery() : QStringListModel() +{ + socket = new QUdpSocket(this); + connect(socket, SIGNAL(readyRead()), + this, SLOT(readPendingDatagrams())); + connect(this, SIGNAL(favouritesChanged()), + this, SLOT(update())); +} + +IppDiscovery::~IppDiscovery() { + delete socket; +} + + +void IppDiscovery::discover() { + + Bytestream query; + quint16 transactionid = 0; + quint16 flags = 0; + quint16 questions = 1; + + query << transactionid << flags << questions << (quint16)0 << (quint16)0 << (quint16)0; + put_addr(query, {"_ipp","_tcp","local"}); + query << (quint16)0x000C << (quint16)0x0001; + + QByteArray bytes((char*)(query.raw()), query.size()); + socket->writeDatagram(bytes, QHostAddress("224.0.0.251"), 5353); + +} + +void IppDiscovery::update() +{ + qDebug() << _favourites << _found; + this->setStringList(_favourites+_found); +} + +void IppDiscovery::readPendingDatagrams() +{ + while (socket->hasPendingDatagrams()) { + + size_t size = socket->pendingDatagramSize(); + Bytestream resp(size); + QHostAddress sender; + quint16 senderPort; + + QMap ptrs; + QMap rps; + QMap ports; + QMap targets; + QMultiMap AAs; + QMultiMap AAAAs; + + socket->readDatagram((char*)(resp.raw()), size, &sender, &senderPort); + sender = QHostAddress(sender.toIPv4Address()); + + quint16 transactionid, flags, questions, answerRRs, authRRs, addRRs; + resp >> transactionid >> flags >> questions >> answerRRs >> authRRs >> addRRs; + + for(quint16 i = 0; i < questions; i++) + { + quint16 qtype, qflags; + QString qaddr = get_addr(resp).join('.'); + resp >> qtype >> qflags; + } + + for(quint16 i = 0; i < answerRRs; i++) + { + quint16 atype, aflags, len; + quint32 ttl; + + QString aaddr = get_addr(resp).join('.'); + resp >> atype >> aflags >> ttl >> len; + + quint16 pos_before = resp.pos(); + if (atype == PTR) + { + QString tmpname = get_addr(resp).join("."); + ptrs[aaddr] = tmpname; + } + else if(atype == TXT) + { + Bytestream tmp; + while(resp.pos() < pos_before+len) + { + resp/resp.getU8() >> tmp; + if(tmp >>= "rp=") + { + std::string tmprp; + tmp/tmp.remaining() >> tmprp; + rps[aaddr] = tmprp.c_str(); + } + } + } + else if (atype == SRV) + { + quint16 prio, w, port; + resp >> prio >> w >> port; + QString target = get_addr(resp).join("."); + ports[aaddr] = port; + targets[aaddr] = target; + } + else if(atype == A) + { + quint32 addr; + resp >> addr; + QHostAddress haddr(addr); + AAs.insert(aaddr, haddr.toString()); + } + else + { + resp += len; + } + Q_ASSERT(resp.pos() == pos_before+len); + + } + + for(QMap::Iterator it = ptrs.begin(); it != ptrs.end(); it++) + { + quint16 port = ports[it.value()]; + QString target = targets[it.value()]; + QString rp = rps[it.value()]; + + for(QMultiMap::Iterator ait = AAs.begin(); ait != AAs.end(); ait++) + { + if(ait.key() == target) + { + QString ip = ait.value(); + QString addr = ip+":"+QString::number(port)+"/"+rp; + if(!_found.contains(addr)) + { + _found.append(addr); + _found.sort(Qt::CaseInsensitive); + } + } + } + } + } + this->update(); + +} diff --git a/src/ippdiscovery.h b/src/ippdiscovery.h new file mode 100644 index 0000000..f988eaa --- /dev/null +++ b/src/ippdiscovery.h @@ -0,0 +1,29 @@ +#ifndef IPPDISCOVERY_H +#define IPPDISCOVERY_H +#include +#include +#include "bytestream.h" + +class IppDiscovery : public QStringListModel +{ + Q_OBJECT +public: + IppDiscovery(); + ~IppDiscovery(); + Q_PROPERTY(QStringList favourites MEMBER _favourites NOTIFY favouritesChanged) + Q_INVOKABLE void discover(); + +signals: + void favouritesChanged(); + +public slots: + void readPendingDatagrams(); + void update(); +protected: +private: + QStringList _favourites; + QStringList _found; + QUdpSocket* socket; +}; + +#endif // IPPDISCOVERY_H diff --git a/src/ippmsg.cpp b/src/ippmsg.cpp new file mode 100644 index 0000000..e2181c6 --- /dev/null +++ b/src/ippmsg.cpp @@ -0,0 +1,278 @@ +#include "ippmsg.h" + +#define MAJ_VSN 1 +#define MIN_VSN 1 + +quint32 IppMsg::_reqid=1; + +IppMsg::IppMsg() +{ +} + +IppMsg::IppMsg(QJsonObject opAttrs, QJsonObject jobAttrs) +{ + _opAttrs = opAttrs; + _jobAttrs = jobAttrs; +} + + +IppMsg::~IppMsg() +{ +} + +IppMsg::IppMsg(QNetworkReply* resp) +{ + QByteArray tmp = resp->readAll(); + Bytestream bts(tmp.constData(), tmp.length()); + + quint8 majVsn; + quint8 minVsn; + quint16 status; + quint32 reqId; + + bts >> majVsn >> minVsn >> status >> reqId; + + QJsonObject* attrs = 0; + + QString last_name; + + while(!bts.atEnd()) + { + if(bts>>=(quint8)IppTag::OpAttrs) + { + attrs = &_opAttrs; + } + else if (bts>>=(quint8)IppTag::JobAttrs) { + attrs = &_jobAttrs; + } + else if (bts>>=(quint8)IppTag::EndAttrs) { + break; + } + else if (bts>>=(quint8)IppTag::PrinterAttrs) { + attrs = &_printerAttrs; + } + else { + last_name = consume_attribute(attrs, bts, last_name); + } + } +} + +QString IppMsg::consume_attribute(QJsonObject* attrs, Bytestream& data, QString lastName) +{ + quint8 tag; + quint16 tmp_len; + QString name; + QJsonValue value; + std::string tmp_str = ""; + bool noList = false; + + data >> tag >> tmp_len; + + data/tmp_len >> tmp_str; + name = tmp_str!="" ? tmp_str.c_str() : lastName; + + + switch (tag) { + case OpAttrs: + case JobAttrs: + case EndAttrs: + case PrinterAttrs: + Q_ASSERT(false); + case Integer: + case Enum: + quint32 tmp_u32; + data >> tmp_len >> tmp_u32; + value = (int)tmp_u32; + break; + case Boolean: + quint8 tmp_bool; + data >> tmp_len >> tmp_bool; + value = (bool)tmp_bool; + noList = true; + break; + case DateTime: + { + quint16 year; + quint8 month, day, hour, minutes, seconds, deci_seconds, + plus_minus, utc_h_offset, utc_m_offset; + data >> tmp_len >> year >> month >> day >> hour >> minutes >> seconds >> deci_seconds + >> plus_minus >> utc_h_offset >> utc_m_offset; + QDate date(year, month, day); + QTime time(hour, minutes, seconds, deci_seconds*100); + int offset_seconds = (plus_minus == '+' ? 1 : -1)*(utc_h_offset*60*60+utc_m_offset*60); + value = QDateTime(date, time, Qt::OffsetFromUTC, offset_seconds).toString(Qt::ISODate); + break; + } + case Resolution: + { + qint32 x, y; + qint8 units; + QJsonObject tmp_res; + data >> tmp_len >> x >> y >> units; + tmp_res.insert("x", x); + tmp_res.insert("y", y); + tmp_res.insert("units", units); + value = tmp_res; + break; + } + case IntegerRange: + { + qint32 low, high; + data >> tmp_len >> low >> high; + QJsonObject tmp_range; + tmp_range.insert("low", low); + tmp_range.insert("high", high); + value = tmp_range; + noList = true; + break; + } + case OctetStringUnknown: + case TextWithLanguage: + case NameWithLanguage: + case TextWithoutLanguage: + case NameWithoutLanguage: + case Keyword: + case Uri: + case UriScheme: + case Charset: + case NaturalLanguage: + case MimeMediaType: + default: + data >> tmp_len; + data/tmp_len >> tmp_str; + value = tmp_str.c_str(); + break; + }; + + + if(attrs->contains(name)) + { + QJsonObject tmp = (*attrs)[name].toObject(); + QJsonArray tmpa; + if(tmp["value"].isArray()) + { + tmpa = tmp["value"].toArray(); + } + else + { + tmpa = QJsonArray {tmp["value"]}; + } + tmpa.append(value); + tmp["value"] = tmpa; + attrs->insert(name, tmp); + } + else + { + if((name.endsWith("-supported") || name == "printer-icons") && !noList) + { + value = QJsonArray {value}; + } + attrs->insert(name, QJsonObject {{"tag", tag}, {"value", value}}); + } + return name; +} + +QByteArray IppMsg::encode(Operation op) +{ + Bytestream ipp; + + ipp << quint8(MAJ_VSN) << quint8(MIN_VSN); + + ipp << quint16(op); + ipp << _reqid++; + + if(!_opAttrs.empty()) + { + ipp << quint8(1); + for(QJsonObject::iterator it = _opAttrs.begin(); it != _opAttrs.end(); it++) + { + QJsonObject val = it.value().toObject(); + ipp << encode_attr(val["tag"].toInt(), it.key(), val["value"]); + } + } + if(!_jobAttrs.empty()) + { + ipp << quint8(2); + for(QJsonObject::iterator it = _jobAttrs.begin(); it != _jobAttrs.end(); it++) + { + QJsonObject val = it.value().toObject(); + ipp << encode_attr(val["tag"].toInt(), it.key(), val["value"]); + } + } + + ipp << quint8(3); + + return QByteArray((char*)(ipp.raw()), ipp.size()); +} + +Bytestream IppMsg::encode_attr(quint8 tag, QString name, QJsonValueRef value) +{ + Bytestream req; + + switch (tag) { + case OpAttrs: + case JobAttrs: + case EndAttrs: + case PrinterAttrs: + Q_ASSERT(false); + case Integer: + case Enum: + { + quint32 tmp_u32 = value.toInt(); + req << (quint16)4 << tmp_u32; + break; + } + case Boolean: + { + quint32 tmp_u8 = value.toBool(); + req << (quint16)1 << tmp_u8; + break; + } + case DateTime: + { + Q_ASSERT("fixme"); + break; + } + case Resolution: + { + qDebug() << value << value.toObject(); + qint32 x = value.toObject()["x"].toInt(); + qint32 y = value.toObject()["y"].toInt(); + qint8 units = value.toObject()["units"].toInt(); + req << (quint16)9 << x << y << units; + break; + } + case IntegerRange: + { + qint32 low = value.toObject()["low"].toInt(); + qint32 high = value.toObject()["high"].toInt(); + req << (quint16)8 << low << high; + break; + } + case OctetStringUnknown: + case TextWithLanguage: + case NameWithLanguage: + case TextWithoutLanguage: + case NameWithoutLanguage: + case Keyword: + case Uri: + case UriScheme: + case Charset: + case NaturalLanguage: + case MimeMediaType: + req << quint16(value.toString().length()) << value.toString().toStdString(); + break; + default: + qDebug() << "uncaught tag" << tag; + Q_ASSERT(false); + break; + } + + Bytestream actual; + if(req.size() != 0) + { + actual << tag << quint16(name.length()) << name.toStdString() << req; + } + + return actual; +} diff --git a/src/ippmsg.h b/src/ippmsg.h new file mode 100644 index 0000000..2ded090 --- /dev/null +++ b/src/ippmsg.h @@ -0,0 +1,84 @@ +#ifndef IPP_PROTO_H +#define IPP_PROTO_H + +#include "bytestream.h" + +#include +#include +#include +#include +#include +#include + +class IppMsg +{ +public: + + enum IppTag : quint8 { + OpAttrs = 0x01, + JobAttrs = 0x02, + EndAttrs = 0x03, + PrinterAttrs = 0x04, + Integer = 0x21, + Boolean = 0x22, + Enum = 0x23, + OctetStringUnknown = 0x30, + DateTime = 0x31, + Resolution = 0x32, + IntegerRange = 0x33, + TextWithLanguage = 0x35, + NameWithLanguage = 0x36, + TextWithoutLanguage = 0x41, + NameWithoutLanguage = 0x42, + Keyword = 0x44, + Uri = 0x45, + UriScheme = 0x46, + Charset = 0x47, + NaturalLanguage = 0x48, + MimeMediaType = 0x49 + }; + + enum Operation : quint16 { + PrintJob = 0x0002, + PrintUri = 0x0003, + ValidateJob = 0x0004, + CreateJob = 0x0005, + SendDocument = 0x0006, + SendUri = 0x0007, + CancelJob = 0x0008, + GetJobAttrs = 0x0009, + GetJobs = 0x000A, + GetPrinterAttrs = 0x000B, + HoldJob = 0x000C, + ReleaseJob = 0x000D, + RestartJob = 0x000E, + PausePrinter = 0x0010, + ResumePrinter = 0x0011, + PurgeJobs = 0x0012 + }; + + IppMsg(); + IppMsg(QNetworkReply* resp); + IppMsg(QJsonObject opAttrs, QJsonObject jobAttrs = QJsonObject()); + IppMsg(const IppMsg& other) = default; + ~IppMsg(); + + QByteArray encode(Operation op); + QJsonObject getPrinterAttrs() {return _printerAttrs;} + QJsonObject getJobAttrs() {return _jobAttrs;} + QJsonObject getOpAttrs() {return _opAttrs;} + + +protected: +private: + QString consume_attribute(QJsonObject* attrs, Bytestream& data, QString lastName); + Bytestream encode_attr(quint8 tag, QString name, QJsonValueRef value); + + QJsonObject _opAttrs; + QJsonObject _jobAttrs; + QJsonObject _printerAttrs; + + static quint32 _reqid; +}; + +#endif // IPP_PROTO_H diff --git a/src/ippprinter.cpp b/src/ippprinter.cpp new file mode 100644 index 0000000..a12b8d0 --- /dev/null +++ b/src/ippprinter.cpp @@ -0,0 +1,120 @@ +#include "ippprinter.h" + +IppPrinter::IppPrinter() +{ + _nam = new QNetworkAccessManager(this); + _jnam = new QNetworkAccessManager(this); + connect(_nam, SIGNAL(finished(QNetworkReply*)),this, SLOT(getPrinterAttributesFinished(QNetworkReply*))); + connect(_jnam, SIGNAL(finished(QNetworkReply*)),this, SLOT(jobRequestFinished(QNetworkReply*))); + QObject::connect(this, &IppPrinter::urlChanged, this, &IppPrinter::onUrlChanged); +} + +IppPrinter::~IppPrinter() { + delete _nam; + delete _jnam; +} + +void IppPrinter::setUrl(QString url) +{ + if(url != _url) + { + _url = url; + emit urlChanged(); + } +} + +void IppPrinter::onUrlChanged() +{ + _attrs = QJsonObject(); + emit attrsChanged(); + + QNetworkRequest request; + QUrl url("http://"+_url); + qDebug() << _url << url.port(); + if(url.port() == -1) { + url.setPort(631); + } + request.setUrl(url); +// request.setRawHeader("User-Agent", "MyOwnBrowser 1.0"); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/ipp"); + + QJsonObject o + { + {"attributes-charset", QJsonObject {{"tag", IppMsg::Charset}, {"value", "utf-8"}}}, + {"attributes-natural-language", QJsonObject {{"tag", IppMsg::NaturalLanguage}, {"value", "en-us"}}}, + {"printer-uri", QJsonObject {{"tag", IppMsg::Uri}, {"value", "ipp://"+_url}}}, + {"requesting-user-name", QJsonObject {{"tag", IppMsg::NameWithoutLanguage}, {"value", "nemo"}}} + }; + IppMsg msg = IppMsg(o); + _nam->post(request, msg.encode(IppMsg::GetPrinterAttrs)); + +} + +void IppPrinter::getPrinterAttributesFinished(QNetworkReply *reply) +{ + if(reply->error() == QNetworkReply::NoError) + { + try { + IppMsg resp(reply); + _attrs = resp.getPrinterAttrs(); + emit attrsChanged(); + } + catch(std::exception e) + { + qDebug() << e.what(); + } + } + +} + +void IppPrinter::jobRequestFinished(QNetworkReply *reply) +{ + if(reply->error() == QNetworkReply::NoError) + { + try { + IppMsg resp(reply); + qDebug() << resp.getOpAttrs() << resp.getJobAttrs(); + _jobAttrs = resp.getJobAttrs(); + emit jobAttrsChanged(); + } + catch(std::exception e) + { + qDebug() << e.what(); + } + } +} + + +bool IppPrinter::print(QJsonObject attrs, QString filename){ + qDebug() << "printing" << filename << attrs; + + QFile file(filename); + bool file_ok = file.open(QIODevice::ReadOnly); + if(!file_ok) + return false; + + QFileInfo fileinfo(file); + QNetworkRequest request; + QUrl url("http://"+_url); + request.setUrl(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/ipp"); + + QJsonObject o + { + {"attributes-charset", QJsonObject {{"tag", IppMsg::Charset}, {"value", "utf-8"}}}, + {"attributes-natural-language", QJsonObject {{"tag", IppMsg::NaturalLanguage}, {"value", "en-us"}}}, + {"printer-uri", QJsonObject {{"tag", IppMsg::Uri}, {"value", "ipp://"+_url}}}, + {"requesting-user-name", QJsonObject {{"tag", IppMsg::NameWithoutLanguage}, {"value", "nemo"}}}, + {"job-name", QJsonObject {{"tag", IppMsg::NameWithoutLanguage}, {"value", fileinfo.fileName()}}}, + }; + + IppMsg job = IppMsg(o, attrs); + + QByteArray contents = job.encode(IppMsg::PrintJob); + QByteArray filedata = file.readAll(); + contents = contents.append(filedata); + + _jnam->post(request, contents); + file.close(); + return true; +} diff --git a/src/ippprinter.h b/src/ippprinter.h new file mode 100644 index 0000000..51581c4 --- /dev/null +++ b/src/ippprinter.h @@ -0,0 +1,46 @@ +#ifndef IPPPRINTER_H +#define IPPPRINTER_H + +#include +#include +#include "ippmsg.h" + +class IppPrinter : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString url READ getUrl WRITE setUrl NOTIFY urlChanged) + Q_PROPERTY(QJsonObject attrs MEMBER _attrs NOTIFY attrsChanged) + Q_PROPERTY(QJsonObject jobAttrs MEMBER _jobAttrs NOTIFY jobAttrsChanged) + +public: + IppPrinter(); + ~IppPrinter(); + + + QString getUrl() {return _url;} + void setUrl(QString url); + + Q_INVOKABLE bool print(QJsonObject attrs, QString file); + +signals: + void urlChanged(); + void attrsChanged(); + void jobAttrsChanged(); + +public slots: + void onUrlChanged(); + void getPrinterAttributesFinished(QNetworkReply* reply); + void jobRequestFinished(QNetworkReply* reply); + +private: + QString _url; + + QNetworkAccessManager* _nam; + QNetworkAccessManager* _jnam; + + QJsonObject _attrs; + QJsonObject _jobAttrs; + +}; + +#endif // IPPPRINTER_H