summaryrefslogtreecommitdiff
path: root/raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp
diff options
context:
space:
mode:
authornippy <you@example.com>2026-04-18 13:49:56 +0200
committernippy <you@example.com>2026-04-18 13:49:56 +0200
commit5d94c0a7d44a2255b81815a52a7056a94a39842d (patch)
tree759bdea9645c6a62f9e1e4c001f7d81cccd120d2 /raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp
parente79cdf210b267f21a186255ce1a4d50029439d54 (diff)
downloadRaveOS-PKGBUILD-5d94c0a7d44a2255b81815a52a7056a94a39842d.tar.gz
RaveOS-PKGBUILD-5d94c0a7d44a2255b81815a52a7056a94a39842d.zip
update Raveos themes
Diffstat (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp')
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/CREDITS.MD5
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/LICENSE201
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/adapter-http.go132
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/adapter.go9
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/attribute.go528
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/constants.go450
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/cups-client.go313
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/error.go31
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/ipp-client.go329
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/request.go299
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/response.go383
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/core/pkg/ipp/utils.go28
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)
+}