forked from NiLuJe/FBInk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
fbink_cmd.c
2973 lines (2862 loc) · 113 KB
/
fbink_cmd.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
FBInk: FrameBuffer eInker, a library to print text & images to an eInk Linux framebuffer
Copyright (C) 2018-2024 NiLuJe <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
----
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifdef FBINK_MINIMAL
# ifndef FBINK_WITH_BITMAP
# error Cannot build this tool without fixed-cell font rendering support!
# endif
#endif
#include "fbink_cmd.h"
static const char*
has_feature(const uint32_t mask, uint32_t flag)
{
if (mask & flag) {
return "Yes";
} else {
return "No";
}
}
// Help message
static void
show_helpmsg(void)
{
printf("\nFBInk %s", fbink_version());
// Display the target platform
const FBINK_TARGET_T platform = fbink_target();
switch (platform) {
case FBINK_TARGET_LINUX:
printf(" for Linux");
break;
case FBINK_TARGET_KOBO:
printf(" for Kobo");
break;
case FBINK_TARGET_KINDLE:
printf(" for Kindle");
break;
case FBINK_TARGET_KINDLE_LEGACY:
printf(" for Legacy Kindle");
break;
case FBINK_TARGET_CERVANTES:
printf(" for Cervantes");
break;
case FBINK_TARGET_REMARKABLE:
printf(" for reMarkable");
break;
case FBINK_TARGET_POCKETBOOK:
printf(" for PocketBook");
break;
}
// Recap the build features
const uint32_t features = fbink_features();
printf(" [Draw=%s, Bitmap=%s, Fonts=%s, Unifont=%s, OpenType=%s, Image=%s, ButtonScan=%s]",
has_feature(features, FBINK_FEATURE_DRAW),
has_feature(features, FBINK_FEATURE_BITMAP),
has_feature(features, FBINK_FEATURE_FONTS),
has_feature(features, FBINK_FEATURE_UNIFONT),
has_feature(features, FBINK_FEATURE_OPENTYPE),
has_feature(features, FBINK_FEATURE_IMAGE),
has_feature(features, FBINK_FEATURE_BUTTON_SCAN));
#ifdef DEBUG
printf(" [DEBUG]");
#endif
printf(
"\n"
"\n"
"Usage: fbink [OPTIONS] [STRING ...]\n"
"\n"
"Print STRING on your device's screen.\n"
"\n"
"EXAMPLES:\n"
"\tfbink -x 1 -y 10 \"Hello World!\"\n"
"\t\tPrints 'Hello World!' on the eleventh line, starting at the second column from the left.\n"
"\tfbink -pmh -y -5 \"Hello World!\"\n"
"\t\tPrints 'Hello World!', highlighted (i.e., white on black with the default colors), centered & padded on both sides, on the fifth line starting from the bottom.\n"
"\tfbink -pmM -y -8 \"Hello World!\"\n"
"\t\tPrints 'Hello World!', centered & padded on both sides, eight lines above the center of the screen.\n"
"\n"
"Options affecting the message's position on screen:\n"
"\t-x, --col NUM\t\tBegin printing STRING @ column NUM (Default: 0).\n"
"\t\t\t\tYou might consider beginning at column 1 instead of 0, as column 0 (the leftmost one) may sometimes be slightly obscured by a bezel.\n"
"\t\t\t\tUse a negative value to count back from the right edge of the screen.\n"
"\t-y, --row NUM\t\tBegin printing STRING @ row NUM (Default: 0).\n"
"\t\t\t\tYou might consider beginning at row 1 instead of 0, as row 0 (the topmost one) may sometimes be slightly obscured by a bezel, especially on Kobos.\n"
"\t\t\t\tUse a negative value to count back from the bottom of the screen.\n"
"\t-X, --hoffset NUM\tAdjust final text position by NUM pixels on the horizontal axis (Default: 0).\n"
"\t\t\t\tHonors negative values, and will let you push stuff off-screen, often without warning.\n"
"\t-Y, --voffset NUM\tAdjust final text position by NUM pixels on the vertical axis (Default: 0).\n"
"\t\t\t\tHonors negative values, and will let you push stuff off-screen, often without warning.\n"
"\t-m, --centered\t\tDynamically override col to print STRING at the center of the screen.\n"
"\t\t\t\tSpecial care is taken to avoid the very edges of the screen, to ensure the complete legibility of the message.\n"
"\t-M, --halfway\t\tDynamically adjust row to print STRING in the middle of the screen.\n"
"\t\t\t\tThe value specified in row then becomes an offset, starting from the middle of the screen.\n"
"\t-p, --padded\t\tLeft pad STRING with blank spaces.\n"
"\t\t\t\tMost useful when combined with --centered to ensure a line will be completely filled, while still centering STRING,\n"
"\t\t\t\ti.e., padding it on both sides.\n"
"\t-r, --rpadded\t\tRight pad STRING with blank spaces.\n"
"\n"
"Options affecting the message's appearance:\n"
"\t-h, --invert\t\tPrint STRING in <background color> over <foreground color> instead of the reverse.\n"
"\t-f, --flash\t\tAsk the eInk driver to do a black flash when refreshing the area of the screen where STRING will be printed.\n"
#ifdef FBINK_FOR_KINDLE
"\t\t\t\tNote that on legacy einkfb devices, this may not always be honored by the hardware.\n"
#endif
"\t-c, --clear\t\tClear the full screen before printing.\n"
"\t\t\t\tHonors -B, --background; -h, --invert; -H, --nightmode; -W, --waveform; -D, --dither; -b, --norefresh; -w, --wait.\n"
"\t\t\t\tCan be specified on its own, without any STRING.\n"
"\t\t\t\tNOTE: If your intent is to simply clear the screen and *nothing else*, use -k, --cls instead!\n"
#ifndef FBINK_FOR_LINUX
"\t-W, --waveform MODE\tRequest a specific waveform update mode from the eInk controller, if supported (mainly useful for images).\n"
"\t\t\t\tAvailable waveform modes: A2, DU, GL16, GC16 & AUTO\n"
# if defined(FBINK_FOR_KINDLE)
"\t\t\t\tAs well as REAGL, REAGLD, GC16_FAST, GL16_FAST, DU4, GL4, GL16_INV, GCK16, GLKW16 & DUNM on some Kindles, depending on the model & FW version.\n"
"\t\t\t\tNote that specifying a waveform mode is ignored on legacy einkfb devices, because the hardware doesn't expose such capabilities.\n"
# elif defined(FBINK_FOR_POCKETBOOK)
"\t\t\t\tAs well as GC4, A2IN, A2OUT, DU4, REAGL, REAGLD, GC16HQ & GS16.\n"
# elif defined(FBINK_FOR_KOBO)
"\t\t\t\tAs well as GC4, REAGL & REAGLD. And GU16, GCK16, GLK16 & GCC16 on Mk. 8. And DU4, GCK16 & GLKW16 on Mk. 9.\n"
# elif defined(FBINK_FOR_CERVANTES)
"\t\t\t\tAs well as GC4, REAGL & REAGLD.\n"
# endif
# if !defined(FBINK_FOR_REMARKABLE)
"\t\t\t\tUnsupported modes *should* safely downgrade to AUTO. Operative word being 'should' ;).\n"
# if defined(FBINK_FOR_POCKETBOOK)
"\t\t\t\tOn devices with a B288 SoC, AUTO is *not* supported. FBInk will silently use GC16 instead!\n"
# endif
"\t\t\t\tOn some devices, REAGL & REAGLD expect to be flashing in order to behave properly.\n"
# endif
"\t-D, --dither\t\tRequest a specific hardware dithering mode from the eInk controller, if supported (mainly useful for images).\n"
"\t\t\t\tAvailable dithering modes: PASSTHROUGH, FLOYD_STEINBERG, ATKINSON, ORDERED, QUANT_ONLY & LEGACY\n"
"\t\t\t\tNote that this is only supported on recent devices, and that only a subset of these options may actually be supported by the HW (usually, PASSTHROUGH & ORDERED, check dmesg).\n"
"\t\t\t\tLEGACY may be supported on more devices, but what exactly it does in practice (and how well it works) depends on the exact device and/or FW version.\n"
# ifdef FBINK_FOR_KINDLE
"\t\t\t\tTrue (i.e., not LEGACY) hardware dithering is completely untested on Kindle, and, while the Oasis 2, PaperWhite 4 & Oasis 3 *should* support it, they *may* not, or at least not in the way FBInk expects...\n"
# endif
"\t-H, --nightmode\t\tRequest full hardware inversion from the eInk controller, if supported.\n"
"\t\t\t\tNote that this can be used *in combination* with -h, --invert! One does not exclude the other, which may lead to some confusing behavior ;).\n"
# ifdef FBINK_FOR_KINDLE
"\t\t\t\tNote that requesting nightmode is ignored on legacy einkfb devices, because the hardware doesn't (easily) expose such capabilities.\n"
# endif
"\t\t\t\tNote that this may be ignored on some specific devices where it is known to be or have been unstable at some point.\n"
# ifdef FBINK_FOR_KINDLE
"\t-K, --animate direction=DIR,steps=NUM\n"
"\t\t\t\t\t\tRequest animated refreshes from the driver, if supported.\n"
"\t\t\t\tAvailable directions: DOWN, UP, LEFT & RIGHT\n"
"\t\t\t\tThe highest step count currently supported is 60.\n"
"\t\t\t\tThis is only supported on devices running on a MTK SoC.\n"
# endif
"\t-b, --norefresh\t\tOnly update the framebuffer, but don't actually refresh the eInk screen (useful when drawing in batch).\n"
# if defined(FBINK_FOR_KOBO)
"\t\t\t\tNOTE: Due to the way buffers are handled on that platform, this will not behave as expected on sunxi SoCs!\n"
# endif
"\t-w, --wait\t\tBlock until the kernel has finished processing the *last* update we sent, if any.\n"
"\t\t\t\tThe actual delay depends for the most part on the waveform mode that was used.\n"
"\t\t\t\tSee the API documentation around fbink_wait_for_submission & fbink_wait_for_complete for more details.\n"
"\t\t\t\tAs a point of reference, eips only does a wait_for_complete after the flashing refresh of an image.\n"
"\t\t\t\tWe used to do that by default for *all* flashing updates until FBInk 1.20.0.\n"
#endif //!FBINK_FOR_LINUX
"\t-S, --size\t\tOverride the automatic font scaling multiplier (Default: 0, automatic selection, ranging from 1 (no scaling), to 4 (4x upscaling), depending on screen resolution).\n"
#ifdef FBINK_WITH_FONTS
"\t\t\t\tNote that user-supplied values will be clamped to safe boundaries (from 1 to around 45 for most fonts, and from 1 to around 30 for TALL).\n"
#else
"\t\t\t\tNote that user-supplied values will be clamped to safe boundaries (from 1 to around 45).\n"
#endif
"\t\t\t\tThe exact upper value depends on the resolution of your screen.\n"
"\t-F, --font NAME\t\tRender glyphs from builtin font NAME (Default: IBM).\n"
#ifdef FBINK_WITH_FONTS
"\t\t\t\tAvailable font families: IBM, UNSCII, ALT, THIN, FANTASY, MCR, TALL, BLOCK,\n"
"\t\t\t\t\t\tLEGGIE, VEGGIE, KATES, FKP, CTRLD, ORP, ORPB, ORPI, SCIENTIFICA, SCIENTIFICAB, SCIENTIFICAI, TERMINUS, TERMINUSB, FATTY, SPLEEN, TEWI, TEWIB, TOPAZ, MICROKNIGHT, VGA, COZETTE\n"
# ifdef FBINK_WITH_UNIFONT
"\t\t\t\t\t\tAs well as UNIFONT & UNIFONTDW\n"
# endif
"\t\t\t\tNOTE: On low dpi, 600x800 devices, ORP or TEWI's form factor may feel more familiar at default scaling.\n"
#else
"\t\t\t\tAvailable font families: IBM\n"
#endif
#ifdef FBINK_WITH_OPENTYPE
"\t\t\t\tNOTE: If you're looking for vector font rendering, see the OpenType section a few lines down!\n"
#endif
"\t-C, --color NAME\tColor the text will be printed in (Default: BLACK).\n"
"\t-B, --background NAME\tColor of the background the text will be printed on (Default: WHITE).\n"
"\t\t\t\tAvailable colors: BLACK, GRAY1, GRAY2, GRAY3, GRAY4, GRAY5, GRAY6, GRAY7,\n"
"\t\t\t\t\t\tGRAY8, GRAY9, GRAYA, GRAYB, GRAYC, GRAYD, GRAYE, WHITE\n"
"\t-o, --overlay\t\tDon't draw background pixels, and compute foreground pixel color based on the inverse of the underlying framebufer pixel.\n"
"\t\t\t\tObviously ignores -h, --invert & -C, --color *as far as glyphs are concerned*.\n"
"\t\t\t\t-B, --background is still honored if you combine this with -c, --clear\n"
"\t-O, --bgless\t\tDon't draw background pixels.\n"
"\t\t\t\tObviously mutually exclusive with -o, --overlay, because it's simply a subset of what overlay does. If both are enabled, -o, --overlay takes precedence.\n"
"\t-T, --fgless\t\tDon't draw foreground pixels.\n"
"\t\t\t\tMutually exclusive with -o, --overlay or -O, --bgless, and takes precedence over them.\n"
#if defined(FBINK_FOR_KOBO)
"\n"
"\t\t\t\tNOTE: Due to the way buffers are handled on that platform, -o, --overlay; -O, --bgless and -T, --fgless will not behave as expected on sunxi SoCs!\n"
#endif
"\n"
"Options affecting the program's verbosity:\n"
"\t-v, --verbose\tToggle printing diagnostic messages.\n"
"\t-q, --quiet\tToggle hiding hardware setup messages.\n"
"\t-G, --syslog\tSend output to syslog instead of stdout & stderr.\n"
"\t\t\tOught to be the first flag passed, otherwise, some commandline parsing errors might not honor it.\n"
"\n"
"Options affecting the program's behavior:\n"
"\t-I, --interactive\tEnter a very basic interactive mode.\n"
"\t-L, --linecountcode\tWhen successfully printing text, returns the total amount of printed lines as the process exit code.\n"
"\t\t\t\tNOTE: Will be inaccurate if there are more than 255 rows on screen!\n"
"\t-l, --linecount\t\tWhen successfully printing text, outputs the total amount of printed lines in the final line of output to stdout (NOTE: enforces quiet & non-verbose!).\n"
"\t\t\t\tNOTE: With OT/TTF rendering, will output a top margin value to use as-is instead (or 0 if there's no space left on screen)!\n"
"\t\t\t\t The OT/TTF codepath also returns more data, including the results of the line-breaking computations, so it's in an eval-friendly format instead.\n"
"\t-E, --coordinates\tWhen printing something, outputs the coordinates & dimensions of what was printed to stdout, in a format easily consumable by eval (NOTE: enforces quiet & non-verbose!).\n"
"\t\t\t\tNOTE: For both -l, --linecount & -E, --coordinates, output will only be sent to stdout on *success*. On error, the usual error message is sent to stderr.\n"
"\t\t\t\t Given that, you may want to store stdout only in a variable and check the return code for success before running eval on that var!\n"
"\t-P, --progressbar NUM\tDraw a NUM%% full progress bar (full-width). Like other alternative modes, does *NOT* have precedence over text printing.\n"
"\t\t\t\tIgnores -o, --overlay; -x, --col; -X, --hoffset; as well as -m, --centered & -p, --padded\n"
"\t-A, --activitybar NUM\tDraw an activity bar on step NUM (full-width). NUM must be between 0 and 16. Like other alternative modes, does *NOT* have precedence over text printing.\n"
"\t\t\t\tNOTE: If NUM is negative, will cycle between each possible value every 750ms, until the death of the sun! Be careful not to be caught in an involuntary infinite loop!\n"
"\t\t\t\tIgnores -x, --col; -X, --hoffset; as well as -m, --centered & -p, --padded\n"
"\t-V, --noviewport\tIgnore any & all viewport corrections, be it from Kobo devices with rows of pixels hidden by a bezel, or a dynamic offset applied to rows when vertical fit isn't perfect.\n"
"\n"
"NOTES:\n"
"\tYou can specify multiple STRINGs in a single invocation of fbink, each consecutive one will be printed on the subsequent line.\n"
"\t\tAlthough it's worth mentioning that this will lead to undesirable results when combined with --clear,\n"
"\t\tbecause the screen is cleared before each STRING, meaning you'll only get to see the final one.\n"
"\tIf you want to properly print a long string, better do it in a single argument, FBInk will do its best to spread it over multiple lines sanely.\n"
"\tIt will also honor the linefeed character (and I do mean the actual control character, not the human-readable escape sequence),\n"
"\twhich comes in handy when passing a few lines of logs straight from tail as an argument.\n"
#ifdef FBINK_WITH_OPENTYPE
"\n"
"\n"
"\n"
"OpenType & TrueType font support:\n"
"\t-t, --truetype regular=FILE,bold=FILE,italic=FILE,bolditalic=FILE,size=NUM,px=NUM,top=NUM,bottom=NUM,left=NUM,right=NUM,padding=PAD,style=STYLE,format,notrunc,compute\n"
"\t\tregular, bold, italic & bolditalic should point to the font file matching their respective font style. At least one of them MUST be specified.\n"
"\t\tsize sets the rendering size, in points. Defaults to 12pt if unset. Can be a decimal value.\n"
"\t\tpx sets the rendering size, in pixels. Optional. Takes precedence over size if specified.\n"
"\t\ttop, bottom, left & right set the margins used to define the display area. Defaults to 0, i.e., the full screen, starting at the top-left corner.\n"
"\t\t\tNOTE: If a negative value is supplied, counts backward from the opposite edge. Mostly useful with top & left to position stuff relative to the bottom right corner.\n"
"\t\tpadding can optionally be set to ensure the drawing area on both sides of the printed content is padded with the background color on one or both axis.\n"
"\t\t\tAvailable padding axis: HORIZONTAL, VERTICAL, or BOTH (Defaults to NONE). Useful to avoid overlaps on consecutive prints at the same coordinates.\n"
"\t\tIf style is specified, it dictates the default font style to use (e.g., REGULAR, BOLD, ITALIC or BOLD_ITALIC). Defaults to REGULAR.\n"
"\t\tIf format is specified, instead of the default style, the underscore/star Markdown syntax will be honored to set the font style (i.e., *italic*, **bold** & ***bold italic***).\n"
"\t\tIf notrunc is specified, truncation will be considered a failure.\n"
"\t\tNOTE: This may not prevent drawing/refreshing the screen if the truncation couldn't be predicted at compute time!\n"
"\t\t On the CLI, this will prevent you from making use of the returned computation info, as this will chain a CLI abort.\n"
"\t\tIf compute is specified, no rendering will be done, and only the line-breaking computation pass will run. You'll generally want to use that combined with -l, --linecount.\n"
"\n"
"\t\tHonors -h, --invert; -f, --flash; -c, --clear; -W, --waveform; -D, --dither; -H, --nightmode; -b, --norefresh; -m, --centered; -M, --halfway; -o, --overlay; -T, --fgless; -O, --bgless; -C, --color; -B, --background; -l, --linecount\n"
"\n"
"EXAMPLES:\n"
# ifdef FBINK_FOR_KINDLE
"\tfbink -t regular=/usr/java/lib/fonts/Caecilia_LT_65_Medium.ttf,bold=/usr/java/lib/fonts/Caecilia_LT_75_Bold.ttf,size=24,top=100,bottom=500,left=25,right=50,format \"Hello **world**!\"\n"
"\t\tWill use Caecilia to print 'Hello world!' at 24pt in a display area starting from 100px down the top of the screen to 500px before the bottom of the screen,\n"
# else
"\tfbink -t regular=/mnt/onboard/fonts/NotoSans-Regular.ttf,bold=/mnt/onboard/fonts/NotoSans-Bold.ttf,size=24,top=100,bottom=500,left=25,right=50,format \"Hello **world**!\"\n"
"\t\tWill use NotoSans to print 'Hello world!' at 24pt in a display area starting from 100px down the top of the screen to 500px before the bottom of the screen,\n"
# endif
"\t\tfrom 25px of the left edge of the screen until 50px before the right edge.\n"
"\t\tHonoring the Markdown syntax, 'Hello' will be printed with the Regular font style, while 'world' will use the Bold font style.\n"
"\t\tNOTE: You will NOT be able to use obfuscated or encrypted fonts.\n"
# ifdef FBINK_FOR_KOBO
"\t\tPlease note that this means you will NOT be able to use system fonts, because they're all obfuscated.\n"
# else
"\t\tAlso note that the path shown here is fairly arbitrary, and Kobo-specific ;).\n"
# endif
"\n"
#endif
"\n"
"\n"
"\n"
"\n"
"You can also eschew printing a STRING, and simply refresh the screen as per your specification, without touching the framebuffer:\n"
"\t-s, --refresh [top=NUM,left=NUM,width=NUM,height=NUM]\n"
"\n"
"EXAMPLES:\n"
"\tfbink -s top=20,left=10,width=500,height=600 -W GC16 -D ORDERED\n"
"\t\tRefreshes a 500x600 rectangle with its top-left corner at coordinates (10, 20) with a GC16 waveform mode and ORDERED hardware dithering.\n"
"\n"
"NOTES:\n"
"\tThe specified rectangle *must* completely fit on screen, or the ioctl will fail.\n"
"\tNote that this will also honor -W, --waveform; -H, --nightmode & -f, --flash\n"
#if defined(FBINK_FOR_KOBO) || defined(FBINK_FOR_CERVANTES)
"\tNote that the arguments are passed as-is to the ioctl, no viewport or rotation quirks are applied!\n"
#endif
"\tIf you just want a full-screen refresh (which will honor -f, --flash), don't pass any suboptions,\n"
"\te.g., fbink -s (if you group short options together, it needs to be the last in its group, i.e., -fs and not -sf).\n"
#if defined(FBINK_FOR_KOBO)
"\n"
"\tNOTE: Due to the way buffers are handled on that platform, this will not behave as expected on sunxi SoCs!\n"
"\tYou'll just end up with solid black inside your refresh region.\n"
#endif
"\n"
"\tSpecifying one or more STRING takes precedence over this mode.\n"
"\n"
"\n"
"A variant of the above allows you to also clear specific rectangular regions of the screen:\n"
"\t-k, --cls [top=NUM,left=NUM,width=NUM,height=NUM]\n"
"\t\tClear the screen (or a region of it), and abort early.\n"
"\t\tHonors -B, --background; -h, --invert; -H, --nightmode; -W, --waveform; -D, --dither; -b, --norefresh; -w, --wait.\n"
"\t\tThis takes precedence over *everything* and will abort as soon as it's done.\n"
"\t\tIf you just want a full-screen clear (which will honor -f, --flash), don't pass any suboptions,\n"
"\t\te.g., fbink -k (if you group short options together, it needs to be the last in its group, i.e., -fk and not -kf).\n"
"\t\tYou can use this to effectively paint arbitrary background colored rectangular shapes.\n"
"\n"
#ifdef FBINK_WITH_IMAGE
"\n"
"\n"
"\n"
"\n"
"You can also eschew printing a STRING, and print an IMAGE at the requested coordinates instead:\n"
"\t-g, --image file=PATH,x=NUM,y=NUM,halign=ALIGN,valign=ALIGN,w=NUM,h=NUM,dither [-i, --img PATH]\n"
"\t\tSupported ALIGN values: NONE (or LEFT for halign, TOP for valign), CENTER or MIDDLE, EDGE (or RIGHT for halign, BOTTOM for valign).\n"
"\t\tIf dither is specified, *software* dithering (ordered, 8x8) will be applied to the image, ensuring it'll match the eInk palette exactly.\n"
"\t\tThis is *NOT* mutually exclusive with -D, --dither!\n"
"\t\tw & h *may* be used to request scaling. If one of them is set to 0, aspect ratio will be respected.\n"
"\t\tSet to -1 to request the viewport's dimension for that side.\n"
"\t\tIf either side is set to something lower than -1, the image will be scaled to the largest possible dimension that fits on screen while honoring the original aspect ratio.\n"
"\t\tThey both default to 0, meaning no scaling will be done.\n"
"\n"
"EXAMPLES:\n"
"\tfbink -g file=hello.png\n"
"\t\tDisplays the image \"hello.png\", starting at the top left of the screen.\n"
"\tfbink -i hello,world.png -g x=-10,y=11 -x 5 -y 8\n"
"\t\tDisplays the image \"hello,world.png\", starting at the ninth line plus 11px and the sixth column minus 10px.\n"
"\tfbink -g file=hello.png,halign=EDGE,valign=CENTER\n"
"\t\tDisplays the image \"hello.png\", in the middle of the screen, aligned to the right edge.\n"
"\tfbink -g file=hello.png -W A2\n"
"\t\tDisplays the image \"hello.png\", in monochrome.\n"
"\tfbink -i wheee.png\n"
"\t\tDisplays the image \"wheee.png\" with the default settings.\n"
"\n"
"Options affecting the image's appearance:\n"
"\t-a, --flatten\tIgnore the alpha channel.\n"
"\n"
"NOTES:\n"
"\tSupported image formats: JPEG, PNG, TGA, BMP, GIF & PNM\n"
"\t\tNote that, in some cases, exotic encoding settings may not be supported.\n"
"\t\tTransparency is supported, but it may be slightly slower (because we may need to do alpha blending).\n"
"\t\t\tYou can use the --flatten flag to avoid the potential performance penalty by always ignoring alpha.\n"
"\t\tAs an additional quirk, you can't pass paths with commas in it to file. Pass those to the -i, --img flag instead.\n"
"\t\tAnd if you want to read image data from stdin, make sure to pass \"-\" as the file name.\n"
"\tThis honors -f, --flash, as well as -c, --clear; -W, --waveform; -D, --dither; -H, --nightmode; -b, --norefresh & -h, --invert\n"
"\t\tNote that this also honors -x, --col & -y, --row (taking -S, --size into account), in addition to the coordinates you specify.\n"
"\t\tThe aim is to make it easier to align small images to text.\n"
"\t\tAnd to make pixel-perfect adjustments, you can also specifiy negative values for x & y.\n"
"\tSpecifying one or more STRING takes precedence over this mode.\n"
"\t-s, --refresh also takes precedence over this mode.\n"
#endif
"\n"
"\n"
"\n"
"\n"
"NOTES:\n"
"\tShell script writers can also use the -e, --eval flag to have FBInk just spit out a few of its internal state variables to stdout,\n"
"\t\te.g., eval $(fbink -e)\n"
"\n"
"\n"
"NOTES:\n"
"\tFor more complex & long-running use-cases involving *text* only (or a progress/activity bar), you can also switch to daemon mode, via -d, --daemon NUM_LINES\n"
"\tIt expects a single argument: the amount of lines consecutive prints can occupy before wrapping back to the original coordinates.\n"
"\tIt it's set to 0, the behavior matches what usually happens when you pass multiple strings to FBInk (i.e., the only wrapping happens at screen egde).\n"
"\tWhile, for example, setting it to 1 will ensure every print will start at the same coordinates.\n"
"\tIn this mode, FBInk will daemonize instantly, and then print its PID to stdout. You should consume stdout, and check the return code:\n"
"\tif it's 0, then you have a guarantee that what you've grabbed from stdout is *strictly* a PID.\n"
"\tYou can then send a kill -0 to that PID to check for an early abort.\n"
"\tBy default, it will create a named pipe for IPC: " FBINK_PIPE
" (if the file already exists, whatever type it may be, FBInk will abort).\n"
"\tYou can ask for a custom path by setting FBINK_NAMED_PIPE to an absolute path in your environment.\n"
"\tCreating and removing that FIFO is FBInk's responsibility. Don't create it yourself.\n"
"\tMake sure you kill FBInk via SIGTERM so it has a chance to remove it itself on exit.\n"
"\t(Otherwise, you may want to ensure that it doesn't already exist *before* launching a daemon mode session).\n"
"\tWith the technicalities out of the way, it's then as simple as writing to that pipe for stuff to show up on screen ;).\n"
"\te.g., echo -n 'Hello World!' > " FBINK_PIPE
"\n"
"\tRemember that LFs are honored!\n"
"\tAlso, the daemon will NOT abort on FBInk errors, and it redirects stdout & stderr to /dev/null, so errors & bogus input will be silently ignored!\n"
"\tIt can abort on early setup errors, though, before *or* after having redirected stderr...\n"
"\tIt does enforce logging to the syslog, though, but, again, early commandline parsing errors may still be sent to stderr...\n"
"\n");
return;
}
// Fun helpers for the daemon mode...
static void
cleanup_handler(int signum __attribute__((unused)), siginfo_t* siginfo, void* context __attribute__((unused)))
{
// Our main loop handles EINTR, and will abort cleanly once it sees that flag
g_timeToDie = 1;
// NOTE: I have no idea how long that pointer is supposed to be safe to use, so, make a copy of the fields we care about.
g_sigCaught.signo = siginfo->si_signo;
g_sigCaught.pid = siginfo->si_pid;
g_sigCaught.uid = siginfo->si_uid;
}
// Because daemon() only appeared in glibc 2.21 (and doesn't double-fork anyway)
static int
daemonize(void)
{
switch (fork()) {
case -1:
PFWARN("initial fork: %m");
return -1;
case 0:
break;
default:
_exit(EXIT_SUCCESS);
}
if (setsid() == -1) {
PFWARN("setsid: %m");
return -1;
}
// Double fork, for... reasons!
// In practical terms, this ensures we get re-parented to init *now*.
// Ignore SIGHUP while we're there, since we don't want to be killed by it.
struct sigaction sa = { .sa_handler = SIG_IGN, .sa_flags = SA_RESTART };
if (sigaction(SIGHUP, &sa, NULL) == -1) {
PFWARN("sigaction: %m");
return -1;
}
switch (fork()) {
case -1:
PFWARN("final fork: %m");
return -1;
case 0:
break;
default:
_exit(EXIT_SUCCESS);
}
if (chdir("/tmp") == -1) {
PFWARN("chdir: %m");
return -1;
}
// Make sure we keep honoring rcS's umask
umask(022); // Flawfinder: ignore
// Before we close the shop down, output our final PID to stdout...
fprintf(stdout, "%ld\n", (long) getpid());
fflush(stdout);
// Redirect stdin, stdout & stderr to /dev/null
int fd = open("/dev/null", O_RDWR);
if (fd != -1) {
dup2(fd, fileno(stdin));
dup2(fd, fileno(stdout));
dup2(fd, fileno(stderr));
if (fd > 3) {
close(fd);
}
} else {
PFWARN("Failed to redirect stdin, stdout & stderr to /dev/null (open: %m)");
return -1;
}
return 0;
}
// Truly infinite progress bar
// NOTE: Punted off to a dedicated function to workaround an amazingly weird & obscure performance issue:
// keeping this inlined in main massively tanks *image* processing performance (by ~50%!),
// when built w/ LTO... o_O.
static int
do_infinite_progress_bar(int fbfd, const FBInkConfig* fbink_cfg)
{
int rv = EXIT_SUCCESS;
const struct timespec zzz = { 0L, 750000000L };
for (;;) {
for (uint8_t i = 0; i < 16; i++) {
rv = fbink_print_activity_bar(fbfd, i, fbink_cfg);
if (rv != EXIT_SUCCESS) {
break;
}
nanosleep(&zzz, NULL);
}
for (uint8_t i = 16; i > 0; i--) {
rv = fbink_print_activity_bar(fbfd, i, fbink_cfg);
if (rv != EXIT_SUCCESS) {
break;
}
nanosleep(&zzz, NULL);
}
}
return rv;
}
// Small helper to handle loading OT fonts
static void
load_ot_fonts(const char* reg_ot_file,
const char* bd_ot_file,
const char* it_ot_file,
const char* bdit_ot_file,
const FBInkConfig* fbink_cfg,
FBInkOTConfig* ot_config)
{
if (reg_ot_file) {
if (!fbink_cfg->is_quiet) {
LOG("Loading font '%s' for the Regular style", reg_ot_file);
}
if (fbink_add_ot_font(reg_ot_file, FNT_REGULAR) < 0) {
WARN("Failed to open font file '%s'", reg_ot_file);
}
}
if (bd_ot_file) {
if (!fbink_cfg->is_quiet) {
LOG("Loading font '%s' for the Bold style", bd_ot_file);
}
if (fbink_add_ot_font(bd_ot_file, FNT_BOLD) < 0) {
WARN("Failed to open font file '%s'", bd_ot_file);
}
}
if (it_ot_file) {
if (!fbink_cfg->is_quiet) {
LOG("Loading font '%s' for the Italic style", it_ot_file);
}
if (fbink_add_ot_font(it_ot_file, FNT_ITALIC) < 0) {
WARN("Failed to open font file '%s'", it_ot_file);
}
}
if (bdit_ot_file) {
if (!fbink_cfg->is_quiet) {
LOG("Loading font '%s' for the Bold Italic style", bdit_ot_file);
}
if (fbink_add_ot_font(bdit_ot_file, FNT_BOLD_ITALIC) < 0) {
WARN("Failed to open font file '%s'", bdit_ot_file);
}
}
// NOTE: Backward compatibility to match the behavior of version < 1.22.3 in most cases:
// if a *single* non-Regular style has been loaded, make it the default style.
if (bd_ot_file && !reg_ot_file && !it_ot_file && !bdit_ot_file) {
ot_config->style = FNT_BOLD;
LOG("The only loaded font style was Bold: make it the default style!");
} else if (it_ot_file && !reg_ot_file && !bd_ot_file && !bdit_ot_file) {
ot_config->style = FNT_ITALIC;
LOG("The only loaded font style was Italic: make it the default style!");
} else if (bdit_ot_file && !reg_ot_file && !bd_ot_file && !it_ot_file) {
ot_config->style = FNT_BOLD_ITALIC;
LOG("The only loaded font style was Bold Italic: make it the default style!");
}
}
// Small utility functions for want_lastrect
static void
compute_lastrect(void)
{
// No need to check for error, it will return {0, 0, 0, 0} on failure anyway ;).
const FBInkRect last_rect = fbink_get_last_rect(false);
static unsigned short int max_y2 = 0U;
// If that's the first call, simply use last_rect as-is
if (totalRect.width == 0U && totalRect.height == 0U) {
totalRect = last_rect;
// Don't forget to keep track of the bottom of our first rectangle, though ;).
const unsigned short int y2 = (unsigned short int) (last_rect.top + last_rect.height);
max_y2 = y2;
} else {
// Otherwise, build a rect that overlaps w/ every previous rects...
totalRect.top = (unsigned short int) MIN(totalRect.top, last_rect.top);
totalRect.left = (unsigned short int) MIN(totalRect.left, last_rect.left);
totalRect.width = (unsigned short int) MAX(totalRect.width, last_rect.width);
// Height is a wee bit trickier, as we *can* wraparound, so last_rect might be *above* totalRect...
// Se we compute the absolute y coordinate of the bottom of both rectangles, keep the largest one,
// and re-compute height.
const unsigned short int y2 =
(unsigned short int) MAX(totalRect.top + totalRect.height, last_rect.top + last_rect.height);
// Remember the largest y2 we ever encountered so we can compute height properly
max_y2 = (unsigned short int) MAX(max_y2, y2);
totalRect.height = (unsigned short int) (max_y2 - totalRect.top);
}
}
static void
recap_lastrect(void)
{
printf("lastRect_Left=%hu;lastRect_Top=%hu;lastRect_Width=%hu;lastRect_Height=%hu;",
totalRect.left,
totalRect.top,
totalRect.width,
totalRect.height);
}
static void
print_lastrect(void)
{
// No need to check for error, it will return {0, 0, 0, 0} on failure anyway ;).
const FBInkRect last_rect = fbink_get_last_rect(false);
printf("lastRect_Left=%hu;lastRect_Top=%hu;lastRect_Width=%hu;lastRect_Height=%hu;",
last_rect.left,
last_rect.top,
last_rect.width,
last_rect.height);
}
// Input validation via strtoul, for an uint32_t
// Adapted from the same in KFMon ;).
static int
strtoul_u(int opt, const char* subopt, const char* str, uint32_t* result)
{
strtoul_chk(opt, subopt, str, result, true);
}
// Input validation via strtoul, for an uint16_t
static int
strtoul_hu(int opt, const char* subopt, const char* str, uint16_t* result)
{
strtoul_chk(opt, subopt, str, result, true);
}
// Input validation via strtoul, for an uint8_t
static int
strtoul_hhu(int opt, const char* subopt, const char* str, uint8_t* result)
{
strtoul_chk(opt, subopt, str, result, true);
}
// Input validation via strtoul, for an uint8_t (accept trailing garbage, e.g., LFs)
static int
strtoul_hhu_lax(int opt, const char* subopt, const char* str, uint8_t* result)
{
strtoul_chk(opt, subopt, str, result, false);
}
// Input validation via strtol, for a short int
static int
strtol_hi(int opt, const char* subopt, const char* str, short int* result)
{
strtol_chk(opt, subopt, str, result, true);
}
// Input validation via strtol, for an int8_t
static int
strtol_hhi(int opt, const char* subopt, const char* str, int8_t* result)
{
strtol_chk(opt, subopt, str, result, true);
}
// In the same vein, but with strtof, for a *positive* float
static int
strtof_pos(int opt, const char* subopt, const char* str, float* result)
{
strtof_chk(opt, subopt, str, result, true);
}
// Application entry point
int
main(int argc, char* argv[])
{
int opt;
// NOTE: getopt_long will only update opt_index when passed a *long* option,
// so we need to do the matching ourselves when we're passed *short* options, hence the sentinel value...
int opt_index = -1;
static const struct option opts[] = {
{ "row", required_argument, NULL, 'y' },
{ "col", required_argument, NULL, 'x' },
{ "voffset", required_argument, NULL, 'Y' },
{ "hoffset", required_argument, NULL, 'X' },
{ "invert", no_argument, NULL, 'h' },
{ "flash", no_argument, NULL, 'f' },
{ "clear", no_argument, NULL, 'c' },
{ "centered", no_argument, NULL, 'm' },
{ "halfway", no_argument, NULL, 'M' },
{ "padded", no_argument, NULL, 'p' },
{ "rpadded", no_argument, NULL, 'r' },
{ "refresh", optional_argument, NULL, 's' },
{ "size", required_argument, NULL, 'S' },
{ "font", required_argument, NULL, 'F' },
{ "verbose", no_argument, NULL, 'v' },
{ "quiet", no_argument, NULL, 'q' },
{ "image", required_argument, NULL, 'g' },
{ "img", required_argument, NULL, 'i' },
{ "flatten", no_argument, NULL, 'a' },
{ "eval", no_argument, NULL, 'e' },
{ "interactive", no_argument, NULL, 'I' },
{ "color", required_argument, NULL, 'C' },
{ "background", required_argument, NULL, 'B' },
{ "linecountcode", no_argument, NULL, 'L' },
{ "linecount", no_argument, NULL, 'l' },
{ "progressbar", required_argument, NULL, 'P' },
{ "activitybar", required_argument, NULL, 'A' },
{ "noviewport", no_argument, NULL, 'V' },
{ "overlay", no_argument, NULL, 'o' },
{ "bgless", no_argument, NULL, 'O' },
{ "fgless", no_argument, NULL, 'T' },
{ "truetype", required_argument, NULL, 't' },
{ "norefresh", no_argument, NULL, 'b' },
{ "dither", optional_argument, NULL, 'D' },
{ "waveform", required_argument, NULL, 'W' },
{ "nightmode", no_argument, NULL, 'H' },
{ "coordinates", no_argument, NULL, 'E' },
{ "mimic", no_argument, NULL, 'Z' },
{ "koreader", no_argument, NULL, 'z' },
{ "cls", optional_argument, NULL, 'k' },
{ "animate", required_argument, NULL, 'K' },
{ "wait", no_argument, NULL, 'w' },
{ "daemon", required_argument, NULL, 'd' },
{ "syslog", no_argument, NULL, 'G' },
{ "help", no_argument, NULL, 'Q' },
{ NULL, 0, NULL, 0 }
};
FBInkConfig fbink_cfg = { 0 };
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-braces"
FBInkOTConfig ot_config = { 0 };
#pragma GCC diagnostic pop
FBInkOTFit ot_fit = { 0 };
enum
{
TOP_OPT = 0,
LEFT_OPT,
WIDTH_OPT,
HEIGHT_OPT,
};
enum
{
FILE_OPT = 0,
XOFF_OPT,
YOFF_OPT,
HALIGN_OPT,
VALIGN_OPT,
SCALED_WIDTH_OPT,
SCALED_HEIGHT_OPT,
SW_DITHER_OPT,
};
enum
{
REGULAR_OPT = 0,
BOLD_OPT,
ITALIC_OPT,
BOLDITALIC_OPT,
SIZE_OPT,
PX_OPT,
TM_OPT,
BM_OPT,
LM_OPT,
RM_OPT,
PADDING_OPT,
FMT_OPT,
COMPUTE_OPT,
NOTRUNC_OPT,
STYLE_OPT,
};
enum
{
DIRECTION_OPT = 0,
STEPS_OPT,
};
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#pragma clang diagnostic ignored "-Wunknown-warning-option"
#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers"
#pragma clang diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers"
char* const refresh_token[] = {
[TOP_OPT] = "top", [LEFT_OPT] = "left", [WIDTH_OPT] = "width", [HEIGHT_OPT] = "height", NULL
};
char* const image_token[] = { [FILE_OPT] = "file", [XOFF_OPT] = "x", [YOFF_OPT] = "y",
[HALIGN_OPT] = "halign", [VALIGN_OPT] = "valign", [SCALED_WIDTH_OPT] = "w",
[SCALED_HEIGHT_OPT] = "h", [SW_DITHER_OPT] = "dither", NULL };
char* const truetype_token[] = { [REGULAR_OPT] = "regular", [BOLD_OPT] = "bold",
[ITALIC_OPT] = "italic", [BOLDITALIC_OPT] = "bolditalic",
[SIZE_OPT] = "size", [PX_OPT] = "px",
[TM_OPT] = "top", [BM_OPT] = "bottom",
[LM_OPT] = "left", [RM_OPT] = "right",
[PADDING_OPT] = "padding", [FMT_OPT] = "format",
[COMPUTE_OPT] = "compute", [NOTRUNC_OPT] = "notrunc",
[STYLE_OPT] = "style", NULL };
// Recycle the refresh enum ;).
char* const cls_token[] = {
[TOP_OPT] = "top", [LEFT_OPT] = "left", [WIDTH_OPT] = "width", [HEIGHT_OPT] = "height", NULL
};
char* const anim_token[] = { [DIRECTION_OPT] = "direction", [STEPS_OPT] = "steps", NULL };
#pragma GCC diagnostic pop
char* full_subopts = NULL;
char* subopts;
char* value = NULL;
uint32_t region_top = 0;
uint32_t region_left = 0;
uint32_t region_width = 0;
uint32_t region_height = 0;
char* hwd_name = NULL;
char* wfm_name = NULL;
bool is_refresh = false;
char* image_file = NULL;
short int image_x_offset = 0;
short int image_y_offset = 0;
bool is_image = false;
bool is_eval = false;
bool is_interactive = false;
bool want_linecode = false;
bool want_linecount = false;
bool want_lastrect = false;
bool is_progressbar = false;
bool is_activitybar = false;
bool is_infinite = false;
bool is_mimic = false;
bool is_koreader = false;
bool is_cls = false;
bool is_help = false;
const char* pipe_path = NULL;
bool is_daemon = false;
uint8_t daemon_lines = 0U;
bool wait_for = false;
uint8_t progress = 0;
bool is_truetype = false;
char* reg_ot_file = NULL;
char* bd_ot_file = NULL;
char* it_ot_file = NULL;
char* bdit_ot_file = NULL;
// Default to a 12 steps right-to-left swipe, àla Malbec.
MTK_SWIPE_DIRECTION_INDEX_T direction = MTK_SWIPE_DIR_LEFT;
uint8_t steps = 12U;
bool errfnd = false;
// NOTE: c.f., https://codegolf.stackexchange.com/q/148228 to sort this mess when I need to find an available letter ;p
// In fact, that's the current tally of alnum entries left: JjNnRUu
while (
(opt = getopt_long(
argc, argv, "y:x:Y:X:hfcmMprs::S:F:vqg:i:aeIC:B:LlP:A:oOTVt:bD::W:HEZzk::wd:GQK:", opts, &opt_index)) !=
-1) {
switch (opt) {
case 'y':
if (strtol_hi(opt, NULL, optarg, &fbink_cfg.row) < 0) {
errfnd = true;
}
break;
case 'x':
if (strtol_hi(opt, NULL, optarg, &fbink_cfg.col) < 0) {
errfnd = true;
}
break;
case 'Y':
if (strtol_hi(opt, NULL, optarg, &fbink_cfg.voffset) < 0) {
errfnd = true;
}
break;
case 'X':
if (strtol_hi(opt, NULL, optarg, &fbink_cfg.hoffset) < 0) {
errfnd = true;
}
break;
case 'h':
fbink_cfg.is_inverted = true;
break;
case 'f':
fbink_cfg.is_flashing = true;
break;
case 'c':
fbink_cfg.is_cleared = true;
break;
case 'm':
fbink_cfg.is_centered = true;
break;
case 'M':
fbink_cfg.is_halfway = true;
break;
case 'p':
fbink_cfg.is_padded = true;
break;
case 'r':
fbink_cfg.is_rpadded = true;
break;
case 's': {
// We'll want our longform name for diagnostic messages...
const char* opt_longname = NULL;
// Look it up if we were passed the short form...
if (opt_index == -1) {
// Loop until we hit the final NULL entry
for (opt_index = 0; opts[opt_index].name; opt_index++) {
if (opts[opt_index].val == opt) {
opt_longname = opts[opt_index].name;
break;
}
}
} else {
opt_longname = opts[opt_index].name;
}
// NOTE: Nasty bit of trickery to make getopt's optional_argument actually useful...
// Hat trick (& explanation) courtesy of https://stackoverflow.com/a/32575314
// If `optarg` isn't set and argv[optind] doesn't look like another option,
// then assume it's our parameter and overtly modify optind to compensate.
if (!optarg && argv[optind] != NULL && argv[optind][0] != '-') {
subopts = argv[optind++];
} else {
subopts = optarg;
}
// NOTE: We'll need to remember the original, full suboption string for diagnostic messages,
// because getsubopt will rewrite it during processing...
if (subopts && *subopts != '\0') {
// Only remember the first offending suboption list...
if (!errfnd) {
full_subopts = strdupa(subopts); // lgtm [cpp/alloca-in-loop]
}
}
while (subopts && *subopts != '\0' && !errfnd) {
switch (getsubopt(&subopts, refresh_token, &value)) {
case TOP_OPT:
if (value == NULL) {
ELOG("Missing value for suboption '%s' of -%c, --%s",
refresh_token[TOP_OPT],
opt,
opt_longname);
errfnd = true;
break;
}
if (strtoul_u(opt, refresh_token[TOP_OPT], value, ®ion_top) <
0) {
errfnd = true;
}
break;
case LEFT_OPT:
if (value == NULL) {
ELOG("Missing value for suboption '%s' of -%c, --%s",
refresh_token[LEFT_OPT],
opt,
opt_longname);
errfnd = true;
break;
}
if (strtoul_u(opt, refresh_token[LEFT_OPT], value, ®ion_left) <
0) {
errfnd = true;
}
break;
case WIDTH_OPT:
if (value == NULL) {
ELOG("Missing value for suboption '%s' of -%c, --%s",
refresh_token[WIDTH_OPT],
opt,
opt_longname);
errfnd = true;
break;
}
if (strtoul_u(
opt, refresh_token[WIDTH_OPT], value, ®ion_width) <
0) {
errfnd = true;
}
break;
case HEIGHT_OPT:
if (value == NULL) {
ELOG("Missing value for suboption '%s' of -%c, --%s",
refresh_token[HEIGHT_OPT],
opt,
opt_longname);
errfnd = true;
break;
}
if (strtoul_u(
opt, refresh_token[HEIGHT_OPT], value, ®ion_height) <
0) {
errfnd = true;
}
break;
default:
ELOG("No match found for token: /%s/ for -%c, --%s",
value,
opt,
opt_longname);
errfnd = true;
break;
}
}
// Make sure we won't pass an invalid rectangle to the driver, because that'd soft lock.
if ((region_height == 0 || region_width == 0) &&
!(region_top == 0 && region_left == 0 && region_height == 0 && region_width == 0)) {
ELOG(
"Non-zero values must be specified for suboptions '%s' and '%s' of -%c, --%s",
refresh_token[HEIGHT_OPT],
refresh_token[WIDTH_OPT],
opt,
opt_longname);
errfnd = true;
} else {