1package kitty
2
3import (
4 "encoding"
5 "fmt"
6 "strconv"
7 "strings"
8)
9
10var (
11 _ encoding.TextMarshaler = Options{}
12 _ encoding.TextUnmarshaler = &Options{}
13)
14
15// Options represents a Kitty Graphics Protocol options.
16type Options struct {
17 // Common options.
18
19 // Action (a=t) is the action to be performed on the image. Can be one of
20 // [Transmit], [TransmitDisplay], [Query], [Put], [Delete], [Frame],
21 // [Animate], [Compose].
22 Action byte
23
24 // Quite mode (q=0) is the quiet mode. Can be either zero, one, or two
25 // where zero is the default, 1 suppresses OK responses, and 2 suppresses
26 // both OK and error responses.
27 Quite byte
28
29 // Transmission options.
30
31 // ID (i=) is the image ID. The ID is a unique identifier for the image.
32 // Must be a positive integer up to [math.MaxUint32].
33 ID int
34
35 // PlacementID (p=) is the placement ID. The placement ID is a unique
36 // identifier for the placement of the image. Must be a positive integer up
37 // to [math.MaxUint32].
38 PlacementID int
39
40 // Number (I=0) is the number of images to be transmitted.
41 Number int
42
43 // Format (f=32) is the image format. One of [RGBA], [RGB], [PNG].
44 Format int
45
46 // ImageWidth (s=0) is the transmitted image width.
47 ImageWidth int
48
49 // ImageHeight (v=0) is the transmitted image height.
50 ImageHeight int
51
52 // Compression (o=) is the image compression type. Can be [Zlib] or zero.
53 Compression byte
54
55 // Transmission (t=d) is the image transmission type. Can be [Direct], [File],
56 // [TempFile], or[SharedMemory].
57 Transmission byte
58
59 // File is the file path to be used when the transmission type is [File].
60 // If [Options.Transmission] is omitted i.e. zero and this is non-empty,
61 // the transmission type is set to [File].
62 File string
63
64 // Size (S=0) is the size to be read from the transmission medium.
65 Size int
66
67 // Offset (O=0) is the offset byte to start reading from the transmission
68 // medium.
69 Offset int
70
71 // Chunk (m=) whether the image is transmitted in chunks. Can be either
72 // zero or one. When true, the image is transmitted in chunks. Each chunk
73 // must be a multiple of 4, and up to [MaxChunkSize] bytes. Each chunk must
74 // have the m=1 option except for the last chunk which must have m=0.
75 Chunk bool
76
77 // Display options.
78
79 // X (x=0) is the pixel X coordinate of the image to start displaying.
80 X int
81
82 // Y (y=0) is the pixel Y coordinate of the image to start displaying.
83 Y int
84
85 // Z (z=0) is the Z coordinate of the image to display.
86 Z int
87
88 // Width (w=0) is the width of the image to display.
89 Width int
90
91 // Height (h=0) is the height of the image to display.
92 Height int
93
94 // OffsetX (X=0) is the OffsetX coordinate of the cursor cell to start
95 // displaying the image. OffsetX=0 is the leftmost cell. This must be
96 // smaller than the terminal cell width.
97 OffsetX int
98
99 // OffsetY (Y=0) is the OffsetY coordinate of the cursor cell to start
100 // displaying the image. OffsetY=0 is the topmost cell. This must be
101 // smaller than the terminal cell height.
102 OffsetY int
103
104 // Columns (c=0) is the number of columns to display the image. The image
105 // will be scaled to fit the number of columns.
106 Columns int
107
108 // Rows (r=0) is the number of rows to display the image. The image will be
109 // scaled to fit the number of rows.
110 Rows int
111
112 // VirtualPlacement (U=0) whether to use virtual placement. This is used
113 // with Unicode [Placeholder] to display images.
114 VirtualPlacement bool
115
116 // DoNotMoveCursor (C=0) whether to move the cursor after displaying the
117 // image.
118 DoNotMoveCursor bool
119
120 // ParentID (P=0) is the parent image ID. The parent ID is the ID of the
121 // image that is the parent of the current image. This is used with Unicode
122 // [Placeholder] to display images relative to the parent image.
123 ParentID int
124
125 // ParentPlacementID (Q=0) is the parent placement ID. The parent placement
126 // ID is the ID of the placement of the parent image. This is used with
127 // Unicode [Placeholder] to display images relative to the parent image.
128 ParentPlacementID int
129
130 // Delete options.
131
132 // Delete (d=a) is the delete action. Can be one of [DeleteAll],
133 // [DeleteID], [DeleteNumber], [DeleteCursor], [DeleteFrames],
134 // [DeleteCell], [DeleteCellZ], [DeleteRange], [DeleteColumn], [DeleteRow],
135 // [DeleteZ].
136 Delete byte
137
138 // DeleteResources indicates whether to delete the resources associated
139 // with the image.
140 DeleteResources bool
141}
142
143// Options returns the options as a slice of a key-value pairs.
144func (o *Options) Options() (opts []string) {
145 opts = []string{}
146 if o.Format == 0 {
147 o.Format = RGBA
148 }
149
150 if o.Action == 0 {
151 o.Action = Transmit
152 }
153
154 if o.Delete == 0 {
155 o.Delete = DeleteAll
156 }
157
158 if o.Transmission == 0 {
159 if len(o.File) > 0 {
160 o.Transmission = File
161 } else {
162 o.Transmission = Direct
163 }
164 }
165
166 if o.Format != RGBA {
167 opts = append(opts, fmt.Sprintf("f=%d", o.Format))
168 }
169
170 if o.Quite > 0 {
171 opts = append(opts, fmt.Sprintf("q=%d", o.Quite))
172 }
173
174 if o.ID > 0 {
175 opts = append(opts, fmt.Sprintf("i=%d", o.ID))
176 }
177
178 if o.PlacementID > 0 {
179 opts = append(opts, fmt.Sprintf("p=%d", o.PlacementID))
180 }
181
182 if o.Number > 0 {
183 opts = append(opts, fmt.Sprintf("I=%d", o.Number))
184 }
185
186 if o.ImageWidth > 0 {
187 opts = append(opts, fmt.Sprintf("s=%d", o.ImageWidth))
188 }
189
190 if o.ImageHeight > 0 {
191 opts = append(opts, fmt.Sprintf("v=%d", o.ImageHeight))
192 }
193
194 if o.Transmission != Direct {
195 opts = append(opts, fmt.Sprintf("t=%c", o.Transmission))
196 }
197
198 if o.Size > 0 {
199 opts = append(opts, fmt.Sprintf("S=%d", o.Size))
200 }
201
202 if o.Offset > 0 {
203 opts = append(opts, fmt.Sprintf("O=%d", o.Offset))
204 }
205
206 if o.Compression == Zlib {
207 opts = append(opts, fmt.Sprintf("o=%c", o.Compression))
208 }
209
210 if o.VirtualPlacement {
211 opts = append(opts, "U=1")
212 }
213
214 if o.DoNotMoveCursor {
215 opts = append(opts, "C=1")
216 }
217
218 if o.ParentID > 0 {
219 opts = append(opts, fmt.Sprintf("P=%d", o.ParentID))
220 }
221
222 if o.ParentPlacementID > 0 {
223 opts = append(opts, fmt.Sprintf("Q=%d", o.ParentPlacementID))
224 }
225
226 if o.X > 0 {
227 opts = append(opts, fmt.Sprintf("x=%d", o.X))
228 }
229
230 if o.Y > 0 {
231 opts = append(opts, fmt.Sprintf("y=%d", o.Y))
232 }
233
234 if o.Z > 0 {
235 opts = append(opts, fmt.Sprintf("z=%d", o.Z))
236 }
237
238 if o.Width > 0 {
239 opts = append(opts, fmt.Sprintf("w=%d", o.Width))
240 }
241
242 if o.Height > 0 {
243 opts = append(opts, fmt.Sprintf("h=%d", o.Height))
244 }
245
246 if o.OffsetX > 0 {
247 opts = append(opts, fmt.Sprintf("X=%d", o.OffsetX))
248 }
249
250 if o.OffsetY > 0 {
251 opts = append(opts, fmt.Sprintf("Y=%d", o.OffsetY))
252 }
253
254 if o.Columns > 0 {
255 opts = append(opts, fmt.Sprintf("c=%d", o.Columns))
256 }
257
258 if o.Rows > 0 {
259 opts = append(opts, fmt.Sprintf("r=%d", o.Rows))
260 }
261
262 if o.Delete != DeleteAll || o.DeleteResources {
263 da := o.Delete
264 if o.DeleteResources {
265 da = da - ' ' // to uppercase
266 }
267
268 opts = append(opts, fmt.Sprintf("d=%c", da))
269 }
270
271 if o.Action != Transmit {
272 opts = append(opts, fmt.Sprintf("a=%c", o.Action))
273 }
274
275 return
276}
277
278// String returns the string representation of the options.
279func (o Options) String() string {
280 return strings.Join(o.Options(), ",")
281}
282
283// MarshalText returns the string representation of the options.
284func (o Options) MarshalText() ([]byte, error) {
285 return []byte(o.String()), nil
286}
287
288// UnmarshalText parses the options from the given string.
289func (o *Options) UnmarshalText(text []byte) error {
290 opts := strings.Split(string(text), ",")
291 for _, opt := range opts {
292 ps := strings.SplitN(opt, "=", 2)
293 if len(ps) != 2 || len(ps[1]) == 0 {
294 continue
295 }
296
297 switch ps[0] {
298 case "a":
299 o.Action = ps[1][0]
300 case "o":
301 o.Compression = ps[1][0]
302 case "t":
303 o.Transmission = ps[1][0]
304 case "d":
305 d := ps[1][0]
306 if d >= 'A' && d <= 'Z' {
307 o.DeleteResources = true
308 d = d + ' ' // to lowercase
309 }
310 o.Delete = d
311 case "i", "q", "p", "I", "f", "s", "v", "S", "O", "m", "x", "y", "z", "w", "h", "X", "Y", "c", "r", "U", "P", "Q":
312 v, err := strconv.Atoi(ps[1])
313 if err != nil {
314 continue
315 }
316
317 switch ps[0] {
318 case "i":
319 o.ID = v
320 case "q":
321 o.Quite = byte(v)
322 case "p":
323 o.PlacementID = v
324 case "I":
325 o.Number = v
326 case "f":
327 o.Format = v
328 case "s":
329 o.ImageWidth = v
330 case "v":
331 o.ImageHeight = v
332 case "S":
333 o.Size = v
334 case "O":
335 o.Offset = v
336 case "m":
337 o.Chunk = v == 0 || v == 1
338 case "x":
339 o.X = v
340 case "y":
341 o.Y = v
342 case "z":
343 o.Z = v
344 case "w":
345 o.Width = v
346 case "h":
347 o.Height = v
348 case "X":
349 o.OffsetX = v
350 case "Y":
351 o.OffsetY = v
352 case "c":
353 o.Columns = v
354 case "r":
355 o.Rows = v
356 case "U":
357 o.VirtualPlacement = v == 1
358 case "P":
359 o.ParentID = v
360 case "Q":
361 o.ParentPlacementID = v
362 }
363 }
364 }
365
366 return nil
367}