1 /* pk-cmd.c - terminal related stuff.  */
2 
3 /* Copyright (C) 2019, 2020, 2021 Jose E. Marchesi */
4 
5 /* This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <config.h>
20 
21 #include <stdlib.h> /* For exit.  */
22 #include <assert.h> /* For assert. */
23 #include <string.h>
24 #include <unistd.h> /* For isatty */
25 #include <textstyle.h>
26 #include <assert.h>
27 #include <xalloc.h>
28 
29 #include "poke.h"
30 #include "pk-utils.h"
31 
32 /* The following global is the libtextstyle output stream to use to
33    emit contents to the terminal.  */
34 static styled_ostream_t pk_ostream;
35 
36 /* Stack of active classes.  */
37 
38 struct class_entry
39 {
40   char *class;
41   struct class_entry *next;
42 };
43 
44 static struct class_entry *active_classes;
45 
46 static void
push_active_class(const char * name)47 push_active_class (const char *name)
48 {
49   struct class_entry *new;
50 
51   new = xmalloc (sizeof (struct class_entry));
52   new->class = xstrdup (name);
53   new->next = active_classes;
54   active_classes = new;
55 }
56 
57 static int
pop_active_class(const char * name)58 pop_active_class (const char *name)
59 {
60   struct class_entry *tmp;
61 
62   if (!active_classes || !STREQ (active_classes->class, name))
63     return 0;
64 
65   tmp = active_classes;
66   active_classes = active_classes->next;
67   free (tmp->class);
68   free (tmp);
69   return 1;
70 }
71 
72 /* Color registry.
73 
74    The libtextstyle streams identify colors with integers, whose
75    particular values depend on the kind of the underlying physical
76    terminal and its capabilities.  The pk term, however, identifies
77    colors using a triplet of beams signifying levels of red, green and
78    blue.
79 
80    We therefore maintain a registry of colors that associate RGB
81    triplets with libtextstyle color codes.  The libtextstyled output
82    stream provides a mechanism to translate from RGB codes to color
83    codes, but not the other way around (since it is really a 1-N
84    relationship.)  */
85 
86 static int default_color;
87 static int default_bgcolor;
88 
89 struct color_entry
90 {
91   term_color_t code;
92   struct pk_color color;
93 
94   struct color_entry *next;
95 };
96 
97 static struct color_entry *color_registry;
98 
99 static struct color_entry *
lookup_color(int red,int green,int blue)100 lookup_color (int red, int green, int blue)
101 {
102   struct color_entry *entry;
103 
104   for (entry = color_registry; entry; entry = entry->next)
105     {
106       if (entry->color.red == red
107           && entry->color.green == green
108           && entry->color.blue == blue)
109         break;
110     }
111 
112   return entry;
113 }
114 
115 #if defined HAVE_TEXTSTYLE_ACCESSORS_SUPPORT
116 
117 static struct color_entry *
lookup_color_code(int code)118 lookup_color_code (int code)
119 {
120   struct color_entry *entry;
121 
122   for (entry = color_registry; entry; entry = entry->next)
123     {
124       if (entry->code == code)
125         break;
126     }
127 
128   return entry;
129 }
130 
131 static void
register_color(int code,int red,int green,int blue)132 register_color (int code, int red, int green, int blue)
133 {
134   struct color_entry *entry = lookup_color (red, green, blue);
135 
136   if (entry)
137     entry->code = code;
138   else
139     {
140       entry = xmalloc (sizeof (struct color_entry));
141       entry->code = code;
142       entry->color.red = red;
143       entry->color.green = green;
144       entry->color.blue = blue;
145 
146       entry->next = color_registry;
147       color_registry = entry;
148     }
149 }
150 
151 #endif /* HAVE_TEXTSTYLE_ACCESSORS_SUPPORT */
152 
153 static void
dispose_color_registry(void)154 dispose_color_registry (void)
155 {
156   struct color_entry *entry;
157 
158   while (color_registry)
159     {
160       entry = color_registry->next;
161       free (color_registry);
162       color_registry = entry;
163     }
164 }
165 
166 void
pk_term_init(int argc,char * argv[])167 pk_term_init (int argc, char *argv[])
168 {
169   int i;
170 
171   /* Process terminal-related command-line options.  */
172   for (i = 1; i < argc; i++)
173     {
174       const char *arg = argv[i];
175 
176       if (strncmp (arg, "--color=", 8) == 0)
177         {
178           if (handle_color_option (arg + 8))
179             pk_fatal ("handle_color_option failed");
180         }
181       else if (strncmp (arg, "--style=", 8) == 0)
182         handle_style_option (arg + 8);
183     }
184 
185   /* Handle the --color=test special argument.  */
186   if (color_test_mode)
187     {
188       print_color_test ();
189       exit (EXIT_SUCCESS);
190     }
191 
192   /* Note that the following code needs to be compiled conditionally
193      because the textstyle.h file provided by the gnulib module
194      libtextstyle-optional defines style_file_name as an r-value.  */
195 
196 #ifdef HAVE_LIBTEXTSTYLE
197   /* Open the specified style.  */
198   if (color_mode == color_yes
199       || (color_mode == color_tty
200           && isatty (STDOUT_FILENO)
201           && getenv ("NO_COLOR") == NULL)
202       || color_mode == color_html)
203     {
204       /* Find the style file.  */
205       style_file_prepare ("POKE_STYLE", "POKESTYLESDIR", PKGDATADIR,
206                           "poke-default.css");
207     }
208   else
209     /* No styling.  */
210     style_file_name = NULL;
211 #endif
212 
213   /* Create the output styled stream.  */
214   pk_ostream =
215     (color_mode == color_html
216      ? html_styled_ostream_create (file_ostream_create (stdout),
217                                    style_file_name)
218      : styled_ostream_create (STDOUT_FILENO, "(stdout)",
219                               TTYCTL_AUTO, style_file_name));
220 
221   /* Initialize the default colors and register them associated to the
222      RGB (-1,-1,-1).  */
223 #if defined HAVE_TEXTSTYLE_ACCESSORS_SUPPORT
224   if (color_mode != color_html
225       && is_instance_of_term_styled_ostream (pk_ostream))
226     {
227       term_ostream_t term_ostream =
228         term_styled_ostream_get_destination ((term_styled_ostream_t) pk_ostream);
229 
230       default_color = term_ostream_get_color (term_ostream);
231       default_bgcolor = term_ostream_get_bgcolor (term_ostream);
232 
233       register_color (default_color, -1, -1, -1);
234       register_color (default_bgcolor, -1, -1, -1);
235     }
236 #endif
237 }
238 
239 void
pk_term_shutdown()240 pk_term_shutdown ()
241 {
242   while (active_classes)
243     pop_active_class (active_classes->class);
244   dispose_color_registry ();
245   styled_ostream_free (pk_ostream);
246 }
247 
248 void
pk_term_flush()249 pk_term_flush ()
250 {
251   ostream_flush (pk_ostream, FLUSH_THIS_STREAM);
252 }
253 
254 void
pk_puts(const char * str)255 pk_puts (const char *str)
256 {
257   ostream_write_str (pk_ostream, str);
258 }
259 
260 __attribute__ ((__format__ (__printf__, 1, 2)))
261 void
pk_printf(const char * format,...)262 pk_printf (const char *format, ...)
263 {
264   va_list ap;
265   char *str;
266   int r;
267 
268   va_start (ap, format);
269   r = vasprintf (&str, format, ap);
270   assert (r != -1);
271   va_end (ap);
272 
273   ostream_write_str (pk_ostream, str);
274   free (str);
275 }
276 
277 void
pk_vprintf(const char * format,va_list ap)278 pk_vprintf (const char *format, va_list ap)
279 {
280   char *str;
281   int r;
282 
283   r = vasprintf (&str, format, ap);
284   assert (r != -1);
285 
286   ostream_write_str (pk_ostream, str);
287   free (str);
288 }
289 
290 
291 void
pk_term_indent(unsigned int lvl,unsigned int step)292 pk_term_indent (unsigned int lvl,
293                 unsigned int step)
294 {
295   pk_printf ("\n%*s", (step * lvl), "");
296 }
297 
298 void
pk_term_class(const char * class)299 pk_term_class (const char *class)
300 {
301   styled_ostream_begin_use_class (pk_ostream, class);
302   push_active_class (class);
303 }
304 
305 int
pk_term_end_class(const char * class)306 pk_term_end_class (const char *class)
307 {
308   if (!pop_active_class (class))
309     return 0;
310 
311   styled_ostream_end_use_class (pk_ostream, class);
312   return 1;
313 }
314 
315 /* Counter of open hyperlinks.  */
316 static int hlcount = 0;
317 
318 void
pk_term_hyperlink(const char * url,const char * id)319 pk_term_hyperlink (const char *url, const char *id)
320 {
321 #ifdef HAVE_TEXTSTYLE_HYPERLINK_SUPPORT
322   styled_ostream_set_hyperlink (pk_ostream, url, id);
323   hlcount += 1;
324 #endif
325 }
326 
327 int
pk_term_end_hyperlink(void)328 pk_term_end_hyperlink (void)
329 {
330 #ifdef HAVE_TEXTSTYLE_HYPERLINK_SUPPORT
331   if (hlcount == 0)
332     return 0;
333 
334   styled_ostream_set_hyperlink (pk_ostream, NULL, NULL);
335   hlcount -= 1;
336   return 1;
337 #endif
338 }
339 
340 int
pk_term_color_p(void)341 pk_term_color_p (void)
342 {
343   return (color_mode == color_yes
344           || (color_mode == color_tty
345               && isatty (STDOUT_FILENO)
346               && getenv ("NO_COLOR") == NULL));
347 }
348 
349 struct pk_color
pk_term_get_color(void)350 pk_term_get_color (void)
351 {
352 #if defined HAVE_TEXTSTYLE_ACCESSORS_SUPPORT
353    if (color_mode != color_html
354        && is_instance_of_term_styled_ostream (pk_ostream))
355      {
356        term_ostream_t term_ostream =
357          term_styled_ostream_get_destination ((term_styled_ostream_t) pk_ostream);
358        struct color_entry *entry
359          = lookup_color_code (term_ostream_get_color (term_ostream));
360 
361        assert (entry);
362        return entry->color;
363      }
364    else
365 #endif
366      {
367        struct pk_color dfl = {-1,-1,-1};
368        return dfl;
369      }
370 }
371 
372 struct pk_color
pk_term_get_bgcolor()373 pk_term_get_bgcolor ()
374 {
375 #if defined HAVE_TEXTSTYLE_ACCESSORS_SUPPORT
376   if (color_mode != color_html
377       && is_instance_of_term_styled_ostream (pk_ostream))
378     {
379       term_ostream_t term_ostream =
380         term_styled_ostream_get_destination ((term_styled_ostream_t) pk_ostream);
381       struct color_entry *entry
382         = lookup_color_code (term_ostream_get_bgcolor (term_ostream));
383 
384       assert (entry);
385       return entry->color;
386     }
387   else
388 #endif
389     {
390       struct pk_color dfl = {-1,-1,-1};
391       return dfl;
392     }
393 }
394 
395 void
pk_term_set_color(struct pk_color color)396 pk_term_set_color (struct pk_color color)
397 {
398 #if defined HAVE_TEXTSTYLE_ACCESSORS_SUPPORT
399   if (color_mode != color_html)
400     {
401       if (is_instance_of_term_styled_ostream (pk_ostream))
402         {
403           term_ostream_t term_ostream =
404             term_styled_ostream_get_destination ((term_styled_ostream_t) pk_ostream);
405           term_color_t term_color;
406 
407           if (color.red == -1 && color.green == -1 && color.blue == -1)
408             term_color = default_color;
409           else
410             {
411               term_color = term_ostream_rgb_to_color (term_ostream,
412                                                       color.red, color.green, color.blue);
413               register_color (term_color,
414                               color.red, color.green, color.blue);
415             }
416 
417           term_ostream_set_color (term_ostream, term_color);
418         }
419     }
420 #endif
421 }
422 
423 void
pk_term_set_bgcolor(struct pk_color color)424 pk_term_set_bgcolor (struct pk_color color)
425 {
426 #if defined HAVE_TEXTSTYLE_ACCESSORS_SUPPORT
427   if (color_mode != color_html)
428     {
429       if (is_instance_of_term_styled_ostream (pk_ostream))
430         {
431           term_ostream_t term_ostream =
432             term_styled_ostream_get_destination ((term_styled_ostream_t) pk_ostream);
433           term_color_t term_color;
434 
435           if (color.red == -1 && color.green == -1 && color.blue == -1)
436             term_color = default_bgcolor;
437           else
438             {
439               term_color = term_ostream_rgb_to_color (term_ostream,
440                                                       color.red, color.green, color.blue);
441 
442               register_color (term_color,
443                               color.red, color.green, color.blue);
444             }
445 
446           term_ostream_set_bgcolor (term_ostream, term_color);
447         }
448     }
449 #endif
450 }
451