1 #include "config.h"
2 #include <X11/Xatom.h>
3 #include "ylistbox.h"
4 #include "ymenu.h"
5 #include "yxapp.h"
6 #include "sysdep.h"
7 #include "yaction.h"
8 #include "yinputline.h"
9 #include "ymenuitem.h"
10 #include "ylocale.h"
11 #include "yrect.h"
12 #include "ascii.h"
13 #include "intl.h"
14 #include "ykey.h"
15 #include "yfontname.h"
16 #include "yfontbase.h"
17 #include <strings.h>
18 #include <X11/Xutil.h>
19 
20 #define ICEWM_SITE      "https://ice-wm.org/"
21 #define ICEWM_FAQ       "https://ice-wm.org/FAQ/"
22 #define THEME_HOWTO     "https://ice-wm.org/themes/"
23 #define ICEGIT_SITE     "https://github.com/bbidulock/icewm/"
24 #define ICEWM_1         DOCDIR "/icewm.1.html"
25 #define ICEWMBG_1       DOCDIR "/icewmbg.1.html"
26 #define ICESOUND_1      DOCDIR "/icesound.1.html"
27 #define BACKGROUND      "rgb:E0/E0/E0"
28 #define FOREGROUND      "rgb:00/00/00"
29 #define LINK_COLOR      "rgb:06/45/AD"
30 #define RULE_COLOR      "rgb:80/80/80"
31 
32 #ifdef DEBUG
33 #define DUMP
34 //#define TEXT
35 #endif
36 
37 #define SPACE(c)  ASCII::isWhiteSpace(c)
38 
39 enum ViewerDimensions {
40     ViewerWidth      = 700,
41     ViewerHeight     = 700,
42     ViewerLeftMargin =  20,
43     ViewerTopMargin  =  10,
44 };
45 
46 char const * ApplicationName = "icehelp";
47 
48 static bool verbose, complain, ignoreClose, noFreeType, reverseVideo;
49 
50 class cbuffer {
51     size_t cap, ins;
52     char *ptr;
53 public:
cbuffer()54     cbuffer() : cap(0), ins(0), ptr(nullptr) {
55     }
cbuffer(const char * s)56     cbuffer(const char *s) : cap(1 + strlen(s)), ins(cap - 1),
57         ptr((char *)malloc(cap))
58     {
59         memcpy(ptr, s, cap);
60     }
cbuffer(const cbuffer & c)61     cbuffer(const cbuffer& c) :
62         cap(c.cap), ins(c.ins), ptr(nullptr) {
63         if (c.ptr) {
64             ptr = (char *)malloc(cap);
65             memcpy(ptr, c.ptr, cap);
66         }
67     }
operator =(const char * c)68     cbuffer& operator=(const char *c) {
69         if (c != ptr) {
70             if (c) {
71                 ins = strlen(c);
72                 cap = 1 + ins;
73                 ptr = (char *)realloc(ptr, cap);
74                 memcpy(ptr, c, cap);
75             } else if (ptr) {
76                 cap = ins = 0;
77                 free(ptr); ptr = nullptr;
78             }
79         }
80         return *this;
81     }
operator =(const cbuffer & c)82     cbuffer& operator=(const cbuffer& c) {
83         if (&c != this) {
84             if (c.ptr) {
85                 ins = c.ins;
86                 cap = 1 + ins;
87                 ptr = (char *)realloc(ptr, cap);
88                 memcpy(ptr, c.ptr, cap);
89             } else if (ptr) {
90                 cap = ins = 0;
91                 free(ptr); ptr = nullptr;
92             }
93         }
94         return *this;
95     }
operator +=(const char * c)96     cbuffer& operator+=(const char *c) {
97         if (c) {
98             size_t siz = 1 + strlen(c);
99             if (cap < ins + siz) {
100                 cap = ins + siz;
101                 ptr = (char *)realloc(ptr, cap);
102             }
103             memcpy(ptr + ins, c, siz);
104             ins += siz - 1;
105         }
106         return *this;
107     }
len() const108     int len() const {
109         return (int) ins;
110     }
push(int c)111     void push(int c) {
112         if (cap < 2 + ins) {
113             cap = 8 + 3 * cap / 2;
114             ptr = (char *)realloc(ptr, cap);
115         }
116         ptr[ins+1] = 0;
117         ptr[ins++] = (char) c;
118     }
pop()119     int pop() {
120         int result = 0;
121         if (ins > 0) {
122             result = ptr[--ins];
123             ptr[ins] = 0;
124         }
125         return result;
126     }
peek()127     char *peek() {
128         return ptr;
129     }
release()130     char *release() {
131         char *result = ptr;
132         cap = ins = 0;
133         ptr = nullptr;
134         return result;
135     }
~cbuffer()136     ~cbuffer() {
137         if (ptr) free(ptr);
138     }
operator const char*() const139     operator const char *() const {
140         return ptr;
141     }
last() const142     int last() const {
143         return ins > 0 ? ptr[ins-1] : 0;
144     }
isEmpty() const145     bool isEmpty() const {
146         return ins == 0;
147     }
nonempty() const148     bool nonempty() const {
149         return ins > 0;
150     }
151 };
152 
153 class lowbuffer : public cbuffer {
154 public:
push(int c)155     void push(int c) {
156         cbuffer::push(ASCII::toLower(c));
157     }
operator +=(const char * c)158     lowbuffer& operator+=(const char *c) {
159         if (c) {
160             while (*c) push(*c++);
161         }
162         return *this;
163     }
164 };
165 
166 // singly linked list of nodes which have a 'next' pointer.
167 template <class T>
168 class flist {
169     flist(const flist&);
170     flist& operator=(const flist&);
171     T *head, *tail;
172 public:
flist()173     flist() : head(nullptr), tail(nullptr) {}
flist(T * t)174     flist(T *t) : head(t), tail(t) { t->next = nullptr; }
add(T * t)175     void add(T *t) {
176         t->next = nullptr;
177         if (head) {
178             tail = tail->next = t;
179         } else {
180             tail = head = t;
181         }
182     }
first() const183     T *first() const { return head; }
last() const184     T *last() const { return tail; }
destroy()185     void destroy() {
186         for (; head; head = tail) {
187             tail = head->next;
188             delete head;
189         }
190     }
nonempty() const191     bool nonempty() const { return head != 0; }
operator T*() const192     operator T *() const { return head; }
193 };
194 // like flist, but delete all nodes when done.
195 template <class T>
196 class nlist : public flist<T> {
197     typedef flist<T> super;
198 public:
nlist()199     nlist() : super() {}
nlist(T * t)200     nlist(T *t) : super(t) {}
~nlist()201     ~nlist() { super::destroy(); }
202 };
203 
204 class text_node {
205 public:
text_node(const char * t,int l,int f,int _x,int _y,int _w,int _h)206     text_node(const char *t, int l, int f, int _x, int _y, int _w, int _h) :
207         text(t), len(l), fl(f), x(_x), y(_y), tw(_w), th(_h), next(nullptr)
208     {
209     }
210 
211     const char *text;
212     int len, fl;
213     int x, y;
214     int tw, th;
215     text_node *next;
216 };
217 
218 class attr {
219 public:
220     enum attr_type {
221         noattr = 0,
222         href = 1,
223         id = 2,
224         name = 4,
225         rel = 8,
226         maxattr = rel
227     } atype;
228     mstring value;
229     attr *next;
attr()230     attr() : atype(noattr), value(null), next(nullptr) {}
attr(attr_type t,const mstring & s)231     attr(attr_type t, const mstring& s) : atype(t), value(s), next(nullptr) {}
attr(const attr & a)232     attr(const attr& a) : atype(a.atype), value(a.value), next(nullptr) {}
operator =(const attr & a)233     attr& operator=(const attr& a) {
234         if (&a != this) {
235             atype = a.atype;
236             value = a.value;
237         }
238         return *this;
239     }
operator bool() const240     operator bool() const { return atype > noattr; }
get_type(const char * s)241     static attr_type get_type(const char *s) {
242         if (0 == strcmp(s, "href")) return href;
243         if (0 == strcmp(s, "id")) return id;
244         if (0 == strcmp(s, "name")) return name;
245         if (0 == strcmp(s, "rel")) return rel;
246         return noattr;
247     }
248 };
249 
250 class attr_list {
251 private:
252     attr_list(const attr_list&);
253     attr_list& operator=(const attr_list&);
254     nlist<attr> list;
255 public:
attr_list()256     attr_list() : list() {}
add(attr::attr_type typ,const mstring & s)257     void add(attr::attr_type typ, const mstring& s) {
258         if (typ > 0) {
259             list.add(new attr(typ, s));
260         }
261     }
get(int mask,attr * found) const262     bool get(int mask, attr *found) const {
263         for (attr *a = list.first(); a; a = a->next) {
264             if ((mask & a->atype) != 0) {
265                 *found = *a;
266                 return true;
267             }
268         }
269         return false;
270     }
get(int mask,const char * value,attr * found) const271     bool get(int mask, const char *value, attr *found) const {
272         for (attr *a = list.first(); a; a = a->next) {
273             if ((mask & a->atype) != 0 &&
274                 0 == strcmp(value, a->value))
275             {
276                 *found = *a;
277                 return true;
278             }
279         }
280         return false;
281     }
282 };
283 
284 class node {
285 public:
286     enum node_type {
287         unknown,
288         html, head, title,
289         body,
290         text, pre, line, paragraph, hrule,
291         h1, h2, h3, h4, h5, h6,
292         table, tr, td,
293         div,
294         ul, ol, li,
295         bold, font, italic,
296         center,
297         script,
298         style,
299         anchor, img,
300         tt, dl, dd, dt,
301         thead, tbody, tfoot,
302         link, code, meta, form, input,
303         section, figure, aside, footer, main,
304     };
305 
node(node_type t)306     node(node_type t) :
307         type(t),
308         next(nullptr),
309         container(nullptr),
310         txt(nullptr),
311         wrap(),
312         xr(0),
313         yr(0),
314         attrs()
315     {
316     }
317     ~node();
318 
319     node_type type;
320     node *next;
321     node *container;
322     const char *txt;
323     typedef nlist<text_node> text_list;
324     text_list wrap;
325     int xr, yr;
326     attr_list attrs;
327 
add_attribute(attr::attr_type name,const char * value)328     void add_attribute(attr::attr_type name, const char *value) {
329         if (name > attr::noattr) {
330             attrs.add(name, value);
331         }
332     }
add_attribute(const char * name,const char * value)333     void add_attribute(const char *name, const char *value) {
334         add_attribute(attr::get_type(name), value);
335     }
get_attribute(int mask,attr * found) const336     bool get_attribute(int mask, attr *found) const {
337         return attrs.get(mask, found);
338     }
get_attribute(const char * name,attr * found) const339     bool get_attribute(const char *name, attr *found) const {
340         return attrs.get(attr::get_type(name), found);
341     }
342     node* find_attr(int mask, const char *value);
343 
344     static const char *to_string(node_type type);
345     static node_type get_type(const char *buf);
346 };
347 
~node()348 node::~node() {
349     delete next;
350     delete container;
351     free((void *)txt);
352 }
353 
find_attr(int mask,const char * value)354 node* node::find_attr(int mask, const char *value) {
355     for (node *n = this; n; n = n->next) {
356         attr got;
357         if (n->attrs.get(mask, value, &got)) {
358             return n;
359         }
360         if (n->container) {
361             node *found = n->container->find_attr(mask, value);
362             if (found) return found;
363         }
364     }
365     return nullptr;
366 }
367 
368 
369 #define PRE    0x01
370 #define PRE1   0x02
371 #define BOLD   0x04
372 #define LINK   0x08
373 #define BIG    0x10
374 #define ITAL   0x20
375 #define MONO   0x40
376 
377 #define sfPar  0x01
378 #define sfText  0x02
379 
380 static void dump_tree(int level, node *n);
381 
382 #define TS(x) case x : return #x
383 
to_string(node_type type)384 const char *node::to_string(node_type type) {
385     switch (type) {
386         TS(unknown);
387         TS(html);
388         TS(head);
389         TS(title);
390         TS(body);
391         TS(text);
392         TS(pre);
393         TS(line);
394         TS(paragraph);
395         TS(hrule);
396         TS(anchor);
397         TS(h1);
398         TS(h2);
399         TS(h3);
400         TS(h4);
401         TS(h5);
402         TS(h6);
403         TS(table);
404         TS(tr);
405         TS(td);
406         TS(div);
407         TS(ul);
408         TS(ol);
409         TS(li);
410         TS(bold);
411         TS(font);
412         TS(italic);
413         TS(center);
414         TS(script);
415         TS(style);
416         TS(tt);
417         TS(dl);
418         TS(dd);
419         TS(dt);
420         TS(link);
421         TS(code);
422         TS(meta);
423         TS(thead);
424         TS(tbody);
425         TS(tfoot);
426         TS(img);
427         TS(form);
428         TS(input);
429         TS(section);
430         TS(figure);
431         TS(aside);
432         TS(footer);
433         TS(main);
434     }
435     if (complain) {
436         tlog("Unknown node_type %d, after %s, before %s", type,
437                 type > unknown ? to_string((node_type)(type - 1)) : "",
438                 type < input ? to_string((node_type)(type + 1)) : "");
439     }
440 
441     return "??";
442 }
443 
444 #undef TS
445 #define TS(x,y) if (0 == strcmp(buf, #x)) return node::y
446 
get_type(const char * buf)447 node::node_type node::get_type(const char *buf)
448 {
449     TS(html, html);
450     TS(head, head);
451     TS(title, title);
452     TS(body, body);
453     TS(text, text);
454     TS(pre, pre);
455     TS(address, pre);
456     TS(br, line);
457     TS(p, paragraph);
458     TS(header, paragraph);
459     TS(article, paragraph);
460     TS(nav, paragraph);
461     TS(hr, hrule);
462     TS(a, anchor);
463     TS(h1, h1);
464     TS(h2, h2);
465     TS(h3, h3);
466     TS(h4, h4);
467     TS(h5, h5);
468     TS(h6, h6);
469     TS(table, table);
470     TS(tr, tr);
471     TS(td, td);
472     TS(div, div);
473     TS(span, div);
474     TS(ul, ul);
475     TS(ol, ol);
476     TS(li, li);
477     TS(b, bold);
478     TS(strong, bold);
479     TS(font, font);
480     TS(i, italic);
481     TS(em, italic);
482     TS(small, italic);
483     TS(center, center);
484     TS(script, script);
485     TS(svg, script);
486     TS(button, script);
487     TS(path, script);
488     TS(style, style);
489     TS(tt, tt);
490     TS(dl, dl);
491     TS(dd, dd);
492     TS(dt, dt);
493     TS(link, link);
494     TS(code, code);
495     TS(samp, code);
496     TS(kbd, code);
497     TS(var, code);
498     TS(option, code);
499     TS(label, code);
500     TS(blockquote, code);
501     TS(meta, meta);
502     TS(thead, thead);
503     TS(tbody, tbody);
504     TS(tfoot, tfoot);
505     TS(img, img);
506     TS(form, form);
507     TS(input, input);
508     TS(section, section);
509     TS(figure, figure);
510     TS(aside, aside);
511     TS(footer, footer);
512     TS(main, main);
513     if (*buf && buf[strlen(buf)-1] == '/') {
514         cbuffer cbuf(buf);
515         cbuf.pop();
516         return get_type(cbuf);
517     }
518     static const char* ignored[] = {
519         "abbr",
520         "g",
521         "g-emoji",
522         "include-fragment",
523         "rect",
524         "relative-time",
525         "th",
526         "time",
527         "time-ago",
528     };
529     for (unsigned i = 0; i < ACOUNT(ignored); ++i) {
530         if (0 == strcmp(buf, ignored[i]))
531             return node::unknown;
532     }
533     if (complain) {
534         tlog("unknown tag %s", buf);
535     }
536     return node::unknown;
537 }
538 
ignore_comments(FILE * fp)539 static int ignore_comments(FILE *fp) {
540     int c = getc(fp);
541     if (c == '-') {
542         c = getc(fp);
543         if (c == '-') {
544             // comment
545             int n = 0;
546             while ((c = getc(fp)) != EOF) {
547                 if (c == '>' && n >= 2) {
548                     break;
549                 }
550                 n = (c == '-') ? (n + 1) : 0;
551             }
552         }
553     }
554     else if (c == '[' && (c = getc(fp)) == 'C') {
555         /* skip over <![CDATA[...]]> */
556         int n = 0;
557         while ((c = getc(fp)) != EOF) {
558             if (c == '>' && n >= 2) {
559                 break;
560             }
561             n = (c == ']') ? (n + 1) : 0;
562         }
563     }
564     while (c != EOF && c != '>') {
565         c = getc(fp);
566     }
567     return c;
568 }
569 
non_space(int c,FILE * fp)570 static int non_space(int c, FILE *fp) {
571     while (c != EOF && ASCII::isWhiteSpace(c)) {
572         c = getc(fp);
573     }
574     return c;
575 }
576 
getc_non_space(FILE * fp)577 static int getc_non_space(FILE *fp) {
578     return non_space(getc(fp), fp);
579 }
580 
parse(FILE * fp,int flags,node * parent,node * & nextsub,node::node_type & close)581 static node *parse(FILE *fp, int flags, node *parent, node *&nextsub, node::node_type &close) {
582     flist<node> nodes;
583 
584     for (int c = getc(fp); c != EOF; c = (c == '<') ? c : getc(fp)) {
585         if (c == '<') {
586             c = getc(fp);
587             if (c == '!') {
588                 c = ignore_comments(fp);
589             } else if (c == '/') {
590                 // ignore </BR> </P> </LI> ...
591                 lowbuffer buf;
592 
593                 c = getc_non_space(fp);
594                 while (c != EOF && !SPACE(c) && c != '>') {
595                     buf.push(c);
596                     c = getc(fp);
597                 }
598 
599                 node::node_type type = node::get_type(buf);
600                 if (type == node::paragraph ||
601                     type == node::line ||
602                     type == node::hrule ||
603                     type == node::link ||
604                     type == node::unknown ||
605                     (type == node::form && (!parent || parent->type != type)) ||
606                     type == node::meta)
607                 {
608                 }
609                 else if (parent) {
610                     close = type;
611                     break;
612                 }
613             } else {
614                 lowbuffer buf;
615 
616                 c = non_space(c, fp);
617                 while (c != EOF && !SPACE(c) && c != '>') {
618                     buf.push(c);
619                     c = getc(fp);
620                 }
621 
622                 node::node_type type = buf.nonempty()
623                                      ? node::get_type(buf) : node::unknown;
624                 node *n = new node(type);
625                 if (n == nullptr)
626                     break;
627                 while ((c = non_space(c, fp)) != '>' && c != EOF) {
628                     lowbuffer abuf;
629 
630                     while (c != EOF && !SPACE(c) && c != '=' && c != '>') {
631                         abuf.push(c);
632                         c = getc(fp);
633                     }
634                     c = non_space(c, fp);
635                     if (c == '=') {
636                         cbuffer vbuf;
637                         c = getc_non_space(fp);
638                         if (c == '"') {
639                             while ((c = getc(fp)) != EOF && c != '"') {
640                                 vbuf.push(c);
641                             }
642                         } else {
643                             while (c != EOF && !SPACE(c) && c != '>') {
644                                 vbuf.push(c);
645                                 c = getc(fp);
646                             }
647                         }
648                         if (abuf.nonempty())
649                             n->add_attribute(abuf, vbuf);
650                     }
651                 }
652 
653                 if (type == node::line ||
654                     type == node::hrule ||
655                     type == node::paragraph||
656                     type == node::link ||
657                     type == node::meta)
658                 {
659                     nodes.add(n);
660                 } else {
661                     node *container = nullptr;
662 
663                     if (type == node::li ||
664                         type == node::dt ||
665                         type == node::dd ||
666                         type == node::paragraph ||
667                         type == node::line
668                        )
669                     {
670                         if (parent &&
671                             (parent->type == type ||
672                              (type == node::dd && parent->type == node::dt) ||
673                              (type == node::dt && parent->type == node::dd))
674                            )
675                         {
676                             nextsub = n;
677                             break;
678                         }
679                     }
680 
681                     node *nextsub;
682                     node::node_type close_type;
683                     do {
684                         nextsub = nullptr;
685                         close_type = node::unknown;
686                         int fl = flags;
687                         if (type == node::pre) fl |= PRE | PRE1;
688                         container = parse(fp, fl, n, nextsub, close_type);
689                         if (container)
690                             n->container = container;
691                         nodes.add(n);
692 
693                         if (nextsub) {
694                             n = nextsub;
695                         }
696                         if (close_type != node::unknown) {
697                             if (n->type != close_type)
698                                 return nodes;
699                         }
700                     } while (nextsub != nullptr);
701                 }
702             }
703         }
704         else if (c != '<') {
705             cbuffer buf;
706 
707             do {
708                 if (c == '&') {
709                     lowbuffer entity;
710 
711                     do {
712                         entity.push(c);
713                         c = getc(fp);
714                     } while (ASCII::isAlnum(c) || c == '#');
715                     if (c != ';')
716                         ungetc(c, fp);
717                     if (strcmp(entity, "&amp") == 0)
718                         c = '&';
719                     else if (strcmp(entity, "&lt") == 0)
720                         c = '<';
721                     else if (strcmp(entity, "&gt") == 0)
722                         c = '>';
723                     else if (strcmp(entity, "&quot") == 0)
724                         c = '"';
725                     else if (strcmp(entity, "&nbsp") == 0)
726                         c = ' ';    // 32+128;
727                     else if (strcmp(entity, "&#160") == 0)
728                         c = ' ';    // 32+128;
729                     else if (strcmp(entity, "&#8203") == 0)
730                         c = ' ';    // 32+128;  zero width space
731                     else if (strcmp(entity, "&#8211") == 0
732                           || strcmp(entity, "&ndash") == 0)
733                         c = '-';    // en dash
734                     else if (strcmp(entity, "&#8212") == 0
735                           || strcmp(entity, "&mdash") == 0)
736                         c = '-';    // em dash
737                     else if (strcmp(entity, "&#8217") == 0)
738                         c = '\'';   // right single quote
739                     else if (strcmp(entity, "&#8220") == 0) {
740                         buf += "``"; // left double quotes
741                         continue;
742                     }
743                     else if (strcmp(entity, "&#8221") == 0) {
744                         buf += "''"; // right double quotes
745                         continue;
746                     }
747                     else if (strcmp(entity, "&#8226") == 0
748                           || strcmp(entity, "&middot") == 0)
749                         c = '*';   // bullet
750                     else if (strcmp(entity, "&#8230") == 0
751                           || strcmp(entity, "&hellip") == 0) {
752                         buf += "..."; // horizontal ellipsis
753                         continue;
754                     }
755                     else if (strcmp(entity, "&#8594") == 0) {
756                         buf += "->"; // rightwards arrow
757                         continue;
758                     }
759                     else if (strcmp(entity, "&copy") == 0) {
760                         buf += "(c)"; // copyright symbol
761                         continue;
762                     }
763                     else if (strcmp(entity, "&reg") == 0) {
764                         buf += "(R)"; // registered sign
765                         continue;
766                     }
767                     else if (entity.len() > 2 && !strcmp(entity + 2, "tilde")) {
768                         c = entity[1];
769                     }
770                     else if (entity.len() > 2 && !strcmp(entity + 2, "acute")) {
771                         c = entity[1];
772                     }
773                     else if (entity.len() > 2 && !strcmp(entity + 2, "uml")) {
774                         c = entity[1];
775                     }
776                     else {
777                         unsigned special = 0;
778                         int len = 0;
779                         if (1 == sscanf(entity, "&#%u%n", &special, &len)
780                                 && len == entity.len()
781                                 && inrange(special, 32U, 126U))
782                         {
783                             c = (char) special;
784                         }
785                         else {
786                             if (complain) {
787                                 tlog("unknown special '%s'", entity.peek());
788                             }
789                             c = ' ';
790                         }
791                     }
792                 }
793                 if (c == '\r') {
794                     c = getc(fp);
795                     if (c != '\n')
796                         ungetc(c, fp);
797                     c = '\n';
798                 }
799                 if (!(flags & PRE)) {
800                     if (SPACE(c))
801                         c = ' ';
802                 }
803                 if ((flags & PRE1) && c == '\n')
804                     ;
805                 else if (c != ' ' || (flags & PRE) ||
806                         buf.isEmpty() || buf.last() != ' ')
807                 {
808                     buf.push(c);
809                 }
810                 flags &= ~PRE1;
811             } while ((c = getc(fp)) != EOF && c != '<');
812 
813             if (c == '<') {
814                 int k = getc(fp);
815                 if (k == '/') {
816                     if (SPACE(buf.last()))
817                         buf.pop();
818                 }
819                 ungetc(k, fp);
820             }
821 
822             if (buf.nonempty()) {
823                 node *n = new node(node::text);
824                 n->txt = buf.release();
825                 nodes.add(n);
826             }
827         }
828     }
829     return nodes;
830 }
831 
832 class History {
833 private:
834     MStringArray array;
835     int where;
836 public:
History()837     History() : where(-1) { }
empty() const838     bool empty() const { return array.isEmpty(); }
size() const839     int size() const { return array.getCount(); }
get(int i) const840     const char* get(int i) const { return array[i]; }
push(const mstring & s)841     void push(const mstring& s) {
842         if (where == -1 || (s.nonempty() && s != get(where))) {
843             for (int k = size() - 1; k > where; --k) {
844                 array.remove(k);
845             }
846             array.insert(++where, s);
847         }
848     }
current()849     mstring& current() {
850         if (empty()) array.insert(++where, null);
851         return array[where];
852     }
hasLeft() const853     bool hasLeft() const { return where > 0; }
left()854     bool left() {
855         return hasLeft() ? (--where, true) : false;
856     }
hasRight() const857     bool hasRight() const { return where + 1 < size(); }
right()858     bool right() {
859         return hasRight() ? (++where, true) : false;
860     }
hasFirst() const861     bool hasFirst() const { return 0 < size(); }
first()862     bool first() {
863         return hasFirst() ? (where = 0, true) : false;
864     }
865 };
866 
867 class FontEntry {
868 public:
869     int size;
870     int flag;
871     const char *core;
872     const char *xeft;
873     YFont font;
874 };
875 class FontTable {
876 public:
877     static FontEntry table[];
878     static YFont get(int size, int flags);
reset()879     static void reset() {
880         for (int k = 0; table[k].size; ++k) {
881             table[k].font = null;
882         }
883     }
884 };
885 FontEntry FontTable::table[] = {
886     { 12, 0,
887         "-adobe-helvetica-medium-r-normal--12-120-75-75-p-67-iso8859-1",
888         "Snap:size=10,sans-serif:size=10" },
889     { 14, 0,
890         "-adobe-helvetica-medium-r-normal--14-140-75-75-p-77-iso8859-1",
891         "Snap:size=12,sans-serif:size=12" },
892     { 18, 0,
893         "-adobe-helvetica-medium-r-normal--18-180-75-75-p-98-iso8859-1",
894         "Snap:size=14,sans-serif:size=14" },
895     { 12, BOLD,
896         "-adobe-helvetica-bold-r-normal--12-120-75-75-p-70-iso8859-1",
897         "Snap:size=10:bold,sans-serif:size=10:bold" },
898     { 14, BOLD,
899         "-adobe-helvetica-bold-r-normal--14-140-75-75-p-82-iso8859-1",
900         "Snap:size=12:bold,sans-serif:size=12:bold" },
901     { 18, BOLD,
902         "-adobe-helvetica-bold-r-normal--18-180-75-75-p-103-iso8859-1",
903         "Snap:size=14:bold,sans-serif:size=14:bold" },
904     { 12, MONO,
905         "-adobe-courier-medium-r-normal--12-120-75-75-m-70-iso8859-1",
906         "courier:size=10,monospace:size=10" },
907     { 17, MONO | BOLD,
908         "-adobe-courier-bold-r-normal--17-120-100-100-m-100-iso8859-1",
909         "courier:size=14,monospace:size=14" },
910     { 12, ITAL,
911         "-adobe-helvetica-medium-o-normal--12-120-75-75-p-67-iso8859-1",
912         "sans-serif:size=10:oblique" },
913     { 14, ITAL,
914         "-adobe-helvetica-medium-o-normal--14-140-75-75-p-78-iso8859-1",
915         "sans-serif:size=12:oblique" },
916     { 0, 0, nullptr, nullptr },
917 };
get(int size,int flags)918 YFont FontTable::get(int size, int flags) {
919     int best = 0;
920     int diff = abs(size - table[best].size);
921     for (int i = 1; table[i].size > 0; ++i) {
922         int delt = abs(size - table[i].size);
923         if (delt < diff) {
924             best = i;
925             diff = delt;
926         }
927         else if (diff == delt) {
928             int bflag = table[best].flag;
929             int iflag = table[i].flag;
930             if ((bflag & BOLD) != (iflag & BOLD)) {
931                 if ((iflag & BOLD) == (flags & BOLD)) {
932                     best = i;
933                 }
934             }
935             else if ((bflag & MONO) != (iflag & MONO)) {
936                 if ((iflag & MONO) == (flags & MONO)) {
937                     best = i;
938                 }
939             }
940             else if ((bflag & ITAL) != (iflag & ITAL)) {
941                 if ((iflag & ITAL) == (flags & ITAL)) {
942                     best = i;
943                 }
944             }
945         }
946     }
947     if (table[best].font == null) {
948         const char* xeft = noFreeType ? nullptr : table[best].xeft;
949         YFontName fontName(&table[best].core, &xeft);
950         table[best].font = fontName;
951     }
952     return table[best].font;
953 }
954 
955 class HTListener {
956 public:
957     virtual void activateURL(mstring url, bool relative = false) = 0;
958     virtual void openBrowser(mstring url) = 0;
959     virtual void handleClose() = 0;
960 protected:
~HTListener()961     virtual ~HTListener() {}
962 };
963 
964 class ActionItem : public YAction {
965 private:
966     YMenuItem   *item;
967     ActionItem(const ActionItem&);
968     ActionItem& operator=(const ActionItem&);
969 public:
ActionItem()970     ActionItem() : item(nullptr) {}
~ActionItem()971     ~ActionItem() { }
operator =(YMenuItem * menuItem)972     void operator=(YMenuItem* menuItem) { item = menuItem; }
operator ->()973     YMenuItem* operator->() { return item; }
974 };
975 
976 class HTextView: public YWindow,
977     public YScrollBarListener,
978     public YScrollable,
979     public YActionListener,
980     public YInputListener
981 {
982 public:
983     HTextView(HTListener *fL, YScrollView *v, YWindow *parent);
984     ~HTextView();
985 
986     void find_link(node *n);
987 
setData(node * root)988     void setData(node *root) {
989         delete fRoot;
990         fRoot = root;
991         tx = ty = 0;
992         layout();
993 
994         actionPrev->setEnabled(false);
995         actionNext->setEnabled(false);
996         actionContents->setEnabled(false);
997 
998         nextURL = null;
999         prevURL = null;
1000         contentsURL = null;
1001 
1002         find_link(fRoot);
1003         if (contentsURL == null) {
1004             node *n = fRoot->find_attr(attr::id | attr::name, "toc");
1005             if (n) {
1006                 contentsURL = "#toc";
1007                 actionContents->setEnabled(true);
1008             }
1009         }
1010 
1011         actionIndex->setEnabled(history.hasFirst());
1012         actionLeft->setEnabled(history.hasLeft());
1013         actionRight->setEnabled(history.hasRight());
1014     }
1015 
1016 
1017     void par(int &state, int &x, int &y, unsigned &h, const int left);
1018     void epar(int &state, int &x, int &y, unsigned &h, const int left);
1019     void layout();
1020     void layout(node *parent, node *n1, int left, int right, int &x, int &y, unsigned &w, unsigned &h, int flags, int &state);
1021     void draw(Graphics &g, node *n1, bool isHref = false);
1022     node *find_node(node *n, int x, int y, node *&anchor, node::node_type type);
1023     void find_fragment(const char *frag);
1024     bool findNext(node* n1);
1025     void startSearch();
1026 
paint(Graphics & g,const YRect & r)1027     virtual void paint(Graphics &g, const YRect &r) {
1028         g.setColor(bg);
1029         g.fillRect(r.x(), r.y(), r.width(), r.height());
1030 
1031         if (ty < 20) {
1032             int sz = 19;
1033             g.setColor(reverseVideo ? normalFg.darker().reverse() : bg.darker());
1034             g.fillRect(width() - 20, 20 - ty - sz, sz, sz, 1);
1035             g.setColor(bg.brighter());
1036             for (int i = 0; i <= 2; ++i) {
1037                 g.drawLine(width() - 20 +  2, 20 - ty - 15 + i * 5,
1038                            width() - 20 + 16, 20 - ty - 15 + i * 5);
1039             }
1040         }
1041 
1042         g.setColor(normalFg);
1043         g.setFont(font);
1044 
1045         draw(g, fRoot);
1046     }
handleExpose(const XExposeEvent & expose)1047     virtual void handleExpose(const XExposeEvent& expose) {
1048     }
1049 
isFocusTraversable()1050     bool isFocusTraversable() {
1051         return true;
1052     }
1053 
repaint()1054     virtual void repaint() {
1055         graphicsBuffer.paint();
1056     }
1057 
configure(const YRect2 & r)1058     virtual void configure(const YRect2& r) {
1059         if (r.resized()) {
1060             resetScroll();
1061             repaint();
1062         }
1063     }
1064 
resetScroll()1065     void resetScroll() {
1066         fVerticalScroll->setValues(ty, height(), 0, contentHeight());
1067         fVerticalScroll->setBlockIncrement(max(40U, height()) / 2);
1068         fVerticalScroll->setUnitIncrement(font->height());
1069         fHorizontalScroll->setValues(tx, width(), 0, contentWidth());
1070         fHorizontalScroll->setBlockIncrement(max(40U, width()) / 2);
1071         fHorizontalScroll->setUnitIncrement(20);
1072         if (fScrollView)
1073             fScrollView->layout();
1074     }
1075 
setPos(int x,int y)1076     void setPos(int x, int y) {
1077         if (x != tx || y != ty) {
1078             int dx = x - tx;
1079             int dy = y - ty;
1080 
1081             tx = x;
1082             ty = y;
1083 
1084             graphicsBuffer.scroll(dx, dy);
1085         }
1086     }
1087 
scroll(YScrollBar * sb,int delta)1088     virtual void scroll(YScrollBar *sb, int delta) {
1089         if (sb == fHorizontalScroll)
1090             setPos(tx + delta, ty);
1091         else if (sb == fVerticalScroll)
1092             setPos(tx, ty + delta);
1093     }
move(YScrollBar * sb,int pos)1094     virtual void move(YScrollBar *sb, int pos) {
1095         if (sb == fHorizontalScroll)
1096             setPos(pos, ty);
1097         else if (sb == fVerticalScroll)
1098             setPos(tx, pos);
1099     }
1100 
contentWidth()1101     unsigned contentWidth() {
1102         return conWidth;
1103     }
contentHeight()1104     unsigned contentHeight() {
1105         return conHeight;
1106     }
1107 
1108     virtual void handleClick(const XButtonEvent &up, int /*count*/);
1109 
actionPerformed(YAction action,unsigned int modifiers=0)1110     virtual void actionPerformed(YAction action, unsigned int modifiers = 0) {
1111         if (action == actionClose) {
1112             listener->handleClose();
1113         }
1114         else if (action == actionBrowser) {
1115             listener->openBrowser(history.current());
1116         }
1117         else if (action == actionReload) {
1118             listener->activateURL(history.current(), false);
1119         }
1120         else if (action == actionNext) {
1121             if (actionNext->isEnabled())
1122                 listener->activateURL(nextURL, true);
1123         }
1124         else if (action == actionPrev) {
1125             if (actionPrev->isEnabled())
1126                 listener->activateURL(prevURL, true);
1127         }
1128         else if (action == actionContents) {
1129             if (actionContents->isEnabled())
1130                 listener->activateURL(contentsURL, true);
1131         }
1132         else if (action == actionIndex) {
1133             if (actionIndex->isEnabled() && history.first())
1134                 listener->activateURL(history.current());
1135         }
1136         else if (action == actionLeft) {
1137             if (actionLeft->isEnabled() && history.left())
1138                 listener->activateURL(history.current());
1139         }
1140         else if (action == actionRight) {
1141             if (actionRight->isEnabled() && history.right())
1142                 listener->activateURL(history.current());
1143         }
1144         else if (action == actionLink[0]) {
1145             if (actionLink[0]->isEnabled())
1146                 listener->activateURL(ICEWM_1);
1147         }
1148         else if (action == actionLink[1]) {
1149             if (actionLink[1]->isEnabled())
1150                 listener->activateURL(ICEWMBG_1);
1151         }
1152         else if (action == actionLink[2]) {
1153             if (actionLink[2]->isEnabled())
1154                 listener->activateURL(ICESOUND_1);
1155         }
1156         else if (action == actionLink[3]) {
1157             if (actionLink[3]->isEnabled())
1158                 listener->activateURL(ICEWM_FAQ);
1159         }
1160         else if (action == actionLink[4]) {
1161             if (actionLink[4]->isEnabled())
1162                 listener->activateURL(ICEHELPIDX);
1163         }
1164         else if (action == actionLink[5]) {
1165             if (actionLink[5]->isEnabled())
1166                 listener->activateURL(PACKAGE_BUGREPORT);
1167         }
1168         else if (action == actionLink[6]) {
1169             if (actionLink[6]->isEnabled())
1170                 listener->activateURL(THEME_HOWTO);
1171         }
1172         else if (action == actionLink[7]) {
1173             if (actionLink[7]->isEnabled())
1174                 listener->activateURL(ICEWM_SITE);
1175         }
1176         else if (action == actionLink[8]) {
1177             if (actionLink[8]->isEnabled())
1178                 listener->activateURL(ICEGIT_SITE);
1179         }
1180 #ifdef CONFIG_XFREETYPE
1181         else if (action == actionFreeType) {
1182             noFreeType = !noFreeType;
1183             actionFreeType->setChecked(!noFreeType);
1184             FontTable::reset();
1185             actionPerformed(actionReload);
1186         }
1187 #endif
1188         else if (action == actionReversed) {
1189             reverseVideo = !reverseVideo;
1190             actionReversed->setChecked(reverseVideo);
1191             if (reverseVideo) {
1192                 bg = FOREGROUND;
1193                 normalFg = BACKGROUND;
1194                 linkFg = LINK_COLOR;
1195                 linkFg.reverse();
1196             } else {
1197                 bg = BACKGROUND;
1198                 normalFg = FOREGROUND;
1199                 linkFg = LINK_COLOR;
1200             }
1201             repaint();
1202             YScrollBar::reverseVideo();
1203             if (fHorizontalScroll->visible())
1204                 fHorizontalScroll->repaint();
1205             if (fVerticalScroll->visible())
1206                 fVerticalScroll->repaint();
1207         }
1208         else if (action == actionFind) {
1209             if (input == nullptr) {
1210                 input = new YInputLine(this, this);
1211             }
1212             if (input) {
1213                 input->setBorderWidth(2);
1214                 input->setGeometry(YRect(1, 1, 250, 25));
1215                 input->show();
1216                 xapp->sync();
1217                 setFocus(input);
1218                 input->requestFocus(true);
1219                 input->gotFocus();
1220             }
1221         }
1222         else if (action == actionSearch) {
1223             startSearch();
1224         }
1225     }
1226 
1227     bool handleKey(const XKeyEvent &key);
1228     void handleButton(const XButtonEvent &button);
1229     void addHistory(const mstring& path);
1230 private:
1231     node *fRoot;
1232 
1233     int tx, ty;
1234     unsigned conWidth;
1235     unsigned conHeight;
1236 
1237     YFont font;
1238     int fontFlag, fontSize;
1239     YColorName bg, normalFg, linkFg, hrFg;
1240 
1241     YScrollView *fScrollView;
1242     YScrollBar *fVerticalScroll;
1243     YScrollBar *fHorizontalScroll;
1244 
1245     YMenu *menu;
1246     ActionItem actionClose;
1247     ActionItem actionLeft;
1248     ActionItem actionRight;
1249     ActionItem actionIndex;
1250     ActionItem actionFind;
1251     ActionItem actionSearch;
1252     ActionItem actionPrev;
1253     ActionItem actionNext;
1254     ActionItem actionReload;
1255     ActionItem actionBrowser;
1256     ActionItem actionContents;
1257     ActionItem actionLink[10];
1258     ActionItem actionFreeType;
1259     ActionItem actionReversed;
1260     HTListener *listener;
1261 
1262     mstring search;
1263     mstring prevURL;
1264     mstring nextURL;
1265     mstring contentsURL;
1266     History history;
1267     GraphicsBuffer graphicsBuffer;
1268 
flagFont(int n)1269     void flagFont(int n) {
1270         int mask = (n & (MONO | BOLD | ITAL)) | ((n & PRE) ? MONO : 0);
1271         int size = (n & BIG) ? 18 : 14;
1272         if (mask != fontFlag || size != fontSize) {
1273             font = FontTable::get(size, mask);
1274             fontSize = size;
1275             fontFlag = mask;
1276         }
1277     }
1278     class Flags {
1279     public:
1280         HTextView *view;
1281         const int oldf, newf;
Flags(HTextView * v,int o,int n)1282         Flags(HTextView *v, int o, int n) : view(v), oldf(o), newf(n) {
1283             view->flagFont(newf);
1284         }
~Flags()1285         ~Flags() { view->flagFont(oldf); }
operator int() const1286         operator int() const { return newf; }
1287     };
1288 
inputReturn(YInputLine * inputline)1289     void inputReturn(YInputLine* inputline) {
1290         search = input->getText();
1291         delete input; input = nullptr;
1292         startSearch();
1293     }
inputEscape(YInputLine * inputline)1294     void inputEscape(YInputLine* inputline) {
1295         delete input; input = nullptr;
1296     }
inputLostFocus(YInputLine * inputline)1297     void inputLostFocus(YInputLine* inputline) {
1298         delete input; input = nullptr;
1299     }
1300     YInputLine* input;
1301 };
1302 
HTextView(HTListener * fL,YScrollView * v,YWindow * parent)1303 HTextView::HTextView(HTListener *fL, YScrollView *v, YWindow *parent):
1304     YWindow(parent), fRoot(nullptr),
1305     bg(BACKGROUND),
1306     normalFg(FOREGROUND),
1307     linkFg(LINK_COLOR),
1308     hrFg(RULE_COLOR),
1309     fScrollView(v),
1310     fVerticalScroll(v->getVerticalScrollBar()),
1311     fHorizontalScroll(v->getHorizontalScrollBar()),
1312     listener(fL),
1313     graphicsBuffer(this),
1314     input(nullptr)
1315 {
1316     fScrollView->setListener(this);
1317     tx = ty = 0;
1318     conWidth = conHeight = 0;
1319     fontFlag = 0;
1320     fontSize = 0;
1321     flagFont(0);
1322     setTitle("HTextView");
1323 
1324     menu = new YMenu();
1325     menu->setActionListener(this);
1326     actionLeft = menu->addItem(_("Back"), 0, _("Alt+Left"), actionLeft);
1327     actionLeft->setEnabled(false);
1328     actionRight = menu->addItem(_("Forward"), 0, _("Alt+Right"), actionRight);
1329     actionRight->setEnabled(false);
1330     menu->addSeparator();
1331     actionPrev = menu->addItem(_("Previous"), 0, null, actionPrev);
1332     actionNext = menu->addItem(_("Next"), 0, null, actionNext);
1333     menu->addSeparator();
1334     actionContents = menu->addItem(_("Contents"), 0, null, actionContents);
1335     actionIndex = menu->addItem(_("Index"), 0, null, actionIndex);
1336     actionIndex->setEnabled(false);
1337     actionFind = menu->addItem(_("Find..."), 0, _("Ctrl+F"), actionFind);
1338     actionSearch = menu->addItem(_("Find next"), 0, _("F3"), actionSearch);
1339     menu->addSeparator();
1340     actionBrowser = menu->addItem(_("Open in Browser"), 0, _("Ctrl+B"),
1341                                   actionBrowser);
1342     actionReload = menu->addItem(_("Reload"), 0, _("Ctrl+R"), actionReload);
1343     actionClose = menu->addItem(_("Close"), 0, _("Ctrl+Q"), actionClose);
1344     menu->addSeparator();
1345 
1346     int k = -1;
1347     k++;
1348     actionLink[k] = menu->addItem(_("Icewm(1)"), 2, _("Ctrl+I"), actionLink[k]);
1349     k++;
1350     actionLink[k] = menu->addItem(_("Icewmbg(1)"), 5, null, actionLink[k]);
1351     k++;
1352     actionLink[k] = menu->addItem(_("Icesound(1)"), 3, null, actionLink[k]);
1353     k++;
1354     actionLink[k] = menu->addItem(_("FAQ"), 0, null, actionLink[k]);
1355     k++;
1356     actionLink[k] = menu->addItem(_("Manual"), 0, _("Ctrl+M"), actionLink[k]);
1357     k++;
1358     actionLink[k] = menu->addItem(_("Support"), 0, null, actionLink[k]);
1359     k++;
1360     actionLink[k] = menu->addItem(_("Theme Howto"), 0, _("Ctrl+T"), actionLink[k]);
1361     k++;
1362     actionLink[k] = menu->addItem(_("Website"), 0, _("Ctrl+W"), actionLink[k]);
1363     k++;
1364     actionLink[k] = menu->addItem(_("Github"), 0, null, actionLink[k]);
1365 
1366     menu->addSeparator();
1367 #ifdef CONFIG_XFREETYPE
1368     actionFreeType = menu->addItem("FreeType fonts", -1, _("Ctrl+X"), actionFreeType);
1369     actionFreeType->setChecked(!noFreeType);
1370 #else
1371     noFreeType = true;
1372 #endif
1373     actionReversed = menu->addItem("Reverse video", -1, _("Ctrl+V"), actionReversed);
1374     actionReversed->setChecked(reverseVideo);
1375 }
1376 
~HTextView()1377 HTextView::~HTextView() {
1378     delete fRoot;
1379     delete input;
1380     delete menu;
1381 }
1382 
addHistory(const mstring & path)1383 void HTextView::addHistory(const mstring& path) {
1384     history.push(path);
1385     actionLeft->setEnabled(history.hasLeft());
1386     actionRight->setEnabled(history.hasRight());
1387     actionIndex->setEnabled(history.hasFirst());
1388 }
1389 
find_node(node * n,int x,int y,node * & anchor,node::node_type type)1390 node *HTextView::find_node(node *n, int x, int y, node *&anchor, node::node_type type) {
1391 
1392     for (; n; n = n->next) {
1393         if (n->container) {
1394             node *f = find_node(n->container, x, y, anchor, type);
1395             if (f) {
1396                 if (anchor == nullptr && n->type == type)
1397                     anchor = n;
1398                 return f;
1399             }
1400         }
1401         for (text_node *t = n->wrap; t; t = t->next) {
1402             if (x >= t->x && x < t->x + t->tw &&
1403                 y < t->y && y > t->y - t->th)
1404                 return n;
1405         }
1406     }
1407     return nullptr;
1408 }
1409 
find_link(node * n)1410 void HTextView::find_link(node *n) {
1411     for (; n; n = n->next) {
1412         if (n->type == node::link) {
1413             attr rel, href;
1414             if (n->get_attribute("rel", &rel) &&
1415                 n->get_attribute("href", &href))
1416             {
1417                 if (strcasecmp(rel.value, "previous") == 0) {
1418                     prevURL = href.value;
1419                     actionPrev->setEnabled(true);
1420                 }
1421                 if (strcasecmp(rel.value, "next") == 0) {
1422                     nextURL = href.value;
1423                     actionNext->setEnabled(true);
1424                 }
1425                 if (strcasecmp(rel.value, "contents") == 0) {
1426                     contentsURL = href.value;
1427                     actionContents->setEnabled(true);
1428                 }
1429             }
1430         }
1431         if (n->container)
1432             find_link(n->container);
1433     }
1434 }
1435 
find_fragment(const char * frag)1436 void HTextView::find_fragment(const char *frag) {
1437     node *n = fRoot->find_attr(attr::id | attr::name, frag);
1438     if (n) {
1439         int y = max(0, min(n->yr, (int) contentHeight() - (int) height()));
1440         setPos(0, y);
1441         fVerticalScroll->setValue(y);
1442         fHorizontalScroll->setValue(0);
1443     }
1444 }
1445 
layout()1446 void HTextView::layout() {
1447     int state = sfPar;
1448     int x = ViewerLeftMargin, y = ViewerTopMargin;
1449     int left = x, right = width() - x;
1450     conWidth = conHeight = 0;
1451     layout(nullptr, fRoot, left, right, x, y, conWidth, conHeight, 0, state);
1452     conHeight += font->height();
1453     resetScroll();
1454 }
1455 
addState(int & state,int value)1456 void addState(int &state, int value) {
1457     state |= value;
1458     //msg("addState=%d %d", state, value);
1459 }
1460 
removeState(int & state,int value)1461 void removeState(int &state, int value) {
1462     state &= ~value;
1463     //msg("removeState=%d %d", state, value);
1464 }
1465 
par(int & state,int & x,int & y,unsigned & h,const int left)1466 void HTextView::par(int &state, int &x, int &y, unsigned &h, const int left) {
1467     if (!(state & sfPar)) {
1468         h += font->height();
1469         x = left;
1470         y = h;
1471         addState(state, sfPar);
1472     }
1473 }
1474 
epar(int & state,int & x,int & y,unsigned & h,const int left)1475 void HTextView::epar(int &state, int &x, int &y, unsigned &h, const int left) {
1476     if ((x > left) || ((state & (sfText | sfPar)) == sfText)) {
1477         h += font->height();
1478         x = left;
1479         y = h;
1480         removeState(state, sfText);
1481     }
1482     removeState(state, sfPar);
1483 }
1484 
layout(node * parent,node * n1,int left,int right,int & x,int & y,unsigned & w,unsigned & h,int flags,int & state)1485 void HTextView::layout(
1486         node *parent, node *n1, int left, int right,
1487         int &x, int &y, unsigned &w, unsigned &h,
1488         int flags, int &state)
1489 {
1490     ///puts("{");
1491     for (node *n = n1; n; n = n->next) {
1492         n->xr = x;
1493         n->yr = y;
1494         switch (n->type) {
1495         case node::head:
1496         case node::meta:
1497         case node::title:
1498         case node::script:
1499         case node::style:
1500             break;
1501         case node::h1:
1502         case node::h2:
1503         case node::h3:
1504         case node::h4:
1505         case node::h5:
1506         case node::h6:
1507             if (x > left) {
1508                 x = left;
1509                 y = h + font->height();
1510             }
1511             removeState(state, sfPar);
1512             x = left;
1513             n->xr = x;
1514             n->yr = y;
1515             if (n->container) {
1516                 int h1bold = n->type == node::h1 ? BOLD : 0;
1517                 Flags fl(this, flags, flags | BIG | h1bold);
1518                 layout(n, n->container, n->xr, right, x, y, w, h, fl, state);
1519             }
1520             x = left;
1521             y = h + font->height();
1522             addState(state, sfText);
1523             removeState(state, sfPar);
1524             break;
1525         case node::text:
1526             n->wrap.destroy();
1527             for (const char *b = n->txt, *c; *b; b = c) {
1528                 int wc = 0;
1529 
1530                 if (flags & PRE) {
1531                     c = b;
1532                     while (*c && *c != '\n')
1533                         c++;
1534                     wc = font->textWidth(b, c - b);
1535                 } else {
1536                     if (x == left)
1537                         while (SPACE(*b))
1538                             b++;
1539 
1540                     c = b;
1541 
1542                     do {
1543                         const char *d = c;
1544 
1545                         if (SPACE(*d))
1546                             while (SPACE(*d))
1547                                 d++;
1548                         else
1549                             while (*d && !SPACE(*d))
1550                                 d++;
1551 
1552                         int w1 = font->textWidth(b, d - b);
1553 
1554                         if (x + w1 < right) {
1555                             wc = w1;
1556                             c = d;
1557                         } else if (x == left && wc == 0) {
1558                             wc = w1;
1559                             c = d;
1560                             break;
1561                         } else if (wc == 0) {
1562                             x = left;
1563                             y = h;
1564                             while (SPACE(*c))
1565                                 c++;
1566                             b = c;
1567                         } else
1568                             break;
1569                         ///msg("width=%d %d / %d / %d", wc, x, left, right);
1570                     } while (*c);
1571                 }
1572 
1573                 if (!(flags & PRE) && x == left) {
1574                     while (SPACE(*b)) b++;
1575                 }
1576                 if ((flags & PRE) || c - b > 0) {
1577                     par(state, x, y, h, left);
1578                     addState(state, sfText);
1579 
1580                     n->wrap.add(new text_node(b, c - b, flags,
1581                                 x, y + 12, wc, font->height()));
1582                     if (y + (int)font->height() > (int)h)
1583                         h = y + font->height();
1584 
1585                     x += wc;
1586                     if (x > (int)w)
1587                         w = x;
1588 
1589                     if ((flags & PRE)) {
1590                         if (*c == '\n') {
1591                             c++;
1592                             x = left;
1593                             y = h;
1594                         }
1595                     }
1596                 }
1597                 //msg("len=%d x=%d left=%d %s", c - b, x, left, b);
1598             }
1599             break;
1600         case node::line:
1601             //puts("<BR>");
1602             if (x != left) {
1603                 y = h;
1604                 //h += font->height();
1605             } else {
1606                 h += font->height();
1607                 y = h;
1608             }
1609             x = left;
1610             break;
1611         case node::paragraph:
1612             if (parent) {
1613                 if (parent->type != node::dd && parent->type != node::li) {
1614                     removeState(state, sfPar);
1615                 }
1616             }
1617             //puts("<P>");
1618             //par(state, x, y, h, left);
1619             x = left;
1620             y = h;
1621             n->xr = x;
1622             n->yr = y;
1623             break;
1624         case node::hrule:
1625             //puts("<HR>");
1626             //epar(state, x, y, h, left);
1627             if ((state & sfText) && !(state & sfPar))
1628                 h += font->height();
1629             x = left;
1630             n->xr = x;
1631             n->yr = h;
1632             h += 10;
1633             y = h;
1634             addState(state, sfPar);
1635             break;
1636         case node::center:
1637             x = left;
1638             y = h;
1639             if (n->container) {
1640                 layout(n, n->container, n->xr, right, x, y, w, h, flags, state);
1641                 // !!! center
1642             }
1643             h += font->height();
1644             x = left;
1645             y = h;
1646             break;
1647         case node::anchor:
1648             if (n->container) {
1649                 layout(n, n->container, left, right, x, y, w, h, flags, state);
1650             }
1651             break;
1652         case node::ul:
1653         case node::ol:
1654             //puts("<UL>");
1655             //epar(state, x, y, h, left);
1656             if (!(parent && parent->type == node::li)) {
1657                 removeState(state, sfPar);
1658             }
1659             y = h;
1660             x = left;
1661             n->xr = x;
1662             n->yr = y;
1663             x += 40;
1664             if (n->container) {
1665                 layout(n, n->container, x, right, x, y, w, h, flags, state);
1666             }
1667             addState(state, sfText);
1668             x = left;
1669             y = h;
1670             //state &= ~sfPar;
1671             //par(state, x, y, h, left);
1672             //x = left;
1673             //y = h + font->height();
1674             removeState(state, sfPar);
1675             addState(state, sfText);
1676             break;
1677         case node::dl:
1678             //puts("<UL>");
1679             epar(state, x, y, h, left);
1680             n->xr = x;
1681             n->yr = y;
1682             if (parent && parent->type == node::dt)
1683                 x += 40;
1684             if (n->container) {
1685                 //puts("<DL>\n");
1686                 layout(n, n->container, x, right, x, y, w, h, flags, state);
1687                 //puts("</DL>\n");
1688             }
1689             addState(state, sfText);
1690             x = left;
1691             y = h;
1692             removeState(state, sfPar);
1693             addState(state, sfText);
1694             //state &= ~sfPar;
1695             //par(state, x, y, h, left);
1696             //x = left;
1697             //y = h + font->height();
1698             break;
1699         case node::dt:
1700             //puts("<LI>");
1701             //if (state & sfText)
1702             y = h;
1703             x = left;
1704             n->xr = x;
1705             n->yr = y;
1706 
1707             addState(state, sfPar);
1708             if (n->container) {
1709                 layout(n, n->container, left, right, x, y, w, h, flags, state);
1710             }
1711             y = h;
1712             x = left;
1713             //removeState(state, sfPar | sfText);
1714             break;
1715         case node::dd:
1716             //puts("<DT>");
1717             //if (state & sfText)
1718             epar(state, x, y, h, left);
1719             y = h;
1720             x = left + 40;
1721             n->xr = x;
1722             n->yr = y;
1723 
1724             addState(state, sfPar);
1725             if (n->container) {
1726                 //puts("<DD>\n");
1727                 layout(n, n->container, x, right, x, y, w, h, flags, state);
1728                 //puts("</DD>\n");
1729                 h += font->height();
1730             }
1731             y = h;
1732             x = left;
1733             //removeState(state, sfPar | sfText);
1734             break;
1735 
1736         case node::li:
1737             //puts("<LI>");
1738 
1739             //msg("state=%d", state);
1740             epar(state, x, y, h, left);
1741             //if (state & sfText)
1742             y = h;
1743             n->xr = x - 12;
1744             n->yr = y;
1745 
1746             addState(state, sfPar);
1747             if (n->container) {
1748                 layout(n, n->container, left, right, x, y, w, h, flags, state);
1749             }
1750             y = h;
1751             x = left;
1752             //removeState(state, sfPar | sfText);
1753             break;
1754         case node::pre:
1755             if (x > left) {
1756                 x = left;
1757                 h += font->height();
1758                 y = h;
1759             }
1760             if (n->container) {
1761                 Flags fl(this, flags, flags | PRE);
1762                 layout(n, n->container, x, right, x, y, w, h, fl, state);
1763             }
1764             y = h;
1765             x = left;
1766             removeState(state, sfPar);
1767             addState(state, sfText);
1768             //msg("set=%d", state);
1769             break;
1770         case node::bold:
1771             if (n->container) {
1772                 Flags fl(this, flags, flags | BOLD);
1773                 layout(n, n->container, left, right, x, y, w, h, fl, state);
1774             }
1775             break;
1776         case node::italic:
1777             if (n->container) {
1778                 Flags fl(this, flags, flags | ITAL);
1779                 layout(n, n->container, left, right, x, y, w, h, fl, state);
1780             }
1781             break;
1782         case node::tt:
1783         case node::code:
1784             if (n->container) {
1785                 Flags fl(this, flags, flags | MONO);
1786                 layout(n, n->container, left, right, x, y, w, h, fl, state);
1787             }
1788             break;
1789         case node::html:
1790         case node::body:
1791         case node::div:
1792         case node::table:
1793         case node::tr:
1794         case node::td:
1795         case node::thead:
1796         case node::tbody:
1797         case node::tfoot:
1798         case node::img:
1799         case node::unknown:
1800         case node::link:
1801         case node::form:
1802         case node::input:
1803         case node::section:
1804         case node::figure:
1805         case node::aside:
1806         case node::footer:
1807         case node::main:
1808             if (n->container) {
1809                 layout(n, n->container, left, right, x, y, w, h, flags, state);
1810             }
1811             break;
1812         default:
1813             if (complain) {
1814                 tlog("default layout for node type %s", node::to_string(n->type));
1815             }
1816             if (n->container)
1817                 layout(n, n->container, left, right, x, y, w, h, flags, state);
1818             break;
1819         }
1820     }
1821     ///puts("}");
1822 }
1823 
draw(Graphics & g,node * n1,bool isHref)1824 void HTextView::draw(Graphics &g, node *n1, bool isHref) {
1825     for (node *n = n1; n; n = n->next) {
1826         switch (n->type) {
1827         case node::head:
1828         case node::title:
1829         case node::script:
1830         case node::style:
1831             break;
1832 
1833         case node::text:
1834             for (text_node *t = n->wrap; t; t = t->next) {
1835                 flagFont(t->fl);
1836                 g.setFont(font);
1837                 int y = t->y + t->th - font->height() - ty - 5;
1838                 g.drawChars(t->text, 0, t->len, t->x - tx, y);
1839                 if (isHref) {
1840                     g.drawLine(t->x - tx, y + 1, t->x + t->tw - tx, y + 1);
1841                 }
1842             }
1843             break;
1844 
1845         case node::hrule:
1846             g.setColor(hrFg);
1847             g.drawLine(0 + n->xr - tx, n->yr + 4 - ty, width() - 1 - tx, n->yr + 4 - ty);
1848             g.drawLine(0 + n->xr - tx, n->yr + 5 - ty, width() - 1 - tx, n->yr + 5 - ty);
1849             g.setColor(normalFg);
1850             break;
1851 
1852         case node::anchor:
1853             {
1854                 attr href;
1855                 bool found = n->get_attribute("href", &href);
1856                 if (found && href.value.length())
1857                     g.setColor(linkFg);
1858                 if (n->container)
1859                     draw(g, n->container, found || isHref);
1860                 if (found && isHref == false)
1861                     g.setColor(normalFg);
1862             }
1863             break;
1864 
1865         case node::li:
1866             g.fillArc(n->xr - tx, n->yr - ty - 2, 7, 7, 0, 360 * 64);
1867             if (n->container)
1868                 draw(g, n->container, isHref);
1869             break;
1870 
1871         default:
1872             if (n->container)
1873                 draw(g, n->container, isHref);
1874             break;
1875         }
1876     }
1877 }
1878 
findNext(node * n1)1879 bool HTextView::findNext(node *n1) {
1880     for (node *n = n1; n; n = n->next) {
1881         if (n->container) {
1882             if (findNext(n->container))
1883                 return true;
1884         }
1885         for (text_node *tn = n->wrap; tn; tn = tn->next) {
1886             if (ty < tn->y - tn->th) {
1887                 const char* found = strstr(tn->text, search.c_str());
1888                 if (found && found < tn->text + tn->len) {
1889                     setPos(0, tn->y - tn->th);
1890                     return true;
1891                 }
1892             }
1893         }
1894     }
1895     return false;
1896 }
1897 
startSearch()1898 void HTextView::startSearch() {
1899     while (search.nonempty() && 0 < search[0] && search[0] < ' ') {
1900         search = search.substring(1);
1901     }
1902     if (search.nonempty()) {
1903         if (findNext(fRoot)) {
1904             resetScroll();
1905         }
1906     }
1907 }
1908 
handleKey(const XKeyEvent & key)1909 bool HTextView::handleKey(const XKeyEvent &key) {
1910     if (fScrollView->handleScrollKeys(key)) {
1911         return true;
1912     }
1913     if (input && input->visible()) {
1914         return input->handleKey(key);
1915     }
1916 
1917     if (key.type == KeyPress) {
1918         KeySym k = keyCodeToKeySym(key.keycode);
1919         int m = KEY_MODMASK(key.state);
1920         if ((m & ControlMask) != 0 && (m & ~ControlMask) == 0) {
1921             if (k == XK_q) {
1922                 actionPerformed(actionClose);
1923                 return true;
1924             }
1925             if (k == XK_b) {
1926                 actionPerformed(actionBrowser);
1927                 return true;
1928             }
1929             if (k == XK_r) {
1930                 actionPerformed(actionReload);
1931                 return true;
1932             }
1933             if (k == XK_i) {
1934                 actionPerformed(actionLink[0]);
1935                 return true;
1936             }
1937             if (k == XK_m) {
1938                 actionPerformed(actionLink[4]);
1939                 return true;
1940             }
1941             if (k == XK_t) {
1942                 actionPerformed(actionLink[6]);
1943                 return true;
1944             }
1945             if (k == XK_w) {
1946                 actionPerformed(actionLink[7]);
1947                 return true;
1948             }
1949             if (k == XK_v) {
1950                 actionPerformed(actionReversed);
1951                 return true;
1952             }
1953             if (k == XK_x) {
1954                 actionPerformed(actionFreeType);
1955                 return true;
1956             }
1957             if (k == XK_f) {
1958                 actionPerformed(actionFind);
1959                 return true;
1960             }
1961         }
1962         if ((m & xapp->AltMask) != 0 && (m & ~xapp->AltMask) == 0) {
1963             if (k == XK_Left || k == XK_KP_Left) {
1964                 actionPerformed(actionLeft);
1965                 return true;
1966             }
1967             if (k == XK_Right || k == XK_KP_Right) {
1968                 actionPerformed(actionRight);
1969                 return true;
1970             }
1971         }
1972         if (notbit(m, ControlMask | xapp->AltMask | ShiftMask)) {
1973             if (k == XK_F3) {
1974                 startSearch();
1975                 return true;
1976             }
1977         }
1978     }
1979     return false;
1980 }
1981 
handleButton(const XButtonEvent & button)1982 void HTextView::handleButton(const XButtonEvent &button) {
1983     if (fVerticalScroll->handleScrollMouse(button) == false)
1984         YWindow::handleButton(button);
1985 }
1986 
handleClick(const XButtonEvent & up,int)1987 void HTextView::handleClick(const XButtonEvent &up, int /*count*/) {
1988     if (up.button == Button3) {
1989         menu->popup(nullptr, nullptr, nullptr, up.x_root, up.y_root,
1990                     YPopupWindow::pfCanFlipVertical |
1991                     YPopupWindow::pfCanFlipHorizontal /*|
1992                     YPopupWindow::pfPopupMenu*/);
1993     }
1994     else if (up.button == Button1 && up.x >= int(width()) - 20 && up.y <= 20 - ty) {
1995         menu->popup(this, nullptr, nullptr,
1996                     up.x_root - up.x + int(width()) - 1,
1997                     up.y_root - up.y,
1998                     YPopupWindow::pfFlipHorizontal);
1999     }
2000     else if (up.button == Button1) {
2001         node *anchor = nullptr;
2002         node *n = find_node(fRoot, up.x + tx, up.y + ty, anchor, node::anchor);
2003         if (n && anchor) {
2004             attr href;
2005             if (anchor->get_attribute("href", &href) && href.value) {
2006                 listener->activateURL(href.value, true);
2007             }
2008         }
2009     }
2010 }
2011 
2012 class FileView: public YDndWindow, public HTListener {
2013 public:
2014     FileView(YApplication *app, int argc, char **argv);
~FileView()2015     ~FileView() {
2016         delete view;
2017         delete scroll;
2018         FontTable::reset();
2019         extern void clearFontCache();
2020         clearFontCache();
2021     }
2022 
2023     void activateURL(mstring url, bool relative = false) override;
2024     void openBrowser(mstring url) override;
2025 
configure(const YRect2 & r)2026     void configure(const YRect2& r) override {
2027         if (r.resized()) {
2028             scroll->setGeometry(YRect(0, 0, r.width(), r.height()));
2029         }
2030     }
2031 
handleClose()2032     void handleClose() override {
2033         if (ignoreClose == false) {
2034             app->exitLoop(0);
2035         }
2036     }
handleExpose(const XExposeEvent & expose)2037     void handleExpose(const XExposeEvent& expose) override {
2038     }
2039 
isFocusTraversable()2040     bool isFocusTraversable() override {
2041         return true;
2042     }
2043 
2044 private:
2045     bool loadFile(upath path);
2046     bool loadHttp(upath path);
2047     void invalidPath(upath path, const char *reason);
2048     void run(const char* path, const char* arg1 = nullptr,
2049              const char* arg2 = nullptr, const char* arg3 = nullptr);
2050 
handleKey(const XKeyEvent & key)2051     bool handleKey(const XKeyEvent &key) override {
2052         return view->handleKey(key);
2053     }
2054 
2055     upath fPath;
2056     YApplication *app;
2057 
2058     HTextView *view;
2059     YScrollView *scroll;
2060     ref<YPixmap> small_icon;
2061     ref<YPixmap> large_icon;
2062 };
2063 
FileView(YApplication * iapp,int argc,char ** argv)2064 FileView::FileView(YApplication *iapp, int argc, char **argv)
2065     : fPath(), app(iapp), view(nullptr), scroll(nullptr)
2066 {
2067     setDND(true);
2068     setStyle(wsNoExpose);
2069     setTitle("IceHelp");
2070 
2071     scroll = new YScrollView(this);
2072     view = new HTextView(this, scroll, scroll);
2073     scroll->setView(view);
2074 
2075     view->show();
2076     scroll->show();
2077 
2078     setSize(ViewerWidth, ViewerHeight);
2079 
2080     ref<YIcon> file_icon = YIcon::getIcon("file");
2081     if (file_icon != null) {
2082         Pixmap icons[4];
2083         int count = 0;
2084         if (file_icon->small() != null) {
2085             small_icon = YPixmap::createFromImage(file_icon->small(), xapp->depth());
2086             if (small_icon != null) {
2087                 icons[count++] = small_icon->pixmap();
2088                 icons[count++] = small_icon->mask();
2089             }
2090         }
2091         if (file_icon->large() != null) {
2092             large_icon = YPixmap::createFromImage(file_icon->large(), xapp->depth());
2093             if (large_icon != null) {
2094                 icons[count++] = large_icon->pixmap();
2095                 icons[count++] = large_icon->mask();
2096             }
2097         }
2098         if (count > 0) {
2099             extern Atom _XA_WIN_ICONS;
2100             setProperty(_XA_WIN_ICONS, XA_PIXMAP, icons, count);
2101         }
2102     }
2103     setNetPid();
2104 
2105     XWMHints wmhints = {};
2106     wmhints.flags = InputHint | StateHint;
2107     wmhints.input = True;
2108     wmhints.initial_state = NormalState;
2109     if (large_icon != null) {
2110         wmhints.icon_pixmap = large_icon->pixmap();
2111         wmhints.icon_mask = large_icon->mask();
2112         wmhints.flags |= IconPixmapHint | IconMaskHint;
2113     }
2114     XSetWMHints(xapp->display(), handle(), &wmhints);
2115 
2116     YTextProperty name("icehelp");
2117     YTextProperty icon("icehelp");
2118     XSizeHints size = { PSize, 0, 0,
2119                         ViewerWidth, ViewerHeight,
2120                       };
2121     XClassHint klas = { const_cast<char *>("icehelp"),
2122                         const_cast<char *>("IceWM") };
2123     XSetWMProperties(xapp->display(), handle(),
2124                      &name, &icon, argv, argc,
2125                      &size, &wmhints, &klas);
2126 }
2127 
activateURL(mstring url,bool relative)2128 void FileView::activateURL(mstring url, bool relative) {
2129     if (verbose) {
2130         tlog("activateURL('%s', %s)", url.c_str(),
2131                 relative ? "relative" : "not-relative");
2132     }
2133 
2134     /*
2135      * Differentiate:
2136      * - has (only) a fragment (#).
2137      * - url is local/remote.
2138      * - if local: absolute or relative to previous path.
2139      */
2140 
2141     mstring path, frag;
2142     if (url.splitall('#', &path, &frag) == false ||
2143         path.length() + frag.length() == 0) {
2144         return; // empty
2145     }
2146 
2147     if (relative && path.nonempty() && false == upath(path).hasProtocol()) {
2148         if (upath(path).isRelative()) {
2149             int k = fPath.path().lastIndexOf('/');
2150             if (k >= 0) {
2151                 path = fPath.path().substring(0, k + 1) + path;
2152             }
2153         }
2154         else if (fPath.hasProtocol()) {
2155             int k = fPath.path().find("://");
2156             if (k > 0) {
2157                 int i = fPath.path().substring(k + 3).indexOf('/');
2158                 if (i > 0) {
2159                     path = fPath.path().substring(0, k + 3 + i) + path;
2160                 }
2161             }
2162         }
2163     }
2164     path = path.searchAndReplaceAll("/./", "/");
2165 
2166     if (path.length() > 0) {
2167         if (upath(path).hasProtocol()) {
2168             if (path.startsWith("file:///")) {
2169                 path = path.substring((int)strlen("file://"));
2170             }
2171         }
2172         if (upath(path).hasProtocol()) {
2173             if (upath(path).isHttp()) {
2174                 if (path.count('/') == 2) {
2175                     path += "/";
2176                 }
2177                 if (loadHttp(path) == false) {
2178                     return;
2179                 }
2180             }
2181             else {
2182                 return invalidPath(path, _("Unsupported protocol."));
2183             }
2184         }
2185         else if (loadFile(path) == false) {
2186             return;
2187         }
2188     }
2189     if (frag.length() > 0 && view->contentHeight() > view->height()) {
2190         // search
2191         view->find_fragment(frag);
2192     }
2193     view->repaint();
2194     if (frag.length() > 0) {
2195         path = path + "#" + frag;
2196     }
2197     if (relative && path.charAt(0) == '#') {
2198         path = fPath.path() + path;
2199     }
2200     view->addHistory(path);
2201     fPath = path;
2202     setNetName(path);
2203 }
2204 
openBrowser(mstring url)2205 void FileView::openBrowser(mstring url) {
2206     char* env = getenv("BROWSER");
2207     mstring brow(nonempty(env) && !strchr(env, '/')
2208                  ? mstring("/usr/bin/") + env : env);
2209     if (brow != null && upath(brow).isExecutable()) {
2210         run(brow, url);
2211     }
2212     else if (upath("/usr/bin/xdg-open").isExecutable()) {
2213         run("/usr/bin/xdg-open", url);
2214     }
2215     else if (upath("/usr/bin/gnome-open").isExecutable()) {
2216         run("/usr/bin/gnome-open", url);
2217     }
2218     else if (upath("/usr/bin/kde-open").isExecutable()) {
2219         run("/usr/bin/kde-open", url);
2220     }
2221     else if (upath("/usr/bin/gio").isExecutable()) {
2222         run("/usr/bin/gio", "open", url);
2223     }
2224     else if (upath("/usr/bin/python3").isExecutable()) {
2225         run("/usr/bin/python3", "-m", "webbrowser", url);
2226     }
2227     else if (upath("/usr/bin/python").isExecutable()) {
2228         run("/usr/bin/python", "-m", "webbrowser", url);
2229     }
2230     else if (upath("/usr/bin/sensible-browser").isExecutable()) {
2231         run("/usr/bin/sensible-browser", url);
2232     }
2233 }
2234 
run(const char * path,const char * arg1,const char * arg2,const char * arg3)2235 void FileView::run(const char* path, const char* arg1,
2236                    const char* arg2, const char* arg3)
2237 {
2238     char* args[] = {
2239         const_cast<char*>(path),
2240         const_cast<char*>(arg1),
2241         const_cast<char*>(arg2),
2242         const_cast<char*>(arg3),
2243         nullptr
2244     };
2245     switch (fork()) {
2246         case -1:
2247             fail("fork");
2248             break;
2249         case 0:
2250             if (execv(path, args) == -1) {
2251                 fail("exec %s", path);
2252             }
2253             break;
2254         default:
2255             break;
2256     }
2257 }
2258 
invalidPath(upath path,const char * reason)2259 void FileView::invalidPath(upath path, const char *reason) {
2260     const char *cstr = path.string();
2261     const char *cfmt = _("Invalid path: %s\n");
2262     const char *crea = reason;
2263     tlog(cfmt, cstr);
2264     tlog("%s", crea);
2265 
2266     const size_t size = 9 + strlen(cfmt) + strlen(cstr) + strlen(crea);
2267     char *cbuf = (char *)malloc(size);
2268     snprintf(cbuf, size, cfmt, cstr);
2269     strlcat(cbuf, ":\n ", size);
2270     strlcat(cbuf, crea, size);
2271 
2272     node *root = new node(node::div);
2273     flist<node> nodes(root);
2274 
2275     node *txt = new node(node::text);
2276     txt->txt = cbuf;
2277     nodes.add(txt);
2278 
2279     view->setData(root);
2280     view->repaint();
2281 }
2282 
loadFile(upath path)2283 bool FileView::loadFile(upath path) {
2284     if (path.fileExists() == false) {
2285         invalidPath(path, _("Path does not refer to a file."));
2286         return false;
2287     }
2288     FILE *fp = fopen(path.string(), "r");
2289     if (fp == nullptr) {
2290         invalidPath(path, _("Failed to open file for reading."));
2291         return false;
2292     }
2293     node *nextsub = nullptr;
2294     node::node_type close_type = node::unknown;
2295     node *root = parse(fp, 0, nullptr, nextsub, close_type);
2296     assert(nextsub == 0);
2297     fclose(fp);
2298     dump_tree(0, root);
2299     view->setData(root);
2300     return true;
2301 }
2302 
2303 class temp_file {
2304 private:
2305     FILE *fp;
2306     cbuffer cbuf;
init(const char * tdir)2307     bool init(const char *tdir) {
2308         cbuf = (upath(tdir) + "iceXXXXXX").string();
2309         int fd = mkstemp(cbuf.peek());
2310         if (fd >= 0) fp = fdopen(fd, "w+b");
2311         return fp;
2312     }
2313     temp_file(const temp_file&);
2314     temp_file& operator=(const temp_file&);
2315 public:
temp_file()2316     temp_file() : fp(nullptr) {
2317         const char *tenv = getenv("TMPDIR");
2318         if ((tenv && init(tenv)) || init("/tmp") || init("/var/tmp")) ;
2319         else tlog(_("Failed to create a temporary file"));
2320     }
~temp_file()2321     ~temp_file() {
2322         unlink(cbuf.peek());
2323         if (fp) fclose(fp);
2324     }
operator bool() const2325     operator bool() const { return fp != nullptr; }
fildes() const2326     int fildes() const { return fileno(fp); }
filep() const2327     FILE *filep() const { return fp; }
path() const2328     const char *path() const { return cbuf; }
size() const2329     int size() const {
2330         struct stat b;
2331         return fstat(fildes(), &b) >= 0 ? (int) b.st_size : 0;
2332     }
rewind() const2333     void rewind() const { ::rewind(fp); }
2334 };
2335 
2336 class downloader {
2337 private:
2338     mstring curl, wget, gzip, cat;
test(mstring * mst,const mstring & dir,const char * exe)2339     void test(mstring *mst, const mstring& dir, const char *exe) {
2340         if (mst->isEmpty()) {
2341             upath bin = upath(dir) + exe;
2342             if (bin.isExecutable() && bin.fileExists()) {
2343                 *mst = bin;
2344             }
2345         }
2346     }
empty() const2347     bool empty() const {
2348         return curl.isEmpty() || wget.isEmpty()
2349             || gzip.isEmpty() || cat.isEmpty();
2350     }
init()2351     void init() {
2352         const char defp[] = "/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin";
2353         const char *penv = getenv("PATH");
2354         mstring mpth(penv ? penv : defp), mdir;
2355         while (empty() && mpth.splitall(':', &mdir, &mpth)) {
2356             if (mdir.isEmpty())
2357                 continue;
2358             test(&curl, mdir, "curl");
2359             test(&wget, mdir, "wget");
2360             test(&gzip, mdir, "gzip");
2361             test(&cat, mdir, "cat");
2362         }
2363     }
2364     downloader(const downloader&);
2365     downloader& operator=(const downloader&);
2366 public:
downloader()2367     downloader() { init(); }
operator bool() const2368     operator bool() const {
2369         return curl.nonempty() || wget.nonempty();
2370     }
downloadTo(const char * remote,const temp_file & local)2371     bool downloadTo(const char *remote, const temp_file& local) {
2372         mstring cmd;
2373         if (curl.nonempty()) {
2374             cmd = curl + " --compressed --location -s -o ";
2375         }
2376         else if (wget.nonempty()) {
2377             cmd = wget + " -q -k -O ";
2378         }
2379         else {
2380             return false;
2381         }
2382         cmd = cmd + "'" + local.path() + "' '" + remote + "'";
2383         if (!command(cmd)) {
2384             return false;
2385         }
2386         local.rewind();
2387         if (is_compressed(local.fildes())) {
2388             if (decompress(local) == false) {
2389                 return false;
2390             }
2391         }
2392         local.rewind();
2393         return true;
2394     }
is_safe(const char * url)2395     static bool is_safe(const char *url) {
2396         for (const char *p = url; *p; ++p) {
2397             if (!ASCII::isAlnum(*p) && !strchr(":/.+-_@%?&=", *p)) {
2398                 return false;
2399             }
2400         }
2401         return true;
2402     }
is_compressed(int fd)2403     static bool is_compressed(int fd) {
2404         char b[3];
2405         return filereader(fd, false).read_all(BUFNSIZE(b)) >= 2
2406             && b[0] == '\x1F' && b[1] == '\x8B';
2407     }
command(mstring mcmd)2408     static bool command(mstring mcmd) {
2409         const char *cmd = mcmd;
2410         int xit = ::system(cmd);
2411         if (xit) {
2412             tlog(_("Failed to execute system(%s) (%d)"), cmd, xit);
2413             return false;
2414         }
2415         // tlog("Executed system(%s) OK!", cmd);
2416         return true;
2417     }
decompress(const temp_file & local)2418     bool decompress(const temp_file& local) {
2419         if (gzip.nonempty() && cat.nonempty()) {
2420             temp_file temp;
2421             if (temp) {
2422                 mstring cmd = gzip + " -d -c <'";
2423                 cmd = cmd + local.path() + "' >'";
2424                 cmd = cmd + temp.path() + "'";
2425                 if (command(cmd)) {
2426                     cmd = cat + "<'" + temp.path() + "' >'";
2427                     cmd = cmd + local.path() + "'";
2428                     if (command(cmd)) {
2429                         local.rewind();
2430                         return true;
2431                     }
2432                 }
2433             }
2434         }
2435         tlog(_("Failed to decompress %s"), local.path());
2436         return false;
2437     }
2438 };
2439 
loadHttp(upath path)2440 bool FileView::loadHttp(upath path) {
2441     downloader loader;
2442     if (!loader) {
2443         invalidPath(path, _("Could not locate curl or wget in PATH"));
2444         return false;
2445     }
2446     if (!loader.is_safe(path.string())) {
2447         invalidPath(path, _("Unsafe characters in URL"));
2448         return false;
2449     }
2450     temp_file temp;
2451     if (!temp) {
2452         return false;
2453     }
2454     if (loader.downloadTo(path.string(), temp)) {
2455         return loadFile(temp.path());
2456     }
2457     return false;
2458 }
2459 
handler(Display * display,XErrorEvent * xev)2460 static int handler(Display *display, XErrorEvent *xev) {
2461     if (true) {
2462         char message[80], req[80], number[80];
2463 
2464         snprintf(number, 80, "%d", xev->request_code);
2465         XGetErrorDatabaseText(display, "XRequest",
2466                               number, "",
2467                               req, sizeof(req));
2468         if (req[0] == 0)
2469             snprintf(req, 80, "[request_code=%d]", xev->request_code);
2470 
2471         if (XGetErrorText(display, xev->error_code, message, 80) != Success)
2472             *message = '\0';
2473 
2474         tlog("X error %s(0x%lX): %s. #%lu, +%lu, -%lu.",
2475              req, xev->resourceid, message, xev->serial,
2476              NextRequest(display), LastKnownRequestProcessed(display));
2477     }
2478     return 0;
2479 }
2480 
print_help()2481 static void print_help()
2482 {
2483     printf(_(
2484     "Usage: %s [OPTIONS] [ FILENAME | URL ]\n\n"
2485     "IceHelp is a very simple HTML browser for the IceWM window manager.\n"
2486     "It can display a HTML document from file, or browse a website.\n"
2487     "It remembers visited pages in a history, which is navigable\n"
2488     "by key bindings and a context menu (right mouse click).\n"
2489     "It neither supports rendering of images nor JavaScript.\n"
2490     "If no file or URL is given it will display the IceWM Manual\n"
2491     "from %s.\n"
2492     "\n"
2493     "Options:\n"
2494     "  -d, --display=NAME  NAME of the X server to use.\n"
2495     "  --sync              Synchronize X11 commands.\n"
2496     "\n"
2497     "  -B                  Display the IceWM icewmbg manpage.\n"
2498     "  -b, --bugs          Display the IceWM bug reports (primitively).\n"
2499     "  -f, --faq           Display the IceWM FAQ and Howto.\n"
2500     "  -g                  Display the IceWM Github website.\n"
2501     "  -i, --icewm         Display the IceWM icewm manpage.\n"
2502     "  -m, --manual        Display the IceWM Manual (default).\n"
2503     "  -s                  Display the IceWM icesound manpage.\n"
2504     "  -t, --theme         Display the IceWM themes Howto.\n"
2505     "  -w, --website       Display the IceWM website.\n"
2506     "\n"
2507     "  -V, --version       Prints version information and exits.\n"
2508     "  -h, --help          Prints this usage screen and exits.\n"
2509     "\n"
2510     "Environment variables:\n"
2511     "  DISPLAY=NAME        Name of the X server to use.\n"
2512     "\n"
2513     "To report bugs, support requests, comments please visit:\n"
2514     "%s\n"
2515     "\n"),
2516         ApplicationName,
2517         ICEHELPIDX,
2518         PACKAGE_BUGREPORT);
2519     exit(0);
2520 }
2521 
main(int argc,char ** argv)2522 int main(int argc, char **argv) {
2523     YLocale locale;
2524     const char *helpfile(nullptr);
2525     bool nodelete = false, netping = false;
2526 
2527     for (char **arg = 1 + argv; arg < argv + argc; ++arg) {
2528         if (**arg == '-') {
2529             if (is_switch(*arg, "b", "bugs"))
2530                 helpfile = PACKAGE_BUGREPORT;
2531             else if (is_switch(*arg, "f", "faq"))
2532                 helpfile = ICEWM_FAQ;
2533             else if (is_short_switch(*arg, "B"))
2534                 helpfile = ICEWMBG_1;
2535             else if (is_short_switch(*arg, "g"))
2536                 helpfile = ICEGIT_SITE;
2537             else if (is_switch(*arg, "i", "icewm"))
2538                 helpfile = ICEWM_1;
2539             else if (is_switch(*arg, "m", "manual"))
2540                 helpfile = ICEHELPIDX;
2541             else if (is_short_switch(*arg, "s"))
2542                 helpfile = ICESOUND_1;
2543             else if (is_switch(*arg, "t", "themes"))
2544                 helpfile = THEME_HOWTO;
2545             else if (is_switch(*arg, "w", "website"))
2546                 helpfile = ICEWM_SITE;
2547             else if (is_help_switch(*arg))
2548                 print_help();
2549             else if (is_version_switch(*arg))
2550                 print_version_exit(VERSION);
2551             else if (is_copying_switch(*arg))
2552                 print_copying_exit();
2553             else if (is_long_switch(*arg, "noclose"))
2554                 ignoreClose = true;
2555             else if (is_long_switch(*arg, "nodelete"))
2556                 nodelete = true;
2557             else if (is_long_switch(*arg, "noxft"))
2558                 noFreeType = true;
2559             else if (is_long_switch(*arg, "reverse"))
2560                 reverseVideo = true;
2561             else if (is_long_switch(*arg, "netping"))
2562                 netping = true;
2563             else if (is_long_switch(*arg, "verbose"))
2564                 verbose = true;
2565             else if (is_long_switch(*arg, "sync")) {
2566                 YXApplication::synchronizeX11 = true; }
2567             else if (is_long_switch(*arg, "logevents"))
2568                 toggleLogEvents();
2569             else {
2570                 char *dummy(nullptr);
2571                 if (GetArgument(dummy, "d", "display", arg, argv + argc)) {
2572                     /*ignore*/; }
2573                 else
2574                     warn(_("Ignoring option '%s'"), *arg);
2575             }
2576         }
2577         else {
2578             helpfile = *arg;
2579         }
2580     }
2581 #ifdef PRECON
2582     complain = true;
2583 #elif DEBUG
2584     complain = (debug | verbose);
2585 #else
2586     complain = verbose;
2587 #endif
2588 
2589     if (helpfile == nullptr) {
2590         helpfile = ICEHELPIDX;
2591     }
2592 
2593     XSetErrorHandler(handler);
2594     YXApplication app(&argc, &argv);
2595     FileView view(&app, argc, argv);
2596     if (nodelete) {
2597         XDeleteProperty(app.display(), view.handle(), _XA_WM_PROTOCOLS);
2598     } else {
2599         extern Atom _XA_NET_WM_PING;
2600         Atom prot[] = {
2601             _XA_WM_DELETE_WINDOW, _XA_WM_TAKE_FOCUS, _XA_NET_WM_PING
2602         };
2603         XSetWMProtocols(app.display(), view.handle(), prot, 2 + netping);
2604     }
2605     view.activateURL(helpfile);
2606     view.show();
2607     app.mainLoop();
2608 
2609     return 0;
2610 }
2611 
dump_tree(int level,node * n)2612 static void dump_tree(int level, node *n) {
2613 #ifdef DUMP
2614     while (n) {
2615         printf("%*s<%s>\n", level, "", node::to_string(n->type));
2616         if (n->container) {
2617             dump_tree(level + 1, n->container);
2618             printf("%*s</%s>\n", level, "", node::to_string(n->type));
2619         }
2620         n = n->next;
2621     }
2622 #endif
2623 }
2624 
2625 #if 0
2626 
2627 #define S_LINK "\x1B[04m"
2628 #define E_LINK "\x1B[0m"
2629 #define S_HEAD "\x1B[01;31m"
2630 #define E_HEAD "\x1B[0m"
2631 #define S_BOLD "\x1B[01m"
2632 #define E_BOLD "\x1B[0m"
2633 #define S_ITALIC "\x1B[33m"
2634 #define E_ITALIC "\x1B[0m"
2635 #define S_DEBUG "\x1B[07;31m"
2636 #define E_DEBUG "\x1B[0m"
2637 
2638 int pos = 0;
2639 int width = 80;
2640 
2641 void clear_line() {
2642     if (pos != 0)
2643         puts("");
2644     pos = 0;
2645 }
2646 
2647 void new_line(int count = 1) {
2648     while (count-- > 0)
2649         putchar('\n');
2650     pos = 0;
2651 }
2652 
2653 void blank_line(int count = 1) {
2654     clear_line();
2655     new_line(count);
2656 }
2657 
2658 void out(char *text) {
2659     printf("%s", text);
2660     pos += strlen(text);
2661 }
2662 
2663 void tab(int len) {
2664     pos += len;
2665     while (len-- > 0) putchar(' ');
2666 }
2667 
2668 void pre(int left, char *text) {
2669     char *p = text;
2670 
2671     clear_line();
2672     while (*p) {
2673         tab(left);
2674         while (*p && *p != '\n') {
2675             putchar(*p);
2676             pos++;
2677             p++;
2678         }
2679         if (*p == '\n')
2680             p++;
2681         new_line();
2682     }
2683 }
2684 
2685 void wrap(int left, char *text) {
2686     //putchar('[');
2687     while (*text) {
2688         int chr = 0;
2689         int sp = 0;
2690         int col = pos;
2691         char *p = text;
2692 
2693         //printf("[%d:%s]", pos, text);
2694         while (*p && col <= width) {
2695             if (*p == ' ')
2696                 sp = chr;
2697             chr++;
2698             col++;
2699             p++;
2700         }
2701         if (col <= width)
2702             sp = chr;
2703         if (sp == 0)
2704             sp = chr;
2705         //printf("(%d)", col);
2706 
2707         while (sp > 0) {
2708             putchar(*text);
2709             pos++;
2710             text++;
2711             sp--;
2712         }
2713         if (*text == ' ')
2714             text++;
2715         if (*text) {
2716             //if (pos != width)
2717                 puts("");
2718             pos = 0;
2719             tab(left);
2720         }
2721     }
2722     //putchar(']');
2723 }
2724 
2725 node *blank(node *n) {
2726     if (n->next && n->next->type == node::text) {
2727         if (n->next->content.text[0] == ' ' &&
2728             n->next->content.text[1] == 0) {
2729             if (n->next->next &&
2730                 (n->next->next->type == node::h1 ||
2731                  n->next->next->type == node::h2))
2732                 return n->next;
2733             blank_line();
2734             return n->next;
2735         }
2736     }
2737     blank_line();
2738     return n;
2739 }
2740 
2741 void dump(int flags, int left, node *n, node *up) {
2742     while (n) {
2743         switch (n->type) {
2744         case node::html:
2745             dump(flags, left, n->container, n);
2746             break;
2747         case node::head:
2748             dump(flags, left, n->container, n);
2749             break;
2750         case node::body:
2751         case node::table:
2752         case node::font:
2753         case node::tr:
2754         case node::td:
2755         case node::div:
2756             dump(flags, left, n->container, n);
2757             break;
2758         case node::ul:
2759         case node::ol:
2760             dump(flags, left, n->content.container, n);
2761             if (up && up->type != node::li) {
2762                 n = blank(n);
2763             }
2764             break;
2765         case node::li:
2766             clear_line();
2767             tab(left);
2768             out("  * ");
2769             dump(flags, left + 4, n->content.container, n);
2770             break;
2771         case node::bold:
2772             printf(S_BOLD);
2773             dump(flags | BOLD, left, n->content.container, n);
2774             printf(E_BOLD);
2775             break;
2776         case node::italic:
2777             printf(S_ITALIC);
2778             dump(flags | BOLD, left, n->content.container, n);
2779             printf(E_ITALIC);
2780             break;
2781         case node::h1:
2782         case node::h2:
2783         case node::h3:
2784         case node::h4:
2785         case node::h5:
2786         case node::h6:
2787             blank_line();
2788             printf(S_HEAD);
2789             dump(flags, left, n->content.container, n);
2790             printf(E_HEAD);
2791             n = blank(n);
2792             break;
2793         case node::pre:
2794             dump(flags | PRE, left + 4, n->content.container, n);
2795             break;
2796         case node::link:
2797             printf(S_LINK);
2798             dump(flags | LINK, left, n->content.container, n);
2799             printf(E_LINK);
2800             break;
2801         case node::hrule:
2802             clear_line();
2803             for (int i = 0; i < 72; i++)
2804                 pos += printf("-");
2805             clear_line();
2806             break;
2807         case node::paragraph:
2808             clear_line();
2809             new_line(1);
2810             break;
2811         case node::line:
2812             new_line(1);
2813             break;
2814         case node::text:
2815             if (flags & PRE)
2816                 pre(left, n->content.text);
2817             else {
2818                 wrap(left, n->content.text);
2819             }
2820             break;
2821         default:
2822             printf(S_DEBUG "?" E_DEBUG);
2823         }
2824         n = n->next;
2825     }
2826 }
2827 
2828 int main(int argc, char **argv) {
2829     FILE *fp = fopen(argv[1], "r");
2830     if (fp == 0)
2831         fp = stdin;
2832     char *e = getenv("COLUMNS");
2833     if (e) width = atoi(e);
2834     if (fp) {
2835         root = parse(fp, 0);
2836         dump(0, 0, root, 0);
2837         clear_line();
2838     }
2839 }
2840 #endif
2841 
2842 // vim: set sw=4 ts=4 et:
2843