diff options
| author | nippy <you@example.com> | 2026-04-18 13:49:56 +0200 |
|---|---|---|
| committer | nippy <you@example.com> | 2026-04-18 13:49:56 +0200 |
| commit | 5d94c0a7d44a2255b81815a52a7056a94a39842d (patch) | |
| tree | 759bdea9645c6a62f9e1e4c001f7d81cccd120d2 /raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp | |
| parent | e79cdf210b267f21a186255ce1a4d50029439d54 (diff) | |
| download | RaveOS-PKGBUILD-5d94c0a7d44a2255b81815a52a7056a94a39842d.tar.gz RaveOS-PKGBUILD-5d94c0a7d44a2255b81815a52a7056a94a39842d.zip | |
update Raveos themes
Diffstat (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp')
12 files changed, 2708 insertions, 0 deletions
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/CREDITS.MD b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/CREDITS.MD new file mode 100644 index 0000000..b7d28cc --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/CREDITS.MD @@ -0,0 +1,5 @@ +# Credits + +* Original socket adapter code is mostly taken from [korylprince/printer-manager-cups](https://github.com/korylprince/printer-manager-cups) +([MIT](https://github.com/korylprince/printer-manager-cups/blob/v1.0.9/LICENSE) licensed): +[conn.go](https://github.com/korylprince/printer-manager-cups/blob/v1.0.9/cups/conn.go) diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/LICENSE b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/adapter-http.go b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/adapter-http.go new file mode 100644 index 0000000..56ababb --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/adapter-http.go @@ -0,0 +1,132 @@ +package ipp + +import ( + "bytes" + "crypto/tls" + "fmt" + "io" + "net" + "net/http" + "strconv" + "time" +) + +type HttpAdapter struct { + host string + port int + username string + password string + useTLS bool + client *http.Client +} + +func NewHttpAdapter(host string, port int, username, password string, useTLS bool) *HttpAdapter { + httpClient := http.Client{ + Timeout: 0, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + ResponseHeaderTimeout: 90 * time.Second, + IdleConnTimeout: 120 * time.Second, + }, + } + + return &HttpAdapter{ + host: host, + port: port, + username: username, + password: password, + useTLS: useTLS, + client: &httpClient, + } +} + +func (h *HttpAdapter) SendRequest(url string, req *Request, additionalResponseData io.Writer) (*Response, error) { + payload, err := req.Encode() + if err != nil { + return nil, err + } + + size := len(payload) + var body io.Reader + if req.File != nil && req.FileSize != -1 { + size += req.FileSize + body = io.MultiReader(bytes.NewBuffer(payload), req.File) + } else { + body = bytes.NewBuffer(payload) + } + + httpReq, err := http.NewRequest("POST", url, body) + if err != nil { + return nil, err + } + + httpReq.Header.Set("Content-Length", strconv.Itoa(size)) + httpReq.Header.Set("Content-Type", ContentTypeIPP) + + if h.username != "" && h.password != "" { + httpReq.SetBasicAuth(h.username, h.password) + } + + httpResp, err := h.client.Do(httpReq) + if err != nil { + return nil, err + } + defer httpResp.Body.Close() + + if httpResp.StatusCode != 200 { + return nil, HTTPError{ + Code: httpResp.StatusCode, + } + } + + // buffer response to avoid read issues + buf := new(bytes.Buffer) + if httpResp.ContentLength > 0 { + buf.Grow(int(httpResp.ContentLength)) + } + if _, err := io.Copy(buf, httpResp.Body); err != nil { + return nil, fmt.Errorf("unable to buffer response: %w", err) + } + + ippResp, err := NewResponseDecoder(buf).Decode(additionalResponseData) + if err != nil { + return nil, err + } + + if err = ippResp.CheckForErrors(); err != nil { + return nil, fmt.Errorf("received error IPP response: %w", err) + } + + return ippResp, nil +} + +func (h *HttpAdapter) GetHttpUri(namespace string, object any) string { + proto := "http" + if h.useTLS { + proto = "https" + } + + uri := fmt.Sprintf("%s://%s:%d", proto, h.host, h.port) + + if namespace != "" { + uri = fmt.Sprintf("%s/%s", uri, namespace) + } + + if object != nil { + uri = fmt.Sprintf("%s/%v", uri, object) + } + + return uri +} + +func (h *HttpAdapter) TestConnection() error { + conn, err := net.Dial("tcp", net.JoinHostPort(h.host, fmt.Sprintf("%d", h.port))) + if err != nil { + return err + } + conn.Close() + + return nil +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/adapter.go b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/adapter.go new file mode 100644 index 0000000..8af0520 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/adapter.go @@ -0,0 +1,9 @@ +package ipp + +import "io" + +type Adapter interface { + SendRequest(url string, req *Request, additionalResponseData io.Writer) (*Response, error) + GetHttpUri(namespace string, object any) string + TestConnection() error +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/attribute.go b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/attribute.go new file mode 100644 index 0000000..295f521 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/attribute.go @@ -0,0 +1,528 @@ +package ipp + +import ( + "encoding/binary" + "fmt" + "io" +) + +const ( + sizeInteger = int16(4) + sizeBoolean = int16(1) +) + +// AttributeEncoder encodes attribute to a io.Writer +type AttributeEncoder struct { + writer io.Writer +} + +// NewAttributeEncoder returns a new encoder that writes to w +func NewAttributeEncoder(w io.Writer) *AttributeEncoder { + return &AttributeEncoder{w} +} + +// Encode encodes a attribute and its value to a io.Writer +// the tag is determined by the AttributeTagMapping map +func (e *AttributeEncoder) Encode(attribute string, value any) error { + tag, ok := AttributeTagMapping[attribute] + if !ok { + return fmt.Errorf("cannot get tag of attribute %s", attribute) + } + + switch v := value.(type) { + case int: + if tag != TagInteger && tag != TagEnum { + return fmt.Errorf("tag for attribute %s does not match with value type", attribute) + } + + if err := e.encodeTag(tag); err != nil { + return err + } + + if err := e.encodeString(attribute); err != nil { + return err + } + + if err := e.encodeInteger(int32(v)); err != nil { + return err + } + case int16: + if tag != TagInteger && tag != TagEnum { + return fmt.Errorf("tag for attribute %s does not match with value type", attribute) + } + + if err := e.encodeTag(tag); err != nil { + return err + } + + if err := e.encodeString(attribute); err != nil { + return err + } + + if err := e.encodeInteger(int32(v)); err != nil { + return err + } + case int8: + if tag != TagInteger && tag != TagEnum { + return fmt.Errorf("tag for attribute %s does not match with value type", attribute) + } + + if err := e.encodeTag(tag); err != nil { + return err + } + + if err := e.encodeString(attribute); err != nil { + return err + } + + if err := e.encodeInteger(int32(v)); err != nil { + return err + } + case int32: + if tag != TagInteger && tag != TagEnum { + return fmt.Errorf("tag for attribute %s does not match with value type", attribute) + } + + if err := e.encodeTag(tag); err != nil { + return err + } + + if err := e.encodeString(attribute); err != nil { + return err + } + + if err := e.encodeInteger(v); err != nil { + return err + } + case int64: + if tag != TagInteger && tag != TagEnum { + return fmt.Errorf("tag for attribute %s does not match with value type", attribute) + } + + if err := e.encodeTag(tag); err != nil { + return err + } + + if err := e.encodeString(attribute); err != nil { + return err + } + + if err := e.encodeInteger(int32(v)); err != nil { + return err + } + case []int: + if tag != TagInteger && tag != TagEnum { + return fmt.Errorf("tag for attribute %s does not match with value type", attribute) + } + + for index, val := range v { + if err := e.encodeTag(tag); err != nil { + return err + } + + if index == 0 { + if err := e.encodeString(attribute); err != nil { + return err + } + } else { + if err := e.writeNullByte(); err != nil { + return err + } + } + + if err := e.encodeInteger(int32(val)); err != nil { + return err + } + } + case []int16: + if tag != TagInteger && tag != TagEnum { + return fmt.Errorf("tag for attribute %s does not match with value type", attribute) + } + + for index, val := range v { + if err := e.encodeTag(tag); err != nil { + return err + } + + if index == 0 { + if err := e.encodeString(attribute); err != nil { + return err + } + } else { + if err := e.writeNullByte(); err != nil { + return err + } + } + + if err := e.encodeInteger(int32(val)); err != nil { + return err + } + } + case []int8: + if tag != TagInteger && tag != TagEnum { + return fmt.Errorf("tag for attribute %s does not match with value type", attribute) + } + + for index, val := range v { + if err := e.encodeTag(tag); err != nil { + return err + } + + if index == 0 { + if err := e.encodeString(attribute); err != nil { + return err + } + } else { + if err := e.writeNullByte(); err != nil { + return err + } + } + + if err := e.encodeInteger(int32(val)); err != nil { + return err + } + } + case []int32: + if tag != TagInteger && tag != TagEnum { + return fmt.Errorf("tag for attribute %s does not match with value type", attribute) + } + + for index, val := range v { + if err := e.encodeTag(tag); err != nil { + return err + } + + if index == 0 { + if err := e.encodeString(attribute); err != nil { + return err + } + } else { + if err := e.writeNullByte(); err != nil { + return err + } + } + + if err := e.encodeInteger(val); err != nil { + return err + } + } + case []int64: + if tag != TagInteger && tag != TagEnum { + return fmt.Errorf("tag for attribute %s does not match with value type", attribute) + } + + for index, val := range v { + if err := e.encodeTag(tag); err != nil { + return err + } + + if index == 0 { + if err := e.encodeString(attribute); err != nil { + return err + } + } else { + if err := e.writeNullByte(); err != nil { + return err + } + } + + if err := e.encodeInteger(int32(val)); err != nil { + return err + } + } + case bool: + if tag != TagBoolean { + return fmt.Errorf("tag for attribute %s does not match with value type", attribute) + } + + if err := e.encodeTag(tag); err != nil { + return err + } + + if err := e.encodeString(attribute); err != nil { + return err + } + + if err := e.encodeBoolean(v); err != nil { + return err + } + case []bool: + if tag != TagBoolean { + return fmt.Errorf("tag for attribute %s does not match with value type", attribute) + } + + for index, val := range v { + if err := e.encodeTag(tag); err != nil { + return err + } + + if index == 0 { + if err := e.encodeString(attribute); err != nil { + return err + } + } else { + if err := e.writeNullByte(); err != nil { + return err + } + } + + if err := e.encodeBoolean(val); err != nil { + return err + } + } + case string: + if err := e.encodeTag(tag); err != nil { + return err + } + + if err := e.encodeString(attribute); err != nil { + return err + } + + if err := e.encodeString(v); err != nil { + return err + } + case []string: + for index, val := range v { + if err := e.encodeTag(tag); err != nil { + return err + } + + if index == 0 { + if err := e.encodeString(attribute); err != nil { + return err + } + } else { + if err := e.writeNullByte(); err != nil { + return err + } + } + + if err := e.encodeString(val); err != nil { + return err + } + } + default: + return fmt.Errorf("type %T is not supported", value) + } + + return nil +} + +func (e *AttributeEncoder) encodeString(s string) error { + if err := binary.Write(e.writer, binary.BigEndian, int16(len(s))); err != nil { + return err + } + + _, err := e.writer.Write([]byte(s)) + return err +} + +func (e *AttributeEncoder) encodeInteger(i int32) error { + if err := binary.Write(e.writer, binary.BigEndian, sizeInteger); err != nil { + return err + } + + return binary.Write(e.writer, binary.BigEndian, i) +} + +func (e *AttributeEncoder) encodeBoolean(b bool) error { + if err := binary.Write(e.writer, binary.BigEndian, sizeBoolean); err != nil { + return err + } + + return binary.Write(e.writer, binary.BigEndian, b) +} + +func (e *AttributeEncoder) encodeTag(t int8) error { + return binary.Write(e.writer, binary.BigEndian, t) +} + +func (e *AttributeEncoder) writeNullByte() error { + return binary.Write(e.writer, binary.BigEndian, int16(0)) +} + +// Attribute defines an ipp attribute +type Attribute struct { + Tag int8 + Name string + Value any +} + +// Resolution defines the resolution attribute +type Resolution struct { + Height int32 + Width int32 + Depth int8 +} + +// AttributeDecoder reads and decodes ipp from an input stream +type AttributeDecoder struct { + reader io.Reader +} + +// NewAttributeDecoder returns a new decoder that reads from r +func NewAttributeDecoder(r io.Reader) *AttributeDecoder { + return &AttributeDecoder{r} +} + +// Decode reads the next ipp attribute into a attribute struct. the type is identified by a tag passed as an argument +func (d *AttributeDecoder) Decode(tag int8) (*Attribute, error) { + attr := Attribute{Tag: tag} + + name, err := d.decodeString() + if err != nil { + return nil, err + } + attr.Name = name + + switch attr.Tag { + case TagEnum, TagInteger: + val, err := d.decodeInteger() + if err != nil { + return nil, err + } + attr.Value = val + case TagBoolean: + val, err := d.decodeBool() + if err != nil { + return nil, err + } + attr.Value = val + case TagDate: + val, err := d.decodeDate() + if err != nil { + return nil, err + } + attr.Value = val + case TagRange: + val, err := d.decodeRange() + if err != nil { + return nil, err + } + attr.Value = val + case TagResolution: + val, err := d.decodeResolution() + if err != nil { + return nil, err + } + attr.Value = val + default: + val, err := d.decodeString() + if err != nil { + return nil, err + } + attr.Value = val + } + + return &attr, nil +} + +func (d *AttributeDecoder) decodeBool() (b bool, err error) { + if _, err = d.readValueLength(); err != nil { + return + } + + if err = binary.Read(d.reader, binary.BigEndian, &b); err != nil { + return + } + + return +} + +func (d *AttributeDecoder) decodeInteger() (i int, err error) { + if _, err = d.readValueLength(); err != nil { + return + } + + var reti int32 + if err = binary.Read(d.reader, binary.BigEndian, &reti); err != nil { + return + } + + return int(reti), nil +} + +func (d *AttributeDecoder) decodeString() (string, error) { + length, err := d.readValueLength() + if err != nil { + return "", err + } + + if length == 0 { + return "", nil + } + + bs := make([]byte, length) + if _, err := d.reader.Read(bs); err != nil { + return "", nil + } + + return string(bs), nil +} + +func (d *AttributeDecoder) decodeDate() ([]int, error) { + length, err := d.readValueLength() + if err != nil { + return nil, err + } + + is := make([]int, length) + var ti int8 + + for i := int16(0); i < length; i++ { + if err = binary.Read(d.reader, binary.BigEndian, &ti); err != nil { + return nil, err + } + is[i] = int(ti) + } + + return is, nil +} + +func (d *AttributeDecoder) decodeRange() ([]int32, error) { + length, err := d.readValueLength() + if err != nil { + return nil, err + } + + // initialize range element count (c) and range slice (r) + c := length / 4 + r := make([]int32, c) + + for i := int16(0); i < c; i++ { + var ti int32 + if err = binary.Read(d.reader, binary.BigEndian, &ti); err != nil { + return nil, err + } + r[i] = ti + } + + return r, nil +} + +func (d *AttributeDecoder) decodeResolution() (res Resolution, err error) { + _, err = d.readValueLength() + if err != nil { + return + } + + if err = binary.Read(d.reader, binary.BigEndian, &res.Height); err != nil { + return + } + + if err = binary.Read(d.reader, binary.BigEndian, &res.Width); err != nil { + return + } + + if err = binary.Read(d.reader, binary.BigEndian, &res.Depth); err != nil { + return + } + + return +} + +func (d *AttributeDecoder) readValueLength() (length int16, err error) { + err = binary.Read(d.reader, binary.BigEndian, &length) + return +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/constants.go b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/constants.go new file mode 100644 index 0000000..83bf41b --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/constants.go @@ -0,0 +1,450 @@ +package ipp + +// ipp status codes +const ( + StatusCupsInvalid int16 = -1 + StatusOk int16 = 0x0000 + StatusOkIgnoredOrSubstituted int16 = 0x0001 + StatusOkConflicting int16 = 0x0002 + StatusOkIgnoredSubscriptions int16 = 0x0003 + StatusOkIgnoredNotifications int16 = 0x0004 + StatusOkTooManyEvents int16 = 0x0005 + StatusOkButCancelSubscription int16 = 0x0006 + StatusOkEventsComplete int16 = 0x0007 + StatusRedirectionOtherSite int16 = 0x0200 + StatusCupsSeeOther int16 = 0x0280 + StatusErrorBadRequest int16 = 0x0400 + StatusErrorForbidden int16 = 0x0401 + StatusErrorNotAuthenticated int16 = 0x0402 + StatusErrorNotAuthorized int16 = 0x0403 + StatusErrorNotPossible int16 = 0x0404 + StatusErrorTimeout int16 = 0x0405 + StatusErrorNotFound int16 = 0x0406 + StatusErrorGone int16 = 0x0407 + StatusErrorRequestEntity int16 = 0x0408 + StatusErrorRequestValue int16 = 0x0409 + StatusErrorDocumentFormatNotSupported int16 = 0x040a + StatusErrorAttributesOrValues int16 = 0x040b + StatusErrorUriScheme int16 = 0x040c + StatusErrorCharset int16 = 0x040d + StatusErrorConflicting int16 = 0x040e + StatusErrorCompressionError int16 = 0x040f + StatusErrorDocumentFormatError int16 = 0x0410 + StatusErrorDocumentAccess int16 = 0x0411 + StatusErrorAttributesNotSettable int16 = 0x0412 + StatusErrorIgnoredAllSubscriptions int16 = 0x0413 + StatusErrorTooManySubscriptions int16 = 0x0414 + StatusErrorIgnoredAllNotifications int16 = 0x0415 + StatusErrorPrintSupportFileNotFound int16 = 0x0416 + StatusErrorDocumentPassword int16 = 0x0417 + StatusErrorDocumentPermission int16 = 0x0418 + StatusErrorDocumentSecurity int16 = 0x0419 + StatusErrorDocumentUnprintable int16 = 0x041a + StatusErrorAccountInfoNeeded int16 = 0x041b + StatusErrorAccountClosed int16 = 0x041c + StatusErrorAccountLimitReached int16 = 0x041d + StatusErrorAccountAuthorizationFailed int16 = 0x041e + StatusErrorNotFetchable int16 = 0x041f + StatusErrorCupsAccountInfoNeeded int16 = 0x049C + StatusErrorCupsAccountClosed int16 = 0x049d + StatusErrorCupsAccountLimitReached int16 = 0x049e + StatusErrorCupsAccountAuthorizationFailed int16 = 0x049f + StatusErrorInternal int16 = 0x0500 + StatusErrorOperationNotSupported int16 = 0x0501 + StatusErrorServiceUnavailable int16 = 0x0502 + StatusErrorVersionNotSupported int16 = 0x0503 + StatusErrorDevice int16 = 0x0504 + StatusErrorTemporary int16 = 0x0505 + StatusErrorNotAcceptingJobs int16 = 0x0506 + StatusErrorBusy int16 = 0x0507 + StatusErrorJobCanceled int16 = 0x0508 + StatusErrorMultipleJobsNotSupported int16 = 0x0509 + StatusErrorPrinterIsDeactivated int16 = 0x050a + StatusErrorTooManyJobs int16 = 0x050b + StatusErrorTooManyDocuments int16 = 0x050c + StatusErrorCupsAuthenticationCanceled int16 = 0x1000 + StatusErrorCupsPki int16 = 0x1001 + StatusErrorCupsUpgradeRequired int16 = 0x1002 +) + +// ipp operations +const ( + OperationCupsInvalid int16 = -0x0001 + OperationCupsNone int16 = 0x0000 + OperationPrintJob int16 = 0x0002 + OperationPrintUri int16 = 0x0003 + OperationValidateJob int16 = 0x0004 + OperationCreateJob int16 = 0x0005 + OperationSendDocument int16 = 0x0006 + OperationSendUri int16 = 0x0007 + OperationCancelJob int16 = 0x0008 + OperationGetJobAttributes int16 = 0x0009 + OperationGetJobs int16 = 0x000a + OperationGetPrinterAttributes int16 = 0x000b + OperationHoldJob int16 = 0x000c + OperationReleaseJob int16 = 0x000d + OperationRestartJob int16 = 0x000e + OperationPausePrinter int16 = 0x0010 + OperationResumePrinter int16 = 0x0011 + OperationPurgeJobs int16 = 0x0012 + OperationSetPrinterAttributes int16 = 0x0013 + OperationSetJobAttributes int16 = 0x0014 + OperationGetPrinterSupportedValues int16 = 0x0015 + OperationCreatePrinterSubscriptions int16 = 0x0016 + OperationCreateJobSubscriptions int16 = 0x0017 + OperationGetSubscriptionAttributes int16 = 0x0018 + OperationGetSubscriptions int16 = 0x0019 + OperationRenewSubscription int16 = 0x001a + OperationCancelSubscription int16 = 0x001b + OperationGetNotifications int16 = 0x001c + OperationSendNotifications int16 = 0x001d + OperationGetResourceAttributes int16 = 0x001e + OperationGetResourceData int16 = 0x001f + OperationGetResources int16 = 0x0020 + OperationGetPrintSupportFiles int16 = 0x0021 + OperationEnablePrinter int16 = 0x0022 + OperationDisablePrinter int16 = 0x0023 + OperationPausePrinterAfterCurrentJob int16 = 0x0024 + OperationHoldNewJobs int16 = 0x0025 + OperationReleaseHeldNewJobs int16 = 0x0026 + OperationDeactivatePrinter int16 = 0x0027 + OperationActivatePrinter int16 = 0x0028 + OperationRestartPrinter int16 = 0x0029 + OperationShutdownPrinter int16 = 0x002a + OperationStartupPrinter int16 = 0x002b + OperationReprocessJob int16 = 0x002c + OperationCancelCurrentJob int16 = 0x002d + OperationSuspendCurrentJob int16 = 0x002e + OperationResumeJob int16 = 0x002f + OperationOperationPromoteJob int16 = 0x0030 + OperationScheduleJobAfter int16 = 0x0031 + OperationCancelDocument int16 = 0x0033 + OperationGetDocumentAttributes int16 = 0x0034 + OperationGetDocuments int16 = 0x0035 + OperationDeleteDocument int16 = 0x0036 + OperationSetDocumentAttributes int16 = 0x0037 + OperationCancelJobs int16 = 0x0038 + OperationCancelMyJobs int16 = 0x0039 + OperationResubmitJob int16 = 0x003a + OperationCloseJob int16 = 0x003b + OperationIdentifyPrinter int16 = 0x003c + OperationValidateDocument int16 = 0x003d + OperationAddDocumentImages int16 = 0x003e + OperationAcknowledgeDocument int16 = 0x003f + OperationAcknowledgeIdentifyPrinter int16 = 0x0040 + OperationAcknowledgeJob int16 = 0x0041 + OperationFetchDocument int16 = 0x0042 + OperationFetchJob int16 = 0x0043 + OperationGetOutputDeviceAttributes int16 = 0x0044 + OperationUpdateActiveJobs int16 = 0x0045 + OperationDeregisterOutputDevice int16 = 0x0046 + OperationUpdateDocumentStatus int16 = 0x0047 + OperationUpdateJobStatus int16 = 0x0048 + OperationUpdateOutputDeviceAttributes int16 = 0x0049 + OperationGetNextDocumentData int16 = 0x004a + OperationAllocatePrinterResources int16 = 0x004b + OperationCreatePrinter int16 = 0x004c + OperationDeallocatePrinterResources int16 = 0x004d + OperationDeletePrinter int16 = 0x004e + OperationGetPrinters int16 = 0x004f + OperationShutdownOnePrinter int16 = 0x0050 + OperationStartupOnePrinter int16 = 0x0051 + OperationCancelResource int16 = 0x0052 + OperationCreateResource int16 = 0x0053 + OperationInstallResource int16 = 0x0054 + OperationSendResourceData int16 = 0x0055 + OperationSetResourceAttributes int16 = 0x0056 + OperationCreateResourceSubscriptions int16 = 0x0057 + OperationCreateSystemSubscriptions int16 = 0x0058 + OperationDisableAllPrinters int16 = 0x0059 + OperationEnableAllPrinters int16 = 0x005a + OperationGetSystemAttributes int16 = 0x005b + OperationGetSystemSupportedValues int16 = 0x005c + OperationPauseAllPrinters int16 = 0x005d + OperationPauseAllPrintersAfterCurrentJob int16 = 0x005e + OperationRegisterOutputDevice int16 = 0x005f + OperationRestartSystem int16 = 0x0060 + OperationResumeAllPrinters int16 = 0x0061 + OperationSetSystemAttributes int16 = 0x0062 + OperationShutdownAllPrinter int16 = 0x0063 + OperationStartupAllPrinters int16 = 0x0064 + OperationPrivate int16 = 0x4000 + OperationCupsGetDefault int16 = 0x4001 + OperationCupsGetPrinters int16 = 0x4002 + OperationCupsAddModifyPrinter int16 = 0x4003 + OperationCupsDeletePrinter int16 = 0x4004 + OperationCupsGetClasses int16 = 0x4005 + OperationCupsAddModifyClass int16 = 0x4006 + OperationCupsDeleteClass int16 = 0x4007 + OperationCupsAcceptJobs int16 = 0x4008 + OperationCupsRejectJobs int16 = 0x4009 + OperationCupsSetDefault int16 = 0x400a + OperationCupsGetDevices int16 = 0x400b + OperationCupsGetPPDs int16 = 0x400c + OperationCupsMoveJob int16 = 0x400d + OperationCupsAuthenticateJob int16 = 0x400e + OperationCupsGetPpd int16 = 0x400f + OperationCupsGetDocument int16 = 0x4027 + OperationCupsCreateLocalPrinter int16 = 0x4028 +) + +// ipp tags +const ( + TagCupsInvalid int8 = -1 + TagZero int8 = 0x00 + TagOperation int8 = 0x01 + TagJob int8 = 0x02 + TagEnd int8 = 0x03 + TagPrinter int8 = 0x04 + TagUnsupportedGroup int8 = 0x05 + TagSubscription int8 = 0x06 + TagEventNotification int8 = 0x07 + TagResource int8 = 0x08 + TagDocument int8 = 0x09 + TagSystem int8 = 0x0a + TagUnsupportedValue int8 = 0x10 + TagDefault int8 = 0x11 + TagUnknown int8 = 0x12 + TagNoValue int8 = 0x13 + TagNotSettable int8 = 0x15 + TagDeleteAttr int8 = 0x16 + TagAdminDefine int8 = 0x17 + TagInteger int8 = 0x21 + TagBoolean int8 = 0x22 + TagEnum int8 = 0x23 + TagString int8 = 0x30 + TagDate int8 = 0x31 + TagResolution int8 = 0x32 + TagRange int8 = 0x33 + TagBeginCollection int8 = 0x34 + TagTextLang int8 = 0x35 + TagNameLang int8 = 0x36 + TagEndCollection int8 = 0x37 + TagText int8 = 0x41 + TagName int8 = 0x42 + TagReservedString int8 = 0x43 + TagKeyword int8 = 0x44 + TagUri int8 = 0x45 + TagUriScheme int8 = 0x46 + TagCharset int8 = 0x47 + TagLanguage int8 = 0x48 + TagMimeType int8 = 0x49 + TagMemberName int8 = 0x4a + TagExtension int8 = 0x7f +) + +// job states +const ( + JobStatePending int8 = 0x03 + JobStateHeld int8 = 0x04 + JobStateProcessing int8 = 0x05 + JobStateStopped int8 = 0x06 + JobStateCanceled int8 = 0x07 + JobStateAborted int8 = 0x08 + JobStateCompleted int8 = 0x09 +) + +// document states +const ( + DocumentStatePending int8 = 0x03 + DocumentStateProcessing int8 = 0x05 + DocumentStateCanceled int8 = 0x07 + DocumentStateAborted int8 = 0x08 + DocumentStateCompleted int8 = 0x08 +) + +// printer states +const ( + PrinterStateIdle int8 = 0x0003 + PrinterStateProcessing int8 = 0x0004 + PrinterStateStopped int8 = 0x0005 +) + +// job state filter +const ( + JobStateFilterNotCompleted = "not-completed" + JobStateFilterCompleted = "completed" + JobStateFilterAll = "all" +) + +// error policies +const ( + ErrorPolicyRetryJob = "retry-job" + ErrorPolicyAbortJob = "abort-job" + ErrorPolicyRetryCurrentJob = "retry-current-job" + ErrorPolicyStopPrinter = "stop-printer" +) + +// ipp defaults +const ( + CharsetLanguage = "en-US" + Charset = "utf-8" + ProtocolVersionMajor = int8(2) + ProtocolVersionMinor = int8(0) + + DefaultJobPriority = 50 +) + +// useful mime types for ipp +const ( + MimeTypePostscript = "application/postscript" + MimeTypePDF = "application/pdf" + MimeTypeOctetStream = "application/octet-stream" +) + +// ipp content types +const ( + ContentTypeIPP = "application/ipp" +) + +// known ipp attributes +const ( + AttributeCopies = "copies" + AttributeDocumentFormat = "document-format" + AttributeDocumentName = "document-name" + AttributeJobID = "job-id" + AttributeJobName = "job-name" + AttributeJobPriority = "job-priority" + AttributeJobURI = "job-uri" + AttributeLastDocument = "last-document" + AttributeMyJobs = "my-jobs" + AttributePPDName = "ppd-name" + AttributePPDMakeAndModel = "ppd-make-and-model" + AttributePrinterIsShared = "printer-is-shared" + AttributePrinterIsTemporary = "printer-is-temporary" + AttributePrinterURI = "printer-uri" + AttributePurgeJobs = "purge-jobs" + AttributeRequestedAttributes = "requested-attributes" + AttributeRequestingUserName = "requesting-user-name" + AttributeWhichJobs = "which-jobs" + AttributeFirstJobID = "first-job-id" + AttributeLimit = "limit" + AttributeStatusMessage = "status-message" + AttributeCharset = "attributes-charset" + AttributeNaturalLanguage = "attributes-natural-language" + AttributeDeviceURI = "device-uri" + AttributeHoldJobUntil = "job-hold-until" + AttributePrinterErrorPolicy = "printer-error-policy" + AttributePrinterInfo = "printer-info" + AttributePrinterLocation = "printer-location" + AttributePrinterName = "printer-name" + AttributePrinterStateReasons = "printer-state-reasons" + AttributeJobPrinterURI = "job-printer-uri" + AttributeMemberURIs = "member-uris" + AttributeDocumentNumber = "document-number" + AttributeDocumentState = "document-state" + AttributeFinishings = "finishings" + AttributeJobHoldUntil = "hold-job-until" + AttributeJobSheets = "job-sheets" + AttributeJobState = "job-state" + AttributeJobStateReason = "job-state-reason" + AttributeMedia = "media" + AttributeSides = "sides" + AttributeNumberUp = "number-up" + AttributeOrientationRequested = "orientation-requested" + AttributePrintQuality = "print-quality" + AttributePrinterIsAcceptingJobs = "printer-is-accepting-jobs" + AttributePrinterResolution = "printer-resolution" + AttributePrinterState = "printer-state" + AttributeMemberNames = "member-names" + AttributePrinterType = "printer-type" + AttributePrinterMakeAndModel = "printer-make-and-model" + AttributePrinterStateMessage = "printer-state-message" + AttributePrinterUriSupported = "printer-uri-supported" + AttributeJobMediaProgress = "job-media-progress" + AttributeJobKilobyteOctets = "job-k-octets" + AttributeNumberOfDocuments = "number-of-documents" + AttributeJobOriginatingUserName = "job-originating-user-name" + AttributeOutputOrder = "outputorder" + AttributeJobStateReasons = "job-state-reasons" + AttributeJobStateMessage = "job-state-message" + AttributeJobPrinterStateReasons = "job-printer-state-reasons" + AttributeJobPrinterStateMessage = "job-printer-state-message" + AttributeJobImpressionsCompleted = "job-impressions-completed" + AttributePrintScaling = "print-scaling" +) + +// Default attributes +var ( + DefaultClassAttributes = []string{AttributePrinterName, AttributeMemberNames} + DefaultPrinterAttributes = []string{ + AttributePrinterName, AttributePrinterType, AttributePrinterLocation, AttributePrinterInfo, + AttributePrinterMakeAndModel, AttributePrinterState, AttributePrinterStateMessage, AttributePrinterStateReasons, + AttributePrinterUriSupported, AttributeDeviceURI, AttributePrinterIsShared, + } + DefaultJobAttributes = []string{ + AttributeJobID, AttributeJobName, AttributePrinterURI, AttributeJobState, AttributeJobStateReason, + AttributeJobHoldUntil, AttributeJobMediaProgress, AttributeJobKilobyteOctets, AttributeNumberOfDocuments, AttributeCopies, + AttributeJobOriginatingUserName, + } +) + +// Attribute to tag mapping +var ( + AttributeTagMapping = map[string]int8{ + AttributeCharset: TagCharset, + AttributeNaturalLanguage: TagLanguage, + AttributeCopies: TagInteger, + AttributeDeviceURI: TagUri, + AttributeDocumentFormat: TagMimeType, + AttributeDocumentName: TagName, + AttributeDocumentNumber: TagInteger, + AttributeDocumentState: TagEnum, + AttributeFinishings: TagEnum, + AttributeJobHoldUntil: TagKeyword, + AttributeHoldJobUntil: TagKeyword, + AttributeJobID: TagInteger, + AttributeJobName: TagName, + AttributeJobPrinterURI: TagUri, + AttributeJobPriority: TagInteger, + AttributeJobSheets: TagName, + AttributeJobState: TagEnum, + AttributeJobStateReason: TagKeyword, + AttributeJobURI: TagUri, + AttributeLastDocument: TagBoolean, + AttributeMedia: TagKeyword, + AttributeSides: TagKeyword, + AttributeMemberURIs: TagUri, + AttributeMyJobs: TagBoolean, + AttributeNumberUp: TagInteger, + AttributeOrientationRequested: TagEnum, + AttributePPDName: TagName, + AttributePPDMakeAndModel: TagText, + AttributeNumberOfDocuments: TagInteger, + AttributePrintQuality: TagEnum, + AttributePrinterErrorPolicy: TagName, + AttributePrinterInfo: TagText, + AttributePrinterIsAcceptingJobs: TagBoolean, + AttributePrinterIsShared: TagBoolean, + AttributePrinterIsTemporary: TagBoolean, + AttributePrinterName: TagName, + AttributePrinterLocation: TagText, + AttributePrinterResolution: TagResolution, + AttributePrinterState: TagEnum, + AttributePrinterStateReasons: TagKeyword, + AttributePrinterURI: TagUri, + AttributePurgeJobs: TagBoolean, + AttributeRequestedAttributes: TagKeyword, + AttributeRequestingUserName: TagName, + AttributeWhichJobs: TagKeyword, + AttributeFirstJobID: TagInteger, + AttributeStatusMessage: TagText, + AttributeLimit: TagInteger, + AttributeOutputOrder: TagName, + AttributeJobStateReasons: TagString, + AttributeJobStateMessage: TagString, + AttributeJobPrinterStateReasons: TagString, + AttributeJobPrinterStateMessage: TagString, + AttributeJobImpressionsCompleted: TagInteger, + AttributePrintScaling: TagKeyword, + // IPP Subscription/Notification attributes (added for dankdots) + "notify-events": TagKeyword, + "notify-pull-method": TagKeyword, + "notify-lease-duration": TagInteger, + "notify-subscription-id": TagInteger, + "notify-subscription-ids": TagInteger, + "notify-sequence-numbers": TagInteger, + "notify-wait": TagBoolean, + "notify-recipient-uri": TagUri, + } +) diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/cups-client.go b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/cups-client.go new file mode 100644 index 0000000..68d60ce --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/cups-client.go @@ -0,0 +1,313 @@ +package ipp + +import ( + "io" + "strings" +) + +// CUPSClient implements a ipp client with specific cups operations +type CUPSClient struct { + *IPPClient +} + +// NewCUPSClient creates a new cups ipp client (used HttpAdapter internally) +func NewCUPSClient(host string, port int, username, password string, useTLS bool) *CUPSClient { + ippClient := NewIPPClient(host, port, username, password, useTLS) + return &CUPSClient{ippClient} +} + +// NewCUPSClientWithAdapter creates a new cups ipp client with given Adapter +func NewCUPSClientWithAdapter(username string, adapter Adapter) *CUPSClient { + ippClient := NewIPPClientWithAdapter(username, adapter) + return &CUPSClient{ippClient} +} + +// GetDevices returns a map of device uris and printer attributes +func (c *CUPSClient) GetDevices() (map[string]Attributes, error) { + req := NewRequest(OperationCupsGetDevices, 1) + + resp, err := c.SendRequest(c.adapter.GetHttpUri("", nil), req, nil) + if err != nil { + return nil, err + } + + printerNameMap := make(map[string]Attributes) + + for _, printerAttributes := range resp.PrinterAttributes { + printerNameMap[printerAttributes[AttributeDeviceURI][0].Value.(string)] = printerAttributes + } + + return printerNameMap, nil +} + +// MoveJob moves a job to a other printer +func (c *CUPSClient) MoveJob(jobID int, destPrinter string) error { + req := NewRequest(OperationCupsMoveJob, 1) + req.OperationAttributes[AttributeJobURI] = c.getJobUri(jobID) + req.PrinterAttributes[AttributeJobPrinterURI] = c.getPrinterUri(destPrinter) + + _, err := c.SendRequest(c.adapter.GetHttpUri("jobs", ""), req, nil) + return err +} + +// MoveAllJob moves all job from a printer to a other printer +func (c *CUPSClient) MoveAllJob(srcPrinter, destPrinter string) error { + req := NewRequest(OperationCupsMoveJob, 1) + req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(srcPrinter) + req.PrinterAttributes[AttributeJobPrinterURI] = c.getPrinterUri(destPrinter) + + _, err := c.SendRequest(c.adapter.GetHttpUri("jobs", ""), req, nil) + return err +} + +// GetPPDs returns a map of ppd names and attributes +func (c *CUPSClient) GetPPDs() (map[string]Attributes, error) { + req := NewRequest(OperationCupsGetPPDs, 1) + + resp, err := c.SendRequest(c.adapter.GetHttpUri("", nil), req, nil) + if err != nil { + return nil, err + } + + ppdNameMap := make(map[string]Attributes) + + for _, printerAttributes := range resp.PrinterAttributes { + ppdNameMap[printerAttributes[AttributePPDName][0].Value.(string)] = printerAttributes + } + + return ppdNameMap, nil +} + +// AcceptJobs lets a printer accept jobs again +func (c *CUPSClient) AcceptJobs(printer string) error { + req := NewRequest(OperationCupsAcceptJobs, 1) + req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) + + _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) + return err +} + +// RejectJobs does not let a printer accept jobs +func (c *CUPSClient) RejectJobs(printer string) error { + req := NewRequest(OperationCupsRejectJobs, 1) + req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) + + _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) + return err +} + +// AddPrinterToClass adds a printer to a class, if the class does not exists it will be created +func (c *CUPSClient) AddPrinterToClass(class, printer string) error { + attributes, err := c.GetPrinterAttributes(class, []string{AttributeMemberURIs}) + if err != nil && !IsNotExistsError(err) { + return err + } + + memberURIList := make([]string, 0) + + if !IsNotExistsError(err) { + for _, member := range attributes[AttributeMemberURIs] { + memberString := strings.Split(member.Value.(string), "/") + printerName := memberString[len(memberString)-1] + + if printerName == printer { + return nil + } + + memberURIList = append(memberURIList, member.Value.(string)) + } + } + + memberURIList = append(memberURIList, c.getPrinterUri(printer)) + + req := NewRequest(OperationCupsAddModifyClass, 1) + req.OperationAttributes[AttributePrinterURI] = c.getClassUri(class) + req.PrinterAttributes[AttributeMemberURIs] = memberURIList + + _, err = c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) + return err +} + +// DeletePrinterFromClass removes a printer from a class, if a class has no more printer it will be deleted +func (c *CUPSClient) DeletePrinterFromClass(class, printer string) error { + attributes, err := c.GetPrinterAttributes(class, []string{AttributeMemberURIs}) + if err != nil { + return err + } + + memberURIList := make([]string, 0) + + for _, member := range attributes[AttributeMemberURIs] { + memberString := strings.Split(member.Value.(string), "/") + printerName := memberString[len(memberString)-1] + + if printerName != printer { + memberURIList = append(memberURIList, member.Value.(string)) + } + } + + if len(memberURIList) == 0 { + return c.DeleteClass(class) + } + + req := NewRequest(OperationCupsAddModifyClass, 1) + req.OperationAttributes[AttributePrinterURI] = c.getClassUri(class) + req.PrinterAttributes[AttributeMemberURIs] = memberURIList + + _, err = c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) + return err +} + +// DeleteClass deletes a class +func (c *CUPSClient) DeleteClass(class string) error { + req := NewRequest(OperationCupsDeleteClass, 1) + req.OperationAttributes[AttributePrinterURI] = c.getClassUri(class) + + _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) + return err +} + +// CreatePrinter creates a new printer +func (c *CUPSClient) CreatePrinter(name, deviceURI, ppd string, shared bool, errorPolicy string, information, location string) error { + req := NewRequest(OperationCupsAddModifyPrinter, 1) + req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(name) + req.OperationAttributes[AttributePPDName] = ppd + req.OperationAttributes[AttributePrinterIsShared] = shared + req.PrinterAttributes[AttributePrinterStateReasons] = "none" + req.PrinterAttributes[AttributeDeviceURI] = deviceURI + req.PrinterAttributes[AttributePrinterInfo] = information + req.PrinterAttributes[AttributePrinterLocation] = location + req.PrinterAttributes[AttributePrinterErrorPolicy] = errorPolicy + + _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) + return err +} + +// SetPrinterPPD sets the ppd for a printer +func (c *CUPSClient) SetPrinterPPD(printer, ppd string) error { + req := NewRequest(OperationCupsAddModifyPrinter, 1) + req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) + req.OperationAttributes[AttributePPDName] = ppd + + _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) + return err +} + +// SetPrinterDeviceURI sets the device uri for a printer +func (c *CUPSClient) SetPrinterDeviceURI(printer, deviceURI string) error { + req := NewRequest(OperationCupsAddModifyPrinter, 1) + req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) + req.PrinterAttributes[AttributeDeviceURI] = deviceURI + + _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) + return err +} + +// SetPrinterIsShared shares or unshares a printer in the network +func (c *CUPSClient) SetPrinterIsShared(printer string, shared bool) error { + req := NewRequest(OperationCupsAddModifyPrinter, 1) + req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) + req.OperationAttributes[AttributePrinterIsShared] = shared + + _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) + return err +} + +// SetPrinterErrorPolicy sets the error policy for a printer +func (c *CUPSClient) SetPrinterErrorPolicy(printer string, errorPolicy string) error { + req := NewRequest(OperationCupsAddModifyPrinter, 1) + req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) + req.PrinterAttributes[AttributePrinterErrorPolicy] = errorPolicy + + _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) + return err +} + +// SetPrinterInformation sets general printer information +func (c *CUPSClient) SetPrinterInformation(printer, information string) error { + req := NewRequest(OperationCupsAddModifyPrinter, 1) + req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) + req.PrinterAttributes[AttributePrinterInfo] = information + + _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) + return err +} + +// SetPrinterLocation sets the printer location +func (c *CUPSClient) SetPrinterLocation(printer, location string) error { + req := NewRequest(OperationCupsAddModifyPrinter, 1) + req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) + req.PrinterAttributes[AttributePrinterLocation] = location + + _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) + return err +} + +// DeletePrinter deletes a printer +func (c *CUPSClient) DeletePrinter(printer string) error { + req := NewRequest(OperationCupsDeletePrinter, 1) + req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) + + _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) + return err +} + +// GetPrinters returns a map of printer names and attributes +func (c *CUPSClient) GetPrinters(attributes []string) (map[string]Attributes, error) { + req := NewRequest(OperationCupsGetPrinters, 1) + + if attributes == nil { + req.OperationAttributes[AttributeRequestedAttributes] = DefaultPrinterAttributes + } else { + req.OperationAttributes[AttributeRequestedAttributes] = append(attributes, AttributePrinterName) + } + + resp, err := c.SendRequest(c.adapter.GetHttpUri("", nil), req, nil) + if err != nil { + return nil, err + } + + printerNameMap := make(map[string]Attributes) + + for _, printerAttributes := range resp.PrinterAttributes { + printerNameMap[printerAttributes[AttributePrinterName][0].Value.(string)] = printerAttributes + } + + return printerNameMap, nil +} + +// GetClasses returns a map of class names and attributes +func (c *CUPSClient) GetClasses(attributes []string) (map[string]Attributes, error) { + req := NewRequest(OperationCupsGetClasses, 1) + + if attributes == nil { + req.OperationAttributes[AttributeRequestedAttributes] = DefaultClassAttributes + } else { + req.OperationAttributes[AttributeRequestedAttributes] = append(attributes, AttributePrinterName) + } + + resp, err := c.SendRequest(c.adapter.GetHttpUri("", nil), req, nil) + if err != nil { + return nil, err + } + + printerNameMap := make(map[string]Attributes) + + for _, printerAttributes := range resp.PrinterAttributes { + printerNameMap[printerAttributes[AttributePrinterName][0].Value.(string)] = printerAttributes + } + + return printerNameMap, nil +} + +// PrintTestPage prints a test page using the provided PDF data +func (c *CUPSClient) PrintTestPage(printer string, testPageData io.Reader, size int) (int, error) { + return c.PrintJob(Document{ + Document: testPageData, + Name: "Test Page", + Size: size, + MimeType: MimeTypePDF, + }, printer, map[string]any{ + AttributeJobName: "Test Page", + }) +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/error.go b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/error.go new file mode 100644 index 0000000..e4edf5e --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/error.go @@ -0,0 +1,31 @@ +package ipp + +import "fmt" + +// IsNotExistsError checks a given error whether a printer or class does not exist +func IsNotExistsError(err error) bool { + if err == nil { + return false + } + + return err.Error() == "The printer or class does not exist." +} + +// IPPError used for non ok ipp status codes +type IPPError struct { + Status int16 + Message string +} + +func (e IPPError) Error() string { + return fmt.Sprintf("ipp status: %d, message: %s", e.Status, e.Message) +} + +// HTTPError used for non 200 http codes +type HTTPError struct { + Code int +} + +func (e HTTPError) Error() string { + return fmt.Sprintf("got http code %d", e.Code) +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/ipp-client.go b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/ipp-client.go new file mode 100644 index 0000000..a21dd54 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/ipp-client.go @@ -0,0 +1,329 @@ +package ipp + +import ( + "errors" + "fmt" + "io" + "os" + "path" +) + +// Document wraps an io.Reader with more information, needed for encoding +type Document struct { + Document io.Reader + Size int + Name string + MimeType string +} + +// IPPClient implements a generic ipp client +type IPPClient struct { + username string + adapter Adapter +} + +// NewIPPClient creates a new generic ipp client (used HttpAdapter internally) +func NewIPPClient(host string, port int, username, password string, useTLS bool) *IPPClient { + adapter := NewHttpAdapter(host, port, username, password, useTLS) + + return &IPPClient{ + username: username, + adapter: adapter, + } +} + +// NewIPPClientWithAdapter creates a new generic ipp client with given Adapter +func NewIPPClientWithAdapter(username string, adapter Adapter) *IPPClient { + return &IPPClient{ + username: username, + adapter: adapter, + } +} + +func (c *IPPClient) getPrinterUri(printer string) string { + return fmt.Sprintf("ipp://localhost/printers/%s", printer) +} + +func (c *IPPClient) getJobUri(jobID int) string { + return fmt.Sprintf("ipp://localhost/jobs/%d", jobID) +} + +func (c *IPPClient) getClassUri(printer string) string { + return fmt.Sprintf("ipp://localhost/classes/%s", printer) +} + +// SendRequest sends a request to a remote uri end returns the response +func (c *IPPClient) SendRequest(url string, req *Request, additionalResponseData io.Writer) (*Response, error) { + if _, ok := req.OperationAttributes[AttributeRequestingUserName]; !ok { + req.OperationAttributes[AttributeRequestingUserName] = c.username + } + + return c.adapter.SendRequest(url, req, additionalResponseData) +} + +// PrintDocuments prints one or more documents using a Create-Job operation followed by one or more Send-Document operation(s). custom job settings can be specified via the jobAttributes parameter +func (c *IPPClient) PrintDocuments(docs []Document, printer string, jobAttributes map[string]any) (int, error) { + printerURI := c.getPrinterUri(printer) + + req := NewRequest(OperationCreateJob, 1) + req.OperationAttributes[AttributePrinterURI] = printerURI + req.OperationAttributes[AttributeRequestingUserName] = c.username + + // set defaults for some attributes, may get overwritten + req.OperationAttributes[AttributeJobName] = docs[0].Name + req.OperationAttributes[AttributeCopies] = 1 + req.OperationAttributes[AttributeJobPriority] = DefaultJobPriority + + for key, value := range jobAttributes { + req.JobAttributes[key] = value + } + + resp, err := c.SendRequest(c.adapter.GetHttpUri("printers", printer), req, nil) + if err != nil { + return -1, err + } + + if len(resp.JobAttributes) == 0 { + return 0, errors.New("server doesn't returned a job id") + } + + jobID := resp.JobAttributes[0][AttributeJobID][0].Value.(int) + + documentCount := len(docs) - 1 + + for docID, doc := range docs { + req = NewRequest(OperationSendDocument, 2) + req.OperationAttributes[AttributePrinterURI] = printerURI + req.OperationAttributes[AttributeRequestingUserName] = c.username + req.OperationAttributes[AttributeJobID] = jobID + req.OperationAttributes[AttributeDocumentName] = doc.Name + req.OperationAttributes[AttributeDocumentFormat] = doc.MimeType + req.OperationAttributes[AttributeLastDocument] = docID == documentCount + req.File = doc.Document + req.FileSize = doc.Size + + _, err = c.SendRequest(c.adapter.GetHttpUri("printers", printer), req, nil) + if err != nil { + return -1, err + } + } + + return jobID, nil +} + +// PrintJob prints a document using a Print-Job operation. custom job settings can be specified via the jobAttributes parameter +func (c *IPPClient) PrintJob(doc Document, printer string, jobAttributes map[string]any) (int, error) { + printerURI := c.getPrinterUri(printer) + + req := NewRequest(OperationPrintJob, 1) + req.OperationAttributes[AttributePrinterURI] = printerURI + req.OperationAttributes[AttributeRequestingUserName] = c.username + req.OperationAttributes[AttributeJobName] = doc.Name + req.OperationAttributes[AttributeDocumentFormat] = doc.MimeType + + // set defaults for some attributes, may get overwritten + req.OperationAttributes[AttributeCopies] = 1 + req.OperationAttributes[AttributeJobPriority] = DefaultJobPriority + + for key, value := range jobAttributes { + req.JobAttributes[key] = value + } + + req.File = doc.Document + req.FileSize = doc.Size + + resp, err := c.SendRequest(c.adapter.GetHttpUri("printers", printer), req, nil) + if err != nil { + return -1, err + } + + if len(resp.JobAttributes) == 0 { + return 0, errors.New("server doesn't returned a job id") + } + + jobID := resp.JobAttributes[0][AttributeJobID][0].Value.(int) + + return jobID, nil +} + +// PrintFile prints a local file on the file system. custom job settings can be specified via the jobAttributes parameter +func (c *IPPClient) PrintFile(filePath, printer string, jobAttributes map[string]any) (int, error) { + fileStats, err := os.Stat(filePath) + if os.IsNotExist(err) { + return -1, err + } + + fileName := path.Base(filePath) + + document, err := os.Open(filePath) + if err != nil { + return 0, err + } + defer document.Close() + + jobAttributes[AttributeJobName] = fileName + + return c.PrintDocuments([]Document{ + { + Document: document, + Name: fileName, + Size: int(fileStats.Size()), + MimeType: MimeTypeOctetStream, + }, + }, printer, jobAttributes) +} + +// GetPrinterAttributes returns the requested attributes for the specified printer, if attributes is nil the default attributes will be used +func (c *IPPClient) GetPrinterAttributes(printer string, attributes []string) (Attributes, error) { + req := NewRequest(OperationGetPrinterAttributes, 1) + req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) + req.OperationAttributes[AttributeRequestingUserName] = c.username + + if attributes == nil { + req.OperationAttributes[AttributeRequestedAttributes] = DefaultPrinterAttributes + } else { + req.OperationAttributes[AttributeRequestedAttributes] = attributes + } + + resp, err := c.SendRequest(c.adapter.GetHttpUri("printers", printer), req, nil) + if err != nil { + return nil, err + } + + if len(resp.PrinterAttributes) == 0 { + return nil, errors.New("server doesn't return any printer attributes") + } + + return resp.PrinterAttributes[0], nil +} + +// ResumePrinter resumes a printer +func (c *IPPClient) ResumePrinter(printer string) error { + req := NewRequest(OperationResumePrinter, 1) + req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) + + _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) + return err +} + +// PausePrinter pauses a printer +func (c *IPPClient) PausePrinter(printer string) error { + req := NewRequest(OperationPausePrinter, 1) + req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) + + _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) + return err +} + +// GetJobAttributes returns the requested attributes for the specified job, if attributes is nil the default job will be used +func (c *IPPClient) GetJobAttributes(jobID int, attributes []string) (Attributes, error) { + req := NewRequest(OperationGetJobAttributes, 1) + req.OperationAttributes[AttributeJobURI] = c.getJobUri(jobID) + + if attributes == nil { + req.OperationAttributes[AttributeRequestedAttributes] = DefaultJobAttributes + } else { + req.OperationAttributes[AttributeRequestedAttributes] = attributes + } + + resp, err := c.SendRequest(c.adapter.GetHttpUri("jobs", jobID), req, nil) + if err != nil { + return nil, err + } + + if len(resp.JobAttributes) == 0 { + return nil, errors.New("server doesn't return any job attributes") + } + + return resp.JobAttributes[0], nil +} + +// GetJobs returns jobs from a printer or class +func (c *IPPClient) GetJobs(printer, class string, whichJobs string, myJobs bool, firstJobId, limit int, attributes []string) (map[int]Attributes, error) { + req := NewRequest(OperationGetJobs, 1) + req.OperationAttributes[AttributeWhichJobs] = whichJobs + req.OperationAttributes[AttributeMyJobs] = myJobs + + if printer != "" { + req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) + } else if class != "" { + req.OperationAttributes[AttributePrinterURI] = c.getClassUri(printer) + } else { + req.OperationAttributes[AttributePrinterURI] = "ipp://localhost/" + } + + if firstJobId > 0 { + req.OperationAttributes[AttributeFirstJobID] = firstJobId + } + + if limit > 0 { + req.OperationAttributes[AttributeLimit] = limit + } + + if myJobs { + req.OperationAttributes[AttributeRequestingUserName] = c.username + } + + if attributes == nil { + req.OperationAttributes[AttributeRequestedAttributes] = DefaultJobAttributes + } else { + req.OperationAttributes[AttributeRequestedAttributes] = append(attributes, AttributeJobID) + } + + resp, err := c.SendRequest(c.adapter.GetHttpUri("", nil), req, nil) + if err != nil { + return nil, err + } + + jobIDMap := make(map[int]Attributes) + + for _, jobAttributes := range resp.JobAttributes { + jobIDMap[jobAttributes[AttributeJobID][0].Value.(int)] = jobAttributes + } + + return jobIDMap, nil +} + +// CancelJob cancels a job. if purge is true, the job will also be removed +func (c *IPPClient) CancelJob(jobID int, purge bool) error { + req := NewRequest(OperationCancelJob, 1) + req.OperationAttributes[AttributeJobURI] = c.getJobUri(jobID) + req.OperationAttributes[AttributePurgeJobs] = purge + + _, err := c.SendRequest(c.adapter.GetHttpUri("jobs", ""), req, nil) + return err +} + +// CancelAllJob cancels all jobs for a specified printer. if purge is true, the jobs will also be removed +func (c *IPPClient) CancelAllJob(printer string, purge bool) error { + req := NewRequest(OperationCancelJobs, 1) + req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) + req.OperationAttributes[AttributePurgeJobs] = purge + + _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) + return err +} + +// RestartJob restarts a job +func (c *IPPClient) RestartJob(jobID int) error { + req := NewRequest(OperationRestartJob, 1) + req.OperationAttributes[AttributeJobURI] = c.getJobUri(jobID) + + _, err := c.SendRequest(c.adapter.GetHttpUri("jobs", ""), req, nil) + return err +} + +// HoldJobUntil holds a job +func (c *IPPClient) HoldJobUntil(jobID int, holdUntil string) error { + req := NewRequest(OperationRestartJob, 1) + req.OperationAttributes[AttributeJobURI] = c.getJobUri(jobID) + req.JobAttributes[AttributeHoldJobUntil] = holdUntil + + _, err := c.SendRequest(c.adapter.GetHttpUri("jobs", ""), req, nil) + return err +} + +// TestConnection tests if a tcp connection to the remote server is possible +func (c *IPPClient) TestConnection() error { + return c.adapter.TestConnection() +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/request.go b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/request.go new file mode 100644 index 0000000..3207038 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/request.go @@ -0,0 +1,299 @@ +package ipp + +import ( + "bytes" + "encoding/binary" + "io" +) + +// Request defines a ipp request +type Request struct { + ProtocolVersionMajor int8 + ProtocolVersionMinor int8 + + Operation int16 + RequestId int32 + + OperationAttributes map[string]any + JobAttributes map[string]any + PrinterAttributes map[string]any + SubscriptionAttributes map[string]any // Added for subscription operations + + File io.Reader + FileSize int +} + +// NewRequest creates a new ipp request +func NewRequest(op int16, reqID int32) *Request { + return &Request{ + ProtocolVersionMajor: ProtocolVersionMajor, + ProtocolVersionMinor: ProtocolVersionMinor, + Operation: op, + RequestId: reqID, + OperationAttributes: make(map[string]any), + JobAttributes: make(map[string]any), + PrinterAttributes: make(map[string]any), + SubscriptionAttributes: make(map[string]any), + File: nil, + FileSize: -1, + } +} + +// Encode encodes the request to a byte slice +func (r *Request) Encode() ([]byte, error) { + buf := new(bytes.Buffer) + enc := NewAttributeEncoder(buf) + + if err := binary.Write(buf, binary.BigEndian, r.ProtocolVersionMajor); err != nil { + return nil, err + } + + if err := binary.Write(buf, binary.BigEndian, r.ProtocolVersionMinor); err != nil { + return nil, err + } + + if err := binary.Write(buf, binary.BigEndian, r.Operation); err != nil { + return nil, err + } + + if err := binary.Write(buf, binary.BigEndian, r.RequestId); err != nil { + return nil, err + } + + if err := binary.Write(buf, binary.BigEndian, TagOperation); err != nil { + return nil, err + } + + if r.OperationAttributes == nil { + r.OperationAttributes = make(map[string]any, 2) + } + + if _, found := r.OperationAttributes[AttributeCharset]; !found { + r.OperationAttributes[AttributeCharset] = Charset + } + + if _, found := r.OperationAttributes[AttributeNaturalLanguage]; !found { + r.OperationAttributes[AttributeNaturalLanguage] = CharsetLanguage + } + + if err := r.encodeOperationAttributes(enc); err != nil { + return nil, err + } + + if len(r.JobAttributes) > 0 { + if err := binary.Write(buf, binary.BigEndian, TagJob); err != nil { + return nil, err + } + for attr, value := range r.JobAttributes { + if err := enc.Encode(attr, value); err != nil { + return nil, err + } + } + } + + if len(r.PrinterAttributes) > 0 { + if err := binary.Write(buf, binary.BigEndian, TagPrinter); err != nil { + return nil, err + } + for attr, value := range r.PrinterAttributes { + if err := enc.Encode(attr, value); err != nil { + return nil, err + } + } + } + + if len(r.SubscriptionAttributes) > 0 { + if err := binary.Write(buf, binary.BigEndian, TagSubscription); err != nil { + return nil, err + } + if err := r.encodeSubscriptionAttributes(enc); err != nil { + return nil, err + } + } + + if err := binary.Write(buf, binary.BigEndian, TagEnd); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (r *Request) encodeOperationAttributes(enc *AttributeEncoder) error { + ordered := []string{ + AttributeCharset, + AttributeNaturalLanguage, + AttributePrinterURI, + AttributeJobID, + } + + for _, attr := range ordered { + if value, ok := r.OperationAttributes[attr]; ok { + delete(r.OperationAttributes, attr) + if err := enc.Encode(attr, value); err != nil { + return err + } + } + } + + for attr, value := range r.OperationAttributes { + if err := enc.Encode(attr, value); err != nil { + return err + } + } + + return nil +} + +func (r *Request) encodeSubscriptionAttributes(enc *AttributeEncoder) error { + // Encode subscription attributes in proper order + // notify-pull-method and notify-lease-duration must come before notify-events + ordered := []string{ + "notify-pull-method", + "notify-lease-duration", + "notify-events", + } + + for _, attr := range ordered { + if value, ok := r.SubscriptionAttributes[attr]; ok { + delete(r.SubscriptionAttributes, attr) + if err := enc.Encode(attr, value); err != nil { + return err + } + } + } + + // Encode any remaining subscription attributes + for attr, value := range r.SubscriptionAttributes { + if err := enc.Encode(attr, value); err != nil { + return err + } + } + + return nil +} + +// RequestDecoder reads and decodes a request from a stream +type RequestDecoder struct { + reader io.Reader +} + +// NewRequestDecoder returns a new decoder that reads from r +func NewRequestDecoder(r io.Reader) *RequestDecoder { + return &RequestDecoder{ + reader: r, + } +} + +// Decode decodes a ipp request into a request struct. additional data will be written to an io.Writer if data is not nil +func (d *RequestDecoder) Decode(data io.Writer) (*Request, error) { + req := new(Request) + + if err := binary.Read(d.reader, binary.BigEndian, &req.ProtocolVersionMajor); err != nil { + return nil, err + } + + if err := binary.Read(d.reader, binary.BigEndian, &req.ProtocolVersionMinor); err != nil { + return nil, err + } + + if err := binary.Read(d.reader, binary.BigEndian, &req.Operation); err != nil { + return nil, err + } + + if err := binary.Read(d.reader, binary.BigEndian, &req.RequestId); err != nil { + return nil, err + } + + startByteSlice := make([]byte, 1) + + tag := TagCupsInvalid + previousAttributeName := "" + tagSet := false + + attribDecoder := NewAttributeDecoder(d.reader) + + // decode attribute buffer + for { + if _, err := d.reader.Read(startByteSlice); err != nil { + // when we read from a stream, we may get an EOF if we want to read the end tag + // all data should be read and we can ignore the error + if err == io.EOF { + break + } + return nil, err + } + + startByte := int8(startByteSlice[0]) + + // check if attributes are completed + if startByte == TagEnd { + break + } + + if startByte == TagOperation { + if req.OperationAttributes == nil { + req.OperationAttributes = make(map[string]any) + } + + tag = TagOperation + tagSet = true + + } + + if startByte == TagJob { + if req.JobAttributes == nil { + req.JobAttributes = make(map[string]any) + } + tag = TagJob + tagSet = true + } + + if startByte == TagPrinter { + if req.PrinterAttributes == nil { + req.PrinterAttributes = make(map[string]any) + } + tag = TagPrinter + tagSet = true + } + + if tagSet { + if _, err := d.reader.Read(startByteSlice); err != nil { + return nil, err + } + startByte = int8(startByteSlice[0]) + } + + attrib, err := attribDecoder.Decode(startByte) + if err != nil { + return nil, err + } + + if attrib.Name != "" { + appendAttributeToRequest(req, tag, attrib.Name, attrib.Value) + previousAttributeName = attrib.Name + } else { + appendAttributeToRequest(req, tag, previousAttributeName, attrib.Value) + } + + tagSet = false + } + + if data != nil { + if _, err := io.Copy(data, d.reader); err != nil { + return nil, err + } + } + + return req, nil +} + +func appendAttributeToRequest(req *Request, tag int8, name string, value any) { + switch tag { + case TagOperation: + req.OperationAttributes[name] = value + case TagPrinter: + req.PrinterAttributes[name] = value + case TagJob: + req.JobAttributes[name] = value + } +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/response.go b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/response.go new file mode 100644 index 0000000..e7cbd75 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/response.go @@ -0,0 +1,383 @@ +package ipp + +import ( + "bytes" + "encoding/binary" + "io" +) + +// Attributes is a wrapper for a set of attributes +type Attributes map[string][]Attribute + +// Response defines a ipp response +type Response struct { + ProtocolVersionMajor int8 + ProtocolVersionMinor int8 + + StatusCode int16 + RequestId int32 + + OperationAttributes Attributes + PrinterAttributes []Attributes + JobAttributes []Attributes + SubscriptionAttributes []Attributes // Added for subscription responses +} + +// CheckForErrors checks the status code and returns a error if it is not zero. it also returns the status message if provided by the server +func (r *Response) CheckForErrors() error { + if r.StatusCode != StatusOk { + err := IPPError{ + Status: r.StatusCode, + Message: "no status message returned", + } + + if len(r.OperationAttributes["status-message"]) > 0 { + err.Message = r.OperationAttributes["status-message"][0].Value.(string) + } + + return err + } + + return nil +} + +// NewResponse creates a new ipp response +func NewResponse(statusCode int16, reqID int32) *Response { + return &Response{ + ProtocolVersionMajor: ProtocolVersionMajor, + ProtocolVersionMinor: ProtocolVersionMinor, + StatusCode: statusCode, + RequestId: reqID, + OperationAttributes: make(Attributes), + PrinterAttributes: make([]Attributes, 0), + JobAttributes: make([]Attributes, 0), + } +} + +// Encode encodes the response to a byte slice +func (r *Response) Encode() ([]byte, error) { + buf := new(bytes.Buffer) + enc := NewAttributeEncoder(buf) + + if err := binary.Write(buf, binary.BigEndian, r.ProtocolVersionMajor); err != nil { + return nil, err + } + + if err := binary.Write(buf, binary.BigEndian, r.ProtocolVersionMinor); err != nil { + return nil, err + } + + if err := binary.Write(buf, binary.BigEndian, r.StatusCode); err != nil { + return nil, err + } + + if err := binary.Write(buf, binary.BigEndian, r.RequestId); err != nil { + return nil, err + } + + if err := binary.Write(buf, binary.BigEndian, TagOperation); err != nil { + return nil, err + } + + if r.OperationAttributes == nil { + r.OperationAttributes = make(Attributes, 0) + } + + if _, found := r.OperationAttributes[AttributeCharset]; !found { + r.OperationAttributes[AttributeCharset] = []Attribute{ + { + Value: Charset, + }, + } + } + + if _, found := r.OperationAttributes[AttributeNaturalLanguage]; !found { + r.OperationAttributes[AttributeNaturalLanguage] = []Attribute{ + { + Value: CharsetLanguage, + }, + } + } + + if err := r.encodeOperationAttributes(enc); err != nil { + return nil, err + } + + if len(r.PrinterAttributes) > 0 { + for _, printerAttr := range r.PrinterAttributes { + if err := binary.Write(buf, binary.BigEndian, TagPrinter); err != nil { + return nil, err + } + + for name, attr := range printerAttr { + if len(attr) == 0 { + continue + } + + values := make([]any, len(attr)) + for i, v := range attr { + values[i] = v.Value + } + + if len(values) == 1 { + if err := enc.Encode(name, values[0]); err != nil { + return nil, err + } + } else { + if err := enc.Encode(name, values); err != nil { + return nil, err + } + } + } + } + } + + if len(r.JobAttributes) > 0 { + for _, jobAttr := range r.JobAttributes { + if err := binary.Write(buf, binary.BigEndian, TagJob); err != nil { + return nil, err + } + + for name, attr := range jobAttr { + if len(attr) == 0 { + continue + } + + values := make([]any, len(attr)) + for i, v := range attr { + values[i] = v.Value + } + + if len(values) == 1 { + if err := enc.Encode(name, values[0]); err != nil { + return nil, err + } + } else { + if err := enc.Encode(name, values); err != nil { + return nil, err + } + } + } + } + } + + if err := binary.Write(buf, binary.BigEndian, TagEnd); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (r *Response) encodeOperationAttributes(enc *AttributeEncoder) error { + ordered := []string{ + AttributeCharset, + AttributeNaturalLanguage, + AttributePrinterURI, + AttributeJobID, + } + + for _, name := range ordered { + if attr, ok := r.OperationAttributes[name]; ok { + delete(r.OperationAttributes, name) + if err := encodeOperationAttribute(enc, name, attr); err != nil { + return err + } + } + } + + for name, attr := range r.OperationAttributes { + if err := encodeOperationAttribute(enc, name, attr); err != nil { + return err + } + } + + return nil +} + +func encodeOperationAttribute(enc *AttributeEncoder, name string, attr []Attribute) error { + if len(attr) == 0 { + return nil + } + + values := make([]any, len(attr)) + for i, v := range attr { + values[i] = v.Value + } + + if len(values) == 1 { + return enc.Encode(name, values[0]) + } + + return enc.Encode(name, values) +} + +// ResponseDecoder reads and decodes a response from a stream +type ResponseDecoder struct { + reader io.Reader +} + +// NewResponseDecoder returns a new decoder that reads from r +func NewResponseDecoder(r io.Reader) *ResponseDecoder { + return &ResponseDecoder{ + reader: r, + } +} + +// Decode decodes a ipp response into a response struct. additional data will be written to an io.Writer if data is not nil +func (d *ResponseDecoder) Decode(data io.Writer) (*Response, error) { + /* + 1 byte: Protocol Major Version - b + 1 byte: Protocol Minor Version - b + 2 byte: Status ID - h + 4 byte: Request ID - i + 1 byte: Operation Attribute Byte (\0x01) + N times: Attributes + 1 byte: Attribute End Byte (\0x03) + */ + + resp := new(Response) + + // wrap the reader so we have more functionality + // reader := bufio.NewReader(d.reader) + + if err := binary.Read(d.reader, binary.BigEndian, &resp.ProtocolVersionMajor); err != nil { + return nil, err + } + + if err := binary.Read(d.reader, binary.BigEndian, &resp.ProtocolVersionMinor); err != nil { + return nil, err + } + + if err := binary.Read(d.reader, binary.BigEndian, &resp.StatusCode); err != nil { + return nil, err + } + + if err := binary.Read(d.reader, binary.BigEndian, &resp.RequestId); err != nil { + return nil, err + } + + startByteSlice := make([]byte, 1) + + tag := TagCupsInvalid + previousAttributeName := "" + tempAttributes := make(Attributes) + tagSet := false + + attribDecoder := NewAttributeDecoder(d.reader) + + // decode attribute buffer + for { + if _, err := d.reader.Read(startByteSlice); err != nil { + // when we read from a stream, we may get an EOF if we want to read the end tag + // all data should be read and we can ignore the error + if err == io.EOF { + break + } + return nil, err + } + + startByte := int8(startByteSlice[0]) + + // check if attributes are completed + if startByte == TagEnd { + break + } + + if startByte == TagOperation { + if len(tempAttributes) > 0 && tag != TagCupsInvalid { + appendAttributeToResponse(resp, tag, tempAttributes) + tempAttributes = make(Attributes) + } + + tag = TagOperation + tagSet = true + } + + if startByte == TagJob { + if len(tempAttributes) > 0 && tag != TagCupsInvalid { + appendAttributeToResponse(resp, tag, tempAttributes) + tempAttributes = make(Attributes) + } + + tag = TagJob + tagSet = true + } + + if startByte == TagPrinter { + if len(tempAttributes) > 0 && tag != TagCupsInvalid { + appendAttributeToResponse(resp, tag, tempAttributes) + tempAttributes = make(Attributes) + } + + tag = TagPrinter + tagSet = true + } + + if startByte == TagSubscription { + if len(tempAttributes) > 0 && tag != TagCupsInvalid { + appendAttributeToResponse(resp, tag, tempAttributes) + tempAttributes = make(Attributes) + } + + tag = TagSubscription + tagSet = true + } + + if startByte == TagEventNotification { + if len(tempAttributes) > 0 && tag != TagCupsInvalid { + appendAttributeToResponse(resp, tag, tempAttributes) + tempAttributes = make(Attributes) + } + + tag = TagEventNotification + tagSet = true + } + + if tagSet { + if _, err := d.reader.Read(startByteSlice); err != nil { + return nil, err + } + startByte = int8(startByteSlice[0]) + } + + attrib, err := attribDecoder.Decode(startByte) + if err != nil { + return nil, err + } + + if attrib.Name != "" { + tempAttributes[attrib.Name] = append(tempAttributes[attrib.Name], *attrib) + previousAttributeName = attrib.Name + } else { + tempAttributes[previousAttributeName] = append(tempAttributes[previousAttributeName], *attrib) + } + + tagSet = false + } + + if len(tempAttributes) > 0 && tag != TagCupsInvalid { + appendAttributeToResponse(resp, tag, tempAttributes) + } + + if data != nil { + if _, err := io.Copy(data, d.reader); err != nil { + return nil, err + } + } + + return resp, nil +} + +func appendAttributeToResponse(resp *Response, tag int8, attr map[string][]Attribute) { + switch tag { + case TagOperation: + resp.OperationAttributes = attr + case TagPrinter: + resp.PrinterAttributes = append(resp.PrinterAttributes, attr) + case TagJob: + resp.JobAttributes = append(resp.JobAttributes, attr) + case TagSubscription, TagEventNotification: + // Both subscription and event notification attributes go to SubscriptionAttributes + resp.SubscriptionAttributes = append(resp.SubscriptionAttributes, attr) + } +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/utils.go b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/utils.go new file mode 100644 index 0000000..62689a3 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/utils.go @@ -0,0 +1,28 @@ +package ipp + +import ( + "fmt" + "os" + "path" +) + +// ParseControlFile reads and decodes a cups control file into a response +func ParseControlFile(jobID int, spoolDirectory string) (*Response, error) { + if spoolDirectory == "" { + spoolDirectory = "/var/spool/cups" + } + + controlFilePath := path.Join(spoolDirectory, fmt.Sprintf("c%d", jobID)) + + if _, err := os.Stat(controlFilePath); err != nil { + return nil, err + } + + controlFile, err := os.Open(controlFilePath) + if err != nil { + return nil, err + } + defer controlFile.Close() + + return NewResponseDecoder(controlFile).Decode(nil) +} |