1 /*
2  * Copyright (C) 2001-2006  Simon Baldwin (simon_baldwin@yahoo.com)
3  * Copyright (C) 2011-2017  Kamil Ignacak (acerion@wp.pl)
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (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 along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #include "config.h"
21 
22 #include <sys/time.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25 #include <signal.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <time.h>
29 #include <limits.h>
30 #include <ctype.h>
31 #include <curses.h>
32 #include <errno.h>
33 
34 #if defined(HAVE_STRING_H)
35 # include <string.h>
36 #endif
37 
38 #if defined(HAVE_STRINGS_H)
39 # include <strings.h>
40 #endif
41 
42 #include "libcw.h"
43 #include "i18n.h"
44 #include "cmdline.h"
45 #include "cw_copyright.h"
46 #include "dictionary.h"
47 #include "memory.h"
48 
49 
50 
51 
52 
53 /*---------------------------------------------------------------------*/
54 /*  Module variables, miscellaneous other stuff                        */
55 /*---------------------------------------------------------------------*/
56 
57 /* Flag set if colors are requested on the user interface. */
58 static bool do_colors = true;
59 
60 /* Are we at the beginning of buffer displaying played characters? */
61 static bool beginning_of_buffer = true;
62 /* Current sending state, active or idle. */
63 static bool is_sending_active = false;
64 
65 
66 /* Width of parameter windows, displayed at the bottom of window.
67    Since the same value is used as size of char buffer, make one
68    character for terminating NUL explicit as +1.
69  */
70 #define CWCP_PARAM_WIDTH (15 + 1)
71 
72 
73 static cw_config_t *config = NULL; /* program-specific configuration */
74 static bool generator = false;     /* have we created a generator? */
75 static const char *all_options = "s:|system,d:|device,"
76 	"w:|wpm,t:|tone,v:|volume,"
77 	"g:|gap,k:|weighting,"
78 	"f:|infile,F:|outfile,"
79 	"T:|time,"
80 	/* "c:|colours,c:|colors,m|mono," */
81 	"h|help,V|version";
82 
83 
84 
85 static WINDOW *screen  = NULL,
86 	*text_window   = NULL, *text_subwindow   = NULL,
87 	*mode_window   = NULL, *mode_subwindow   = NULL,
88 	*speed_window  = NULL, *speed_subwindow  = NULL,
89 	*tone_window   = NULL, *tone_subwindow   = NULL,
90 	*volume_window = NULL, *volume_subwindow = NULL,
91 	*gap_window    = NULL, *gap_subwindow    = NULL,
92 	*timer_window  = NULL, *timer_subwindow  = NULL;
93 
94 static void cwcp_atexit(void);
95 
96 static int  timer_get_total_practice_time(void);
97 static bool timer_set_total_practice_time(int practice_time);
98 static void timer_start(void);
99 static bool timer_is_expired(void);
100 static void timer_window_update(int elapsed, int total);
101 
102 static void speed_update(void);
103 static void frequency_update(void);
104 static void volume_update(void);
105 static void gap_update(void);
106 
107 
108 typedef enum { M_DICTIONARY, M_KEYBOARD, M_EXIT } mode_type_t;
109 
110 static void mode_initialize(void);
111 static void mode_clean(void);
112 static bool mode_change_to_next(void);
113 static bool mode_change_to_previous(void);
114 static int  mode_get_current(void);
115 static int  mode_get_count(void);
116 static const char *mode_get_description(int index);
117 static bool mode_current_is_type(mode_type_t type);
118 static bool mode_is_sending_active(void);
119 
120 
121 
122 /* Definition of an interface operating mode; its description, related
123    dictionary, and data on how to send for the mode. */
124 struct mode_s {
125 	const char *description;       /* Text mode description */
126 	mode_type_t type;              /* Mode type; {M_DICTIONARY|M_KEYBOARD|M_EXIT} */
127 	const cw_dictionary_t *dict;   /* Dictionary, if type is dictionary */
128 };
129 
130 
131 typedef struct mode_s *moderef_t;
132 
133 /* Modes table, current program mode, and count of modes in the table.
134    The program is always in one of these modes, indicated by
135    current_mode. */
136 static moderef_t modes = NULL,
137                  current_mode = NULL;
138 static int modes_count = 0;
139 
140 static int queue_get_length(void);
141 static int queue_next_index(int index);
142 static int queue_prior_index(int index);
143 static void queue_display_add_character(void);
144 static void queue_display_delete_character(void);
145 static void queue_display_highlight_character(bool is_highlight);
146 static void queue_discard_contents(void);
147 static void queue_dequeue_character(void);
148 static void queue_enqueue_string(const char *word);
149 static void queue_enqueue_character(char c);
150 static void queue_enqueue_random_dictionary_text(moderef_t mode, bool beginning_of_buffer);
151 static void queue_transfer_character_to_libcw(void);
152 static void queue_delete_character(void);
153 
154 static void ui_refresh_main_window(void);
155 static void ui_display_state(const char *state);
156 static void ui_clear_main_window(void);
157 static void ui_poll_user_input(int fd, int usecs);
158 static void ui_update_mode_selection(int old_mode, int current_mode);
159 static void ui_handle_event(int c);
160 
161 static void    ui_destroy(void);
162 static void    ui_initialize(void);
163 static WINDOW *ui_init_window(int lines, int columns, int begin_y, int begin_x, const char *header);
164 static void    ui_init_display(int lines, int columns, int begin_y, int begin_x, const char *header, WINDOW **window, WINDOW **subwindow);
165 static WINDOW *ui_init_screen(void);
166 
167 static void signal_handler(int signal_number);
168 
169 static void state_change_to_active(void);
170 static void state_change_to_idle(void);
171 
172 
173 
174 
175 
176 /*---------------------------------------------------------------------*/
177 /*  Circular character queue                                           */
178 /*---------------------------------------------------------------------*/
179 
180 /* Characters awaiting send are stored in a circular buffer,
181    implemented as an array with tail and head indexes that wrap. */
182 enum { QUEUE_CAPACITY = 256 };
183 static volatile char queue_data[QUEUE_CAPACITY];
184 static volatile int queue_tail = 0,
185                     queue_head = 0;
186 
187 /* There are times where we have no data to send.  For these cases,
188    record as idle, so that we know when to wake the sender. */
189 static volatile bool is_queue_idle = true;
190 
191 
192 
193 
194 /**
195    \brief Return the count of characters currently held in the circular buffer
196 */
queue_get_length(void)197 int queue_get_length(void)
198 {
199 	return queue_tail >= queue_head
200 		? queue_tail - queue_head : queue_tail - queue_head + QUEUE_CAPACITY;
201 }
202 
203 
204 
205 
206 
207 /**
208    \brief Advance a tone queue index, including circular wrapping
209 */
queue_next_index(int index)210 int queue_next_index(int index)
211 {
212 	return (index + 1) % QUEUE_CAPACITY;
213 }
214 
215 
216 
217 
218 
219 /**
220    \brief Regress a tone queue index, including circular wrapping
221 */
queue_prior_index(int index)222 int queue_prior_index(int index)
223 {
224 	return index == 0 ? QUEUE_CAPACITY - 1 : index - 1;
225 }
226 
227 
228 
229 
230 
231 /**
232    Add a character to the text display when queueing
233 */
queue_display_add_character(void)234 void queue_display_add_character(void)
235 {
236 	/* Append the last queued character to the text display. */
237 	if (queue_get_length() > 0) {
238 		waddch(text_subwindow, toupper(queue_data[queue_tail]));
239 		wrefresh(text_subwindow);
240 	}
241 
242 	return;
243 }
244 
245 
246 
247 
248 
249 /**
250    Delete a character to the text display when dequeueing
251 */
queue_display_delete_character(void)252 void queue_display_delete_character(void)
253 {
254 	int y, x, max_x;
255 	__attribute__((unused)) int max_y;
256 
257 	/* Get the text display dimensions and current coordinates. */
258 	getmaxyx(text_subwindow, max_y, max_x);
259 	getyx(text_subwindow, y, x);
260 
261 	/* Back the cursor up one position. */
262 	x--;
263 	if (x < 0) {
264 		x += max_x;
265 		y--;
266 	}
267 
268 	/* If these coordinates are on screen, write a space and back up. */
269 	if (y >= 0) {
270 		wmove(text_subwindow, y, x);
271 		waddch(text_subwindow, ' ');
272 		wmove(text_subwindow, y, x);
273 		wrefresh(text_subwindow);
274 	}
275 
276 	return;
277 }
278 
279 
280 
281 
282 
283 /**
284    Highlight or un-highlight a character in the text display when dequeueing
285 */
queue_display_highlight_character(bool is_highlight)286 void queue_display_highlight_character(bool is_highlight)
287 {
288 	int y, x, max_x;
289 	__attribute__((unused)) int max_y;
290 
291 	/* Get the text display dimensions and current coordinates. */
292 	getmaxyx(text_subwindow, max_y, max_x);
293 	getyx(text_subwindow, y, x);
294 
295 	/* Find the coordinates for the queue head character. */
296 	x -= queue_get_length() + 1;
297 	while (x < 0) {
298 		x += max_x;
299 		y--;
300 	}
301 
302 	/* If these coordinates are on screen, highlight or
303 	   unhighlight, and then restore the cursor position so that
304 	   it remains unchanged. */
305 	if (y >= 0) {
306 		int saved_y, saved_x;
307 
308 		getyx(text_subwindow, saved_y, saved_x);
309 		wmove(text_subwindow, y, x);
310 		waddch(text_subwindow,
311 		       is_highlight ? winch(text_subwindow) | A_REVERSE
312 			: winch(text_subwindow) & ~A_REVERSE);
313 		wmove(text_subwindow, saved_y, saved_x);
314 		wrefresh(text_subwindow);
315 	}
316 
317 	return;
318 }
319 
320 
321 
322 
323 
324 /**
325    \brief Forcibly empty the queue, if not already idle
326 */
queue_discard_contents(void)327 void queue_discard_contents(void)
328 {
329 	if (!is_queue_idle) {
330 		queue_display_highlight_character(false);
331 		queue_head = queue_tail;
332 		is_queue_idle = true;
333 	}
334 
335 	return;
336 }
337 
338 
339 
340 
341 
342 /**
343    \brief Dequeue a character
344 
345    Called when the CW send buffer is empty.  If the queue is not idle,
346    take the next character from the queue and send it.  If there are
347    no more queued characters, set the queue to idle.
348 */
queue_dequeue_character(void)349 void queue_dequeue_character(void)
350 {
351 	if (!is_queue_idle) {
352 		/* Unhighlight any previous highlighting, and see if
353 		   we can dequeue. */
354 		queue_display_highlight_character(false);
355 		if (queue_get_length() > 0) {
356 			char c;
357 
358 			/* Take the next character off the queue,
359 			   highlight, and send it.  We don't expect
360 			   sending to fail because only sendable
361 			   characters are queued. */
362 			queue_head = queue_next_index(queue_head);
363 			c = queue_data[queue_head];
364 			queue_display_highlight_character(true);
365 
366 			if (!cw_send_character(c)) {
367 				perror("cw_send_character");
368 				abort();
369 			}
370 		} else {
371 			is_queue_idle = true;
372 		}
373 	}
374 
375 	return;
376 }
377 
378 
379 
380 
381 
382 /**
383    \brief Queue a string for sending by the CW sender
384 
385    Function rejects any unsendable character, and also any characters
386    passed in where the character queue is already full.  Rejection is
387    silent.
388 
389    \param word - string to send
390 */
queue_enqueue_string(const char * word)391 void queue_enqueue_string(const char *word)
392 {
393 	bool is_queue_notify = false;
394 	for (int i = 0; word[i] != '\0'; i++) {
395 
396 		char c = toupper(word[i]);
397 		if (cw_character_is_valid(c)) {
398 			/* Calculate the new character queue tail.  If
399 			   the new value will not hit the current
400 			   queue head, add the character to the
401 			   queue. */
402 			if (queue_next_index(queue_tail) != queue_head) {
403 				queue_tail = queue_next_index(queue_tail);
404 				queue_data[queue_tail] = c;
405 				queue_display_add_character();
406 
407 				if (is_queue_idle) {
408 					is_queue_notify = true;
409 				}
410 			}
411 		}
412 	}
413 
414 	/* If we queued any character, mark the queue as not idle. */
415 	if (is_queue_notify) {
416 		is_queue_idle = false;
417 	}
418 
419 	return;
420 }
421 
422 
423 
424 
425 
426 /**
427    \brief Queue a character for sending by the CW sender
428 
429    Function rejects any unsendable character, and also any characters
430    passed in where the character queue is already full.  Rejection is
431    silent.
432 
433    \param c - character to send
434 */
queue_enqueue_character(char c)435 void queue_enqueue_character(char c)
436 {
437 	char buffer[2];
438 
439 	buffer[0] = c;
440 	buffer[1] = '\0';
441 	queue_enqueue_string(buffer);
442 
443 	return;
444 }
445 
446 
447 
448 
449 
450 /**
451    \brief Delete the most recently added character from the queue
452 
453    Remove the most recently added character from the queue, provided
454    that the dequeue hasn't yet reached it.  If there's nothing
455    available to delete, fail silently.
456 */
queue_delete_character(void)457 void queue_delete_character(void)
458 {
459 	/* If data is queued, regress tail and delete one display character. */
460 	if (queue_get_length() > 0) {
461 		queue_tail = queue_prior_index(queue_tail);
462 		queue_display_delete_character();
463 	}
464 
465 	return;
466 }
467 
468 
469 
470 
471 
472 /**
473    Add a group of elements from current dictionary to cwcp's character queue
474 
475    Function adds a group of letters, or a word, to cwcp's character
476    queue. The group or the word is then played and displayed in main
477    window.
478 
479    The function also enqueues space separating words/groups if \p
480    beginning is true. You want to pass true only for first call of the
481    function for given mode (only when the program should queue and
482    play first group/word).
483 
484    \param mode - mode determining from which dictionary to get elements
485    \param beginning - are we at the beginning of buffer/window?
486 */
queue_enqueue_random_dictionary_text(moderef_t mode,bool beginning)487 void queue_enqueue_random_dictionary_text(moderef_t mode, bool beginning)
488 {
489 	if (!beginning) {
490 		queue_enqueue_character(' ');
491 	}
492 
493 	/* Size of group of letters that will be printed together
494 	   to main window of cwcp. '1' for dictionaries consisting
495 	   of multi-character words (so you get single words separated
496 	   with spaces), or '5' for single-character words (so you get
497 	   5-letter chunks separated with spaces). */
498 	int group_size = cw_dictionary_get_group_size(mode->dict);
499 
500 	/* Select and buffer N random elements selected from dictionary. */
501 	for (int group = 0; group < group_size; group++) {
502 		/* For dictionaries with size of word in dictionary == 1
503 		   this returns single letters. */
504 		queue_enqueue_string(cw_dictionary_get_random_word(mode->dict));
505 	}
506 
507 	return;
508 }
509 
510 
511 
512 
513 
514 /**
515    Check the libcw's tone queue, and if it is getting low, arrange for
516    more data to be passed in to the libcw's tone queue.
517 */
queue_transfer_character_to_libcw(void)518 void queue_transfer_character_to_libcw(void)
519 {
520 	if (cw_get_tone_queue_length() > 1) {
521 		return;
522 	}
523 
524 	if (!is_sending_active) {
525 		return;
526 	}
527 
528 	/* Arrange more data for libcw.  The source for this data is
529 	   dependent on the mode.  If in dictionary modes, update and
530 	   check the timer, then add more random data if the queue is
531 	   empty.  If in keyboard mode, just dequeue anything
532 	   currently on the character queue. */
533 
534 	if (current_mode->type == M_DICTIONARY) {
535 		if (timer_is_expired()) {
536 			state_change_to_idle();
537 			return;
538 		}
539 
540 		if (queue_get_length() == 0) {
541 			queue_enqueue_random_dictionary_text(current_mode, beginning_of_buffer);
542 			if (beginning_of_buffer) {
543 				beginning_of_buffer = false;
544 			}
545 		}
546 	}
547 
548 	if (current_mode->type == M_DICTIONARY
549 	    || current_mode->type == M_KEYBOARD) {
550 
551 		queue_dequeue_character();
552 	}
553 
554 	return;
555 }
556 
557 
558 
559 
560 
561 /*---------------------------------------------------------------------*/
562 /*  Practice timer                                                     */
563 /*---------------------------------------------------------------------*/
564 
565 static const int TIMER_MIN_TIME = 1, TIMER_MAX_TIME = 99; /* practice timer limits */
566 static int timer_total_practice_time = 15; /* total time of practice, from beginning to end */
567 static int timer_practice_start = 0;       /* time() value on practice start */
568 
569 
570 
571 
572 
573 /**
574    \brief Get current value of total time of practice
575 */
timer_get_total_practice_time(void)576 int timer_get_total_practice_time(void)
577 {
578 	return timer_total_practice_time;
579 }
580 
581 
582 
583 
584 
585 /**
586    \brief Set total practice time
587 
588    Set total time (total duration) of practice.
589 
590    \param practice_time - new value of total practice time
591 
592    \return true on success
593    \return false on failure
594 */
timer_set_total_practice_time(int practice_time)595 bool timer_set_total_practice_time(int practice_time)
596 {
597 	if (practice_time >= TIMER_MIN_TIME && practice_time <= TIMER_MAX_TIME) {
598 		timer_total_practice_time = practice_time;
599 		return true;
600 	} else {
601 		return false;
602 	}
603 }
604 
605 
606 
607 
608 
609 /**
610    \brief Set the timer practice start time to the current time
611 */
timer_start(void)612 void timer_start(void)
613 {
614 	timer_practice_start = time(NULL);
615 	return;
616 }
617 
618 
619 
620 
621 
622 /**
623    \brief Update the practice timer, and return true if the timer expires
624 
625    \return true if timer has expired
626    \return false if timer has not expired yet
627 */
timer_is_expired(void)628 bool timer_is_expired(void)
629 {
630 	/* Update the display of minutes practiced. */
631 	int elapsed = (time(NULL) - timer_practice_start) / 60;
632 	timer_window_update(elapsed, timer_total_practice_time);
633 
634 	/* Check the time, requesting stop if over practice time. */
635 	return elapsed >= timer_total_practice_time;
636 }
637 
638 
639 
640 
641 
642 /**
643    \brief Update value of time spent on practicing
644 
645    Function updates 'timer display' with one or two values of practice
646    time: time elapsed and time total. Both times are in minutes.
647 
648    You can pass a negative value of \p elapsed - function will use
649    previous valid value (which is zero at first time).
650 
651    \param elapsed - time elapsed from beginning of practice
652    \param total - total time of practice
653 */
timer_window_update(int elapsed,int total)654 void timer_window_update(int elapsed, int total)
655 {
656 	static int el = 0;
657 	if (elapsed >= 0) {
658 		el = elapsed;
659 	}
660 
661 	char buffer[CWCP_PARAM_WIDTH];
662 	snprintf(buffer, CWCP_PARAM_WIDTH, total == 1 ? _("%2d/%2d min ") : _("%2d/%2d mins"), el, total);
663 	mvwaddstr(timer_subwindow, 0, 2, buffer);
664 	wrefresh(timer_subwindow);
665 
666 	return;
667 }
668 
669 
670 
671 
672 
673 /*---------------------------------------------------------------------*/
674 /*  General program state and mode control                             */
675 /*---------------------------------------------------------------------*/
676 
677 
678 
679 
680 
681 /**
682    \brief Initialize modes
683 
684    Build up the modes from the known dictionaries, then add non-dictionary
685    modes.
686 */
mode_initialize(void)687 void mode_initialize(void)
688 {
689 	if (modes) {
690 		/* Dispose of any pre-existing modes -- unlikely. */
691 		free(modes);
692 		modes = NULL;
693 	}
694 
695 	/* Start the modes with the known dictionaries. */
696 	int count = 0;
697 	for (const cw_dictionary_t *dict = cw_dictionaries_iterate(NULL);
698 	     dict;
699 	     dict = cw_dictionaries_iterate(dict)) {
700 
701 		modes = safe_realloc(modes, sizeof (*modes) * (count + 1));
702 		modes[count].description = cw_dictionary_get_description(dict);
703 		modes[count].type = M_DICTIONARY;
704 		modes[count++].dict = dict;
705 	}
706 
707 	/* Add keyboard, exit, and null sentinel. */
708 	modes = safe_realloc(modes, sizeof (*modes) * (count + 3));
709 	modes[count].description = _("Keyboard");
710 	modes[count].type = M_KEYBOARD;
711 	modes[count++].dict = NULL;
712 
713 	modes[count].description = _("Exit (F12)");
714 	modes[count].type = M_EXIT;
715 	modes[count++].dict = NULL;
716 
717 	memset(modes + count, 0, sizeof (*modes));
718 
719 	/* Initialize the current mode to be the first listed, and set count. */
720 	current_mode = modes;
721 	modes_count = count;
722 
723 	return;
724 }
725 
726 
727 
728 
729 
730 /**
731    \brief Free data structures relates to modes
732 
733    Call this function at exit
734 */
mode_clean(void)735 void mode_clean(void)
736 {
737 	free(modes);
738 	modes = NULL;
739 
740 	return;
741 }
742 
743 
744 
745 
746 
747 /**
748    \brief Get count of modes
749 */
mode_get_count(void)750 int mode_get_count(void)
751 {
752 	return modes_count;
753 }
754 
755 
756 
757 
758 
759 /**
760    \brief Get index of the current mode
761 */
mode_get_current(void)762 int mode_get_current(void)
763 {
764 	return current_mode - modes;
765 }
766 
767 
768 
769 
770 
771 /**
772    \brief Get description of a mode at given index
773 */
mode_get_description(int index)774 const char *mode_get_description(int index)
775 {
776 	return modes[index].description;
777 }
778 
779 
780 
781 
782 
783 /**
784    \brief Get result of a type comparison for the current mode
785 */
mode_current_is_type(mode_type_t type)786 bool mode_current_is_type(mode_type_t type)
787 {
788 	return current_mode->type == type;
789 }
790 
791 
792 
793 
794 
795 /**
796    \brief Change the mode to next one
797 
798    Advance the current node, returning false if at the limit.
799 
800    \return true if mode has been changed
801    \return false if mode has not been changed because it was the last on list
802 */
mode_change_to_next(void)803 bool mode_change_to_next(void)
804 {
805 	current_mode++;
806 	if (!current_mode->description) {
807 		current_mode--;
808 		return false;
809 	} else {
810 		return true;
811 	}
812 }
813 
814 
815 
816 
817 
818 /**
819    \brief Change the mode to previous one
820 
821    Regress the current node, returning false if at the limit.
822 
823    \return true if mode has been changed
824    \return false if mode has not been changed because it was the first on list
825 */
mode_change_to_previous(void)826 bool mode_change_to_previous(void)
827 {
828 	if (current_mode > modes) {
829 		current_mode--;
830 		return true;
831 	} else {
832 		return false;
833 	}
834 }
835 
836 
837 
838 
839 
840 /**
841    \brief Change the state of the program from idle to actively sending.
842 */
state_change_to_active(void)843 void state_change_to_active(void)
844 {
845 	static moderef_t last_mode = NULL;  /* Detect changes of mode */
846 
847 	if (is_sending_active) {
848 		return;
849 	}
850 
851 	cw_start_beep();
852 
853 	is_sending_active = true;
854 
855 	ui_display_state(_("Sending(F9 or Esc to exit)"));
856 
857 	if (current_mode != last_mode) {
858 		ui_clear_main_window();
859 		timer_start();
860 
861 		/* Don't allow a space at the beginning of buffer. */
862 		beginning_of_buffer = true;
863 
864 		last_mode = current_mode;
865         }
866 
867 	ui_refresh_main_window();
868 
869 	return;
870 }
871 
872 
873 
874 
875 
876 /**
877    Change the state of the program from actively sending to idle.
878 */
state_change_to_idle(void)879 void state_change_to_idle(void)
880 {
881 	if (!is_sending_active) {
882 		return;
883 	}
884 
885 	is_sending_active = false;
886 
887 	ui_display_state(_("Start(F9)"));
888 	touchwin(text_subwindow);
889 	wnoutrefresh(text_subwindow);
890 	doupdate();
891 
892 	/* Remove everything in the outgoing character queue. */
893 	queue_discard_contents();
894 
895 	cw_end_beep();
896 
897 	return;
898 }
899 
900 
901 
902 
903 
904 /**
905    \brief Check if sending is active
906 
907    \return true if currently sending
908    \return false otherwise
909 */
mode_is_sending_active(void)910 bool mode_is_sending_active(void)
911 {
912 	return is_sending_active;
913 }
914 
915 
916 
917 
918 
919 /*---------------------------------------------------------------------*/
920 /*  User interface initialization and event handling                   */
921 /*---------------------------------------------------------------------*/
922 
923 /*
924  * User interface introduction strings, split in two to avoid the 509
925  * character limit imposed by ISO C89 on string literal lengths.
926  */
927 static const char *const INTRODUCTION = N_(
928   "UNIX/Linux Morse Tutor v3.5.1\n"
929   "Copyright (C) 1997-2006 Simon Baldwin\n"
930   "Copyright (C) 2011-2017 Kamil Ignacak\n"
931   "---------------------------------------------------------\n"
932   "Cwcp is an interactive Morse code tutor program, designed\n"
933   "both for learning Morse code for the first time, and for\n"
934   "experienced Morse users who want, or need, to improve\n"
935   "their receiving speed.\n");
936 static const char *const INTRODUCTION_CONTINUED = N_(
937   "---------------------------------------------------------\n"
938   "Select mode:                   Up/Down arrow/F10/F11\n"
939   "Start sending selected mode:   Enter/F9\n"
940   "Pause:                         F9/Esc\n"
941   "Resume:                        F9\n"
942   "Exit program:                  menu->Exit/F12/^C\n"
943   "Use keys specified below to adjust speed, tone, volume,\n"
944   "and spacing of the Morse code at any time.\n");
945 
946 /* Alternative F-keys for folks without (some, or all) F-keys. */
947 enum
948 { CTRL_OFFSET = 0100,                   /* Ctrl keys are 'X' - 0100 */
949   PSEUDO_KEYF1 = 'Q' - CTRL_OFFSET,     /* Alternative FKEY1 */
950   PSEUDO_KEYF2 = 'W' - CTRL_OFFSET,     /* Alternative FKEY2 */
951   PSEUDO_KEYF3 = 'E' - CTRL_OFFSET,     /* Alternative FKEY3 */
952   PSEUDO_KEYF4 = 'R' - CTRL_OFFSET,     /* Alternative FKEY4 */
953   PSEUDO_KEYF5 = 'T' - CTRL_OFFSET,     /* Alternative FKEY5 */
954   PSEUDO_KEYF6 = 'Y' - CTRL_OFFSET,     /* Alternative FKEY6 */
955   PSEUDO_KEYF7 = 'U' - CTRL_OFFSET,     /* Alternative FKEY7 */
956   PSEUDO_KEYF8 = 'I' - CTRL_OFFSET,     /* Alternative FKEY8 */
957   PSEUDO_KEYF9 = 'A' - CTRL_OFFSET,     /* Alternative FKEY9 */
958   PSEUDO_KEYF10 = 'S' - CTRL_OFFSET,    /* Alternative FKEY10 */
959   PSEUDO_KEYF11 = 'D' - CTRL_OFFSET,    /* Alternative FKEY11 */
960   PSEUDO_KEYF12 = 'F' - CTRL_OFFSET,    /* Alternative FKEY12 */
961   PSEUDO_KEYNPAGE = 'O' - CTRL_OFFSET,  /* Alternative PageDown */
962   PSEUDO_KEYPPAGE = 'P' - CTRL_OFFSET   /* Alternative PageUp */
963 };
964 
965 /* User interface event loop running flag. */
966 static bool is_running = true;
967 
968 /* Color definitions. */
969 static const short color_array[] = {
970   COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW,
971   COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE
972 };
973 enum { COLORS_COUNT = sizeof (color_array) / sizeof (color_array[0]) };
974 
975 enum {
976 	DISPLAY_EXTERNAL_COLORS = 1,   /* Normal color pair */
977 	DISPLAY_INTERNAL_COLORS = 2,   /* Blue color pair */
978 	DISPLAY_FOREGROUND      = 7,   /* White foreground */
979 	DISPLAY_BACKGROUND      = 4,   /* Blue background */
980 	BOX_FOREGROUND          = 7,   /* White foreground */
981 	BOX_BACKGROUND          = 0    /* Black background */
982 };
983 
984 /* Color values as arrays into color_array. */
985 static int display_foreground = DISPLAY_FOREGROUND,  /* White foreground */
986            display_background = DISPLAY_BACKGROUND,  /* Blue background */
987            box_foreground = BOX_FOREGROUND,          /* White foreground */
988            box_background = BOX_BACKGROUND;          /* Black background */
989 
990 
991 
992 
993 
994 /**
995    \brief Initialize main window
996 */
ui_init_screen(void)997 WINDOW *ui_init_screen(void)
998 {
999 	/* Create the main window for the complete screen. */
1000 	WINDOW *window = initscr();
1001 	wrefresh(window);
1002 
1003 	/* If using colors, set up a base color for the screen. */
1004 	if (do_colors && has_colors())	{
1005 		start_color();
1006 
1007 		init_pair(DISPLAY_EXTERNAL_COLORS,
1008 			  color_array[box_foreground],
1009 			  color_array[box_background]);
1010 
1011 		init_pair(DISPLAY_INTERNAL_COLORS,
1012 			  color_array[display_foreground],
1013 			  color_array[display_background]);
1014 
1015 		wbkgdset(window, COLOR_PAIR (DISPLAY_EXTERNAL_COLORS) | ' ');
1016 		werase(window);
1017 		wrefresh(window);
1018 	}
1019 
1020 	return window;
1021 }
1022 
1023 
1024 
1025 
1026 
1027 /**
1028    \brief Create new window
1029 
1030    Create new window with initial header and contents.
1031    The function allocates new ncurses WINDOW.
1032 
1033    \return new window
1034 */
ui_init_window(int lines,int columns,int begin_y,int begin_x,const char * header)1035 WINDOW *ui_init_window(int lines, int columns, int begin_y, int begin_x, const char *header)
1036 {
1037 	/* Create the window, and set up colors if possible and requested. */
1038 	WINDOW *window = newwin(lines, columns, begin_y, begin_x);
1039 	if (!window) {
1040 		fprintf(stderr, "newwin()\n");
1041 		exit(EXIT_FAILURE);
1042 	}
1043 
1044 	if (do_colors && has_colors()) {
1045 		wbkgdset(window, COLOR_PAIR (DISPLAY_EXTERNAL_COLORS) | ' ');
1046 		wattron(window, COLOR_PAIR (DISPLAY_EXTERNAL_COLORS));
1047 		werase(window);
1048 	}
1049 
1050 	/* Test on top of the area. */
1051 	box(window, 0, 0);
1052 	mvwaddstr(window, 0, 1, header);
1053 
1054 	wrefresh(window);
1055 	return window;
1056 }
1057 
1058 
1059 
1060 
1061 
1062 /**
1063    \brief Create new main window for Morse code text
1064 
1065    Function allocates two new ncurses WINDOW variables.
1066 */
ui_init_display(int lines,int columns,int begin_y,int begin_x,const char * header,WINDOW ** window,WINDOW ** subwindow)1067 void ui_init_display(int lines, int columns, int begin_y, int begin_x,
1068 			  const char *header,
1069 			  WINDOW **window, WINDOW **subwindow)
1070 {
1071 	*window = ui_init_window(lines, columns, begin_y, begin_x, header);
1072 
1073 
1074 	/* Text subwindow: Create the window, and set up colors if possible and requested. */
1075 	*subwindow = newwin(lines - 2, columns - 2, begin_y + 1, begin_x + 1);
1076 	if (!*subwindow) {
1077 		fprintf(stderr, "newwin()\n");
1078 		exit(EXIT_FAILURE);
1079 	}
1080 
1081 	if (do_colors && has_colors()) {
1082 		wbkgdset(*subwindow, COLOR_PAIR (DISPLAY_INTERNAL_COLORS) | ' ');
1083 		wattron(*subwindow, COLOR_PAIR (DISPLAY_INTERNAL_COLORS));
1084 		werase(*subwindow);
1085 	}
1086 
1087 	wrefresh(*subwindow);
1088 
1089 	return;
1090 }
1091 
1092 
1093 
1094 
1095 
1096 /**
1097    \brief Initialize the user interface, boxes and windows
1098 */
ui_initialize(void)1099 void ui_initialize(void)
1100 {
1101 	static bool is_initialized = false;
1102 
1103 	int max_y, max_x;
1104 
1105 	/* Create the over-arching screen window. */
1106 	screen = ui_init_screen();
1107 	getmaxyx(screen, max_y, max_x);
1108 
1109 	/* Create and box in the mode window. */
1110 	ui_init_display(max_y - 3, 20, 0, 0, _("Mode(F10v,F11^)"), &mode_window, &mode_subwindow);
1111 
1112 	for (int i = 0; i < mode_get_count(); i++) {
1113 		if (i == mode_get_current()) {
1114 			wattron(mode_subwindow, A_REVERSE);
1115 		} else {
1116 			wattroff(mode_subwindow, A_REVERSE);
1117 		}
1118 		mvwaddstr(mode_subwindow, i, 1, mode_get_description(i));
1119 	}
1120 	wrefresh(mode_subwindow);
1121 
1122 	/* Create the text display window; do the introduction only once. */
1123 	ui_init_display(max_y - 3, max_x - 20, 0, 20, _("Start(F9)"), &text_window, &text_subwindow);
1124 	wmove(text_subwindow, 0, 0);
1125 	if (!is_initialized) {
1126 		waddstr(text_subwindow, _(INTRODUCTION));
1127 		waddstr(text_subwindow, _(INTRODUCTION_CONTINUED));
1128 		is_initialized = true;
1129 	}
1130 	wrefresh(text_subwindow);
1131 	idlok(text_subwindow, true);
1132 	immedok(text_subwindow, true);
1133 	scrollok(text_subwindow, true);
1134 
1135 	int lines = 3;
1136 	int columns = CWCP_PARAM_WIDTH;
1137 
1138 	/* Create the control feedback boxes. */
1139 
1140 	ui_init_display(lines, columns, max_y - lines, columns * 0, _("Speed(F1-,F2+)"), &speed_window, &speed_subwindow);
1141 	speed_update();
1142 
1143 	ui_init_display(lines, columns, max_y - lines, columns * 1, _("Tone(F3-,F4+)"), &tone_window, &tone_subwindow);
1144 	frequency_update();
1145 
1146 	ui_init_display(lines, columns, max_y - lines, columns * 2, _("Vol(F5-,F6+)"), &volume_window, &volume_subwindow);
1147 	volume_update();
1148 
1149 	ui_init_display(lines, columns, max_y - lines, columns * 3, _("Gap(F7-,F8+)"), &gap_window, &gap_subwindow);
1150 	gap_update();
1151 
1152 	ui_init_display(lines, columns, max_y - lines, columns * 4, _("Time(Dn-,Up+)"), &timer_window, &timer_subwindow);
1153 	timer_window_update(0, timer_get_total_practice_time());
1154 
1155 	/* Set up curses input mode. */
1156 	keypad(screen, true);
1157 	noecho();
1158 	cbreak();
1159 	curs_set(0);
1160 	raw();
1161 	nodelay(screen, false);
1162 
1163 	wrefresh(curscr);
1164 
1165 	return;
1166 }
1167 
1168 
1169 
1170 
1171 
1172 /**
1173    \brief Dismantle the user interface, boxes and windows
1174 */
ui_destroy(void)1175 void ui_destroy(void)
1176 {
1177 	if (text_subwindow) {
1178 		delwin(text_subwindow);
1179 		text_subwindow = NULL;
1180 	}
1181 	if (text_window) {
1182 		delwin(text_window);
1183 		text_window = NULL;
1184 	}
1185 
1186 	if (mode_subwindow) {
1187 		delwin(mode_subwindow);
1188 		mode_subwindow = NULL;
1189 	}
1190 	if (mode_window) {
1191 		delwin(mode_window);
1192 		mode_window = NULL;
1193 	}
1194 
1195 	if (speed_subwindow) {
1196 		delwin(speed_subwindow);
1197 		speed_subwindow = NULL;
1198 	}
1199 	if (speed_window) {
1200 		delwin(speed_window);
1201 		speed_window = NULL;
1202 	}
1203 
1204 	if (tone_subwindow) {
1205 		delwin(tone_subwindow);
1206 		tone_subwindow = NULL;
1207 	}
1208 	if (tone_window) {
1209 		delwin(tone_window);
1210 		tone_window = NULL;
1211 	}
1212 
1213 	if (volume_subwindow) {
1214 		delwin(volume_subwindow);
1215 		volume_subwindow = NULL;
1216 	}
1217 	if (volume_window) {
1218 		delwin(volume_window);
1219 		volume_window = NULL;
1220 	}
1221 
1222 	if (gap_subwindow) {
1223 		delwin(gap_subwindow);
1224 		gap_subwindow = NULL;
1225 	}
1226 	if (gap_window) {
1227 		delwin(gap_window);
1228 		gap_window = NULL;
1229 	}
1230 
1231 	if (timer_subwindow) {
1232 		delwin(timer_subwindow);
1233 		timer_subwindow = NULL;
1234 	}
1235 	if (timer_window) {
1236 		delwin(timer_window);
1237 		timer_window = NULL;
1238 	}
1239 
1240 	if (screen) {
1241 		/* Clear the screen for neatness. */
1242 		werase(screen);
1243 		wrefresh(screen);
1244 
1245 		delwin(screen);
1246 		screen = NULL;
1247 	}
1248 
1249 	/* End curses processing. */
1250 	endwin();
1251 
1252 	return;
1253 }
1254 
1255 
1256 
1257 
1258 
1259 /**
1260    \brief Assess a user command, and action it if valid
1261 
1262    If the command turned out to be a valid user interface command,
1263    return true, otherwise return false.
1264 */
interface_interpret(int c)1265 static int interface_interpret(int c)
1266 {
1267 	/* Interpret the command passed in */
1268 	switch (c) {
1269 	default:
1270 		return false;
1271 
1272 	case ']':
1273 		display_background = (display_background + 1) % COLORS_COUNT;
1274 		goto color_update;
1275 
1276 	case '[':
1277 		display_foreground = (display_foreground + 1) % COLORS_COUNT;
1278 		goto color_update;
1279 
1280 	case '{':
1281 		box_background = (box_background + 1) % COLORS_COUNT;
1282 		goto color_update;
1283 
1284 	case '}':
1285 		box_foreground = (box_foreground + 1) % COLORS_COUNT;
1286 		goto color_update;
1287 
1288 	color_update:
1289 		if (do_colors && has_colors()) {
1290 			init_pair(DISPLAY_EXTERNAL_COLORS,
1291 				  color_array[box_foreground],
1292 				  color_array[box_background]);
1293 			init_pair(DISPLAY_INTERNAL_COLORS,
1294 				  color_array[display_foreground],
1295 				  color_array[display_background]);
1296 			wrefresh(curscr);
1297 		}
1298 		break;
1299 
1300 
1301 	case 'L' - CTRL_OFFSET:
1302 		wrefresh(curscr);
1303 		break;
1304 
1305 
1306 	case KEY_F (1):
1307 	case PSEUDO_KEYF1:
1308 	case KEY_LEFT:
1309 		if (cw_set_send_speed(cw_get_send_speed() - CW_SPEED_STEP)) {
1310 			speed_update();
1311 		}
1312 		break;
1313 
1314 	case KEY_F (2):
1315 	case PSEUDO_KEYF2:
1316 	case KEY_RIGHT:
1317 		if (cw_set_send_speed(cw_get_send_speed() + CW_SPEED_STEP)) {
1318 			speed_update();
1319 		}
1320 		break;
1321 
1322 
1323 	case KEY_F (3):
1324 	case PSEUDO_KEYF3:
1325 	case KEY_END:
1326 		if (cw_set_frequency(cw_get_frequency() - CW_FREQUENCY_STEP)) {
1327 			frequency_update();
1328 		}
1329 		break;
1330 
1331 	case KEY_F (4):
1332 	case PSEUDO_KEYF4:
1333 	case KEY_HOME:
1334 		if (cw_set_frequency(cw_get_frequency() + CW_FREQUENCY_STEP)) {
1335 			frequency_update();
1336 		}
1337 		break;
1338 
1339 	case KEY_F (5):
1340 	case PSEUDO_KEYF5:
1341 		if (cw_set_volume(cw_get_volume() - CW_VOLUME_STEP)) {
1342 			volume_update();
1343 		}
1344 		break;
1345 
1346 	case KEY_F (6):
1347 	case PSEUDO_KEYF6:
1348 		if (cw_set_volume(cw_get_volume() + CW_VOLUME_STEP)) {
1349 			volume_update();
1350 		}
1351 		break;
1352 
1353 
1354 
1355 	case KEY_F (7):
1356 	case PSEUDO_KEYF7:
1357 		if (cw_set_gap(cw_get_gap() - CW_GAP_STEP)) {
1358 			gap_update();
1359 		}
1360 		break;
1361 
1362 	case KEY_F (8):
1363 	case PSEUDO_KEYF8:
1364 		if (cw_set_gap(cw_get_gap() + CW_GAP_STEP)) {
1365 			gap_update();
1366 		}
1367 		break;
1368 
1369 	case KEY_NPAGE:
1370 	case PSEUDO_KEYNPAGE:
1371 		if (timer_set_total_practice_time(timer_get_total_practice_time() - CW_PRACTICE_TIME_STEP)) {
1372 			timer_window_update(-1, timer_get_total_practice_time());
1373 		}
1374 		break;
1375 
1376 	case KEY_PPAGE:
1377 	case PSEUDO_KEYPPAGE:
1378 		if (timer_set_total_practice_time(timer_get_total_practice_time() + CW_PRACTICE_TIME_STEP)) {
1379 			timer_window_update(-1, timer_get_total_practice_time());
1380 		}
1381 		break;
1382 
1383 	case KEY_F (11):
1384 	case PSEUDO_KEYF11:
1385 	case KEY_UP:
1386 		{
1387 			state_change_to_idle();
1388 			int old_mode = mode_get_current();
1389 			if (mode_change_to_previous()) {
1390 				ui_update_mode_selection(old_mode, mode_get_current());
1391 			}
1392 		}
1393 		break;
1394 
1395 	case KEY_F (10):
1396 	case PSEUDO_KEYF10:
1397 	case KEY_DOWN:
1398 		{
1399 			state_change_to_idle();
1400 			int old_mode = mode_get_current();
1401 			if (mode_change_to_next()) {
1402 				ui_update_mode_selection(old_mode, mode_get_current());
1403 			}
1404 		}
1405 		break;
1406 
1407 	case KEY_F (9):
1408 	case PSEUDO_KEYF9:
1409 	case '\n':
1410 		if (mode_current_is_type(M_EXIT)) {
1411 			is_running = false;
1412 		} else {
1413 			if (!mode_is_sending_active()) {
1414 				state_change_to_active();
1415 			} else {
1416 				if (c != '\n') {
1417 					state_change_to_idle();
1418 				}
1419 			}
1420 		}
1421 		break;
1422 
1423 	case KEY_CLEAR:
1424 	case 'V' - CTRL_OFFSET:
1425 		if (!mode_is_sending_active()) {
1426 			ui_clear_main_window();
1427 		}
1428 		break;
1429 
1430 	case '[' - CTRL_OFFSET:
1431 	case 'Z' - CTRL_OFFSET:
1432 		state_change_to_idle();
1433 		break;
1434 
1435 	case KEY_F (12):
1436 	case PSEUDO_KEYF12:
1437 	case 'C' - CTRL_OFFSET:
1438 		queue_discard_contents();
1439 		cw_flush_tone_queue();
1440 		is_running = false;
1441 		break;
1442 
1443 	case KEY_RESIZE:
1444 		state_change_to_idle();
1445 		ui_destroy();
1446 		ui_initialize();
1447 		break;
1448 	}
1449 
1450 	/* The command was a recognized interface key. */
1451 	return true;
1452 }
1453 
1454 
1455 
1456 
1457 
speed_update(void)1458 void speed_update(void)
1459 {
1460 	char buffer[CWCP_PARAM_WIDTH];
1461 	snprintf(buffer, CWCP_PARAM_WIDTH, _("%2d WPM"), cw_get_send_speed());
1462 	mvwaddstr(speed_subwindow, 0, 4, buffer);
1463 	wrefresh(speed_subwindow);
1464 	return;
1465 }
1466 
1467 
1468 
1469 
1470 
frequency_update(void)1471 void frequency_update(void)
1472 {
1473 	char buffer[CWCP_PARAM_WIDTH];
1474 	snprintf(buffer, CWCP_PARAM_WIDTH, _("%4d Hz"), cw_get_frequency());
1475 	mvwaddstr(tone_subwindow, 0, 3, buffer);
1476 	wrefresh(tone_subwindow);
1477 	return;
1478 }
1479 
1480 
1481 
1482 
1483 
volume_update(void)1484 void volume_update(void)
1485 {
1486 	char buffer[CWCP_PARAM_WIDTH];
1487 	snprintf(buffer, CWCP_PARAM_WIDTH, _("%3d %%"), cw_get_volume());
1488 	mvwaddstr(volume_subwindow, 0, 4, buffer);
1489 	wrefresh(volume_subwindow);
1490 	return;
1491 }
1492 
1493 
1494 
1495 
1496 
gap_update(void)1497 void gap_update(void)
1498 {
1499 	char buffer[CWCP_PARAM_WIDTH];
1500 	int value = cw_get_gap();
1501 	snprintf(buffer, CWCP_PARAM_WIDTH, value == 1 ? _("%2d dot ") : _("%2d dots"), value);
1502 	mvwaddstr(gap_subwindow, 0, 3, buffer);
1503 	wrefresh(gap_subwindow);
1504 	return;
1505 }
1506 
1507 
1508 
1509 
1510 
1511 /**
1512    \brief Handle UI event
1513 
1514    Handle an interface 'event', in this case simply a character from
1515    the keyboard via curses.
1516 */
ui_handle_event(int c)1517 void ui_handle_event(int c)
1518 {
1519 	/* See if this character is a valid user interface command. */
1520 	if (interface_interpret(c)) {
1521 		return;
1522 	}
1523 
1524 	/* If the character is standard 8-bit ASCII or backspace, and
1525 	   the current sending mode is from the keyboard, then make an
1526 	   effort to either queue the character for sending, or delete
1527 	   the most recently queued. */
1528 	if (mode_is_sending_active() && mode_current_is_type(M_KEYBOARD)) {
1529 		if (c == KEY_BACKSPACE || c == KEY_DC) {
1530 			queue_delete_character();
1531 			return;
1532 		} else if (c <= UCHAR_MAX)  {
1533 			queue_enqueue_character((char) c);
1534 			return;
1535 		}
1536 	}
1537 
1538 	/* The 'event' is nothing at all of interest; drop it. */
1539 
1540 	return;
1541 }
1542 
1543 
1544 
1545 
1546 
1547 /**
1548    \brief Check for keyboard input from user
1549 
1550    Calls our sender polling function at regular intervals, and returns only
1551    when data is available to getch(), so that it will not block.
1552 
1553    Opportunistically on every poll check if we need to update queue
1554    of elements to play/display, and if so, do update the queue.
1555 
1556    \param fd - file to pool for new keys from the user
1557    \param usecs - pooling interval
1558 */
ui_poll_user_input(int fd,int usecs)1559 void ui_poll_user_input(int fd, int usecs)
1560 {
1561 	int fd_count;
1562 
1563 	/* Poll until the select indicates data on the file descriptor. */
1564 	do {
1565 		fd_set read_set;
1566 		struct timeval timeout;
1567 
1568 		/* Set up a the file descriptor set and timeout information. */
1569 		FD_ZERO(&read_set);
1570 		FD_SET(fd, &read_set);
1571 		timeout.tv_sec = usecs / 1000000;
1572 		timeout.tv_usec = usecs % 1000000;
1573 
1574 		/* Wait until timeout, data, or a signal.
1575 		   If a signal interrupts select, we can just treat it as
1576 		   another timeout. */
1577 		fd_count = select(fd + 1, &read_set, NULL, NULL, &timeout);
1578 		if (fd_count == -1 && errno != EINTR) {
1579 			perror("select");
1580 			exit(EXIT_FAILURE);
1581 		}
1582 
1583 		/* Make this call on timeouts and on reads; it's just easier. */
1584 		queue_transfer_character_to_libcw();
1585 	} while (fd_count != 1);
1586 
1587 	return;
1588 }
1589 
1590 
1591 
1592 
1593 
ui_clear_main_window(void)1594 void ui_clear_main_window(void)
1595 {
1596 	werase(text_subwindow);
1597 	wmove(text_subwindow, 0, 0);
1598 	wrefresh(text_subwindow);
1599 
1600 	return;
1601 }
1602 
1603 
1604 
1605 
1606 
ui_refresh_main_window(void)1607 void ui_refresh_main_window(void)
1608 {
1609 	touchwin(text_subwindow);
1610 	wnoutrefresh(text_subwindow);
1611 	doupdate();
1612 
1613 	return;
1614 }
1615 
1616 
1617 
1618 
1619 
ui_display_state(const char * state)1620 void ui_display_state(const char *state)
1621 {
1622 	box(text_window, 0, 0);
1623 	mvwaddstr(text_window, 0, 1, state);
1624 	wnoutrefresh(text_window);
1625 	doupdate();
1626 
1627 	return;
1628 }
1629 
1630 
1631 
1632 
1633 
1634 /**
1635    Change appearance of list of modes, indicating current mode
1636 
1637    Change an entry in list of modes, indicating which mode is
1638    currently selected. un-highlight \p old_mode, highlight
1639    \p current_mode.
1640 
1641    \param old_mode - index of old mode
1642    \param current_mode - index of currently selected mode
1643 */
ui_update_mode_selection(int old_mode,int current_mode)1644 void ui_update_mode_selection(int old_mode, int current_mode)
1645 {
1646       wattroff(mode_subwindow, A_REVERSE);
1647       mvwaddstr(mode_subwindow,
1648 		old_mode, 1,
1649 		mode_get_description(old_mode));
1650 
1651       wattron(mode_subwindow, A_REVERSE);
1652       mvwaddstr(mode_subwindow,
1653 		current_mode, 1,
1654 		mode_get_description(current_mode));
1655 
1656       wrefresh(mode_subwindow);
1657 
1658       return;
1659 }
1660 
1661 
1662 
1663 
1664 
1665 /**
1666    \brief Signal handler for signals, to clear up on kill
1667 */
signal_handler(int signal_number)1668 void signal_handler(int signal_number)
1669 {
1670 	/* Attempt to wrestle the screen back from curses. */
1671 	ui_destroy();
1672 
1673 	/* Show the signal caught, and exit. */
1674 	fprintf(stderr, _("\nCaught signal %d, exiting...\n"), signal_number);
1675 	exit(EXIT_SUCCESS);
1676 }
1677 
1678 
1679 
1680 
1681 
1682 /**
1683    Parse the command line, initialize a few things, then enter the
1684    main program event loop, from which there is no return.
1685 */
main(int argc,char ** argv)1686 int main(int argc, char **argv)
1687 {
1688 	atexit(cwcp_atexit);
1689 
1690 	/* Set locale and message catalogs. */
1691 	i18n_initialize();
1692 
1693 	/* Parse combined environment and command line arguments. */
1694 	int combined_argc;
1695 	char **combined_argv;
1696 
1697 	/* Parse combined environment and command line arguments. */
1698 	combine_arguments(_("CWCP_OPTIONS"), argc, argv, &combined_argc, &combined_argv);
1699 
1700 	config = cw_config_new(cw_program_basename(argv[0]));
1701 	if (!config) {
1702 		return EXIT_FAILURE;
1703 	}
1704 	config->has_practice_time = true;
1705 	config->has_outfile = true;
1706 
1707 	if (!cw_process_argv(combined_argc, combined_argv, all_options, config)) {
1708 		fprintf(stderr, _("%s: failed to parse command line args\n"), config->program_name);
1709 		return EXIT_FAILURE;
1710 	}
1711 	free(combined_argv);
1712 	combined_argv = NULL;
1713 
1714 	if (!cw_config_is_valid(config)) {
1715 		fprintf(stderr, _("%s: inconsistent arguments\n"), config->program_name);
1716 		return EXIT_FAILURE;
1717 	}
1718 
1719 	if (config->input_file) {
1720 		if (!cw_dictionaries_read(config->input_file)) {
1721 			fprintf(stderr, _("%s: %s\n"), config->program_name, strerror(errno));
1722 			fprintf(stderr, _("%s: can't load dictionary from input file %s\n"), config->program_name, config->input_file);
1723 			return EXIT_FAILURE;
1724 		}
1725 	}
1726 
1727 	if (config->output_file) {
1728 		if (!cw_dictionaries_write(config->output_file)) {
1729 			fprintf(stderr, _("%s: %s\n"), config->program_name, strerror(errno));
1730 			fprintf(stderr, _("%s: can't save dictionary to output file  %s\n"), config->program_name, config->input_file);
1731 			return EXIT_FAILURE;
1732 		}
1733 	}
1734 
1735 #ifndef __FreeBSD__
1736 	if (config->audio_system == CW_AUDIO_ALSA
1737 	    && cw_is_pa_possible(NULL)) {
1738 
1739 		fprintf(stdout, "Selected audio system is ALSA, but audio on your system is handled by PulseAudio. Expect problems with timing.\n");
1740 		fprintf(stdout, "In this situation it is recommended to run %s like this:\n", config->program_name);
1741 		fprintf(stdout, "%s -s p\n\n", config->program_name);
1742 		fprintf(stdout, "Press Enter key to continue\n");
1743 		getchar();
1744 	}
1745 #endif
1746 
1747 	generator = cw_generator_new_from_config(config);
1748 	if (!generator) {
1749 		fprintf(stderr, "%s: failed to create generator\n", config->program_name);
1750 		return EXIT_FAILURE;
1751 	}
1752 	timer_set_total_practice_time(config->practice_time);
1753 
1754 
1755 	static const int SIGNALS[] = { SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGTERM, 0 };
1756 	/* Set up signal handlers to clear up and exit on a range of signals. */
1757 	for (int i = 0; SIGNALS[i]; i++) {
1758 		if (!cw_register_signal_handler(SIGNALS[i], signal_handler)) {
1759 			fprintf(stderr, _("%s: can't register signal: %s\n"), config->program_name, strerror(errno));
1760 			return EXIT_FAILURE;
1761 		}
1762 	}
1763 
1764 	/* Build our table of modes from dictionaries, augmented with
1765 	   keyboard and any other local modes. */
1766 	mode_initialize();
1767 
1768 	/* Initialize the curses user interface, then catch and action
1769 	   every keypress we see.  Before calling getch, wait until
1770 	   data is available on stdin, polling the libcw sender.  At
1771 	   60WPM, a dot is 20ms, so polling for the maximum library
1772 	   speed needs a 10ms (10,000usec) timeout. */
1773 	ui_initialize();
1774 	cw_generator_start();
1775 	while (is_running) {
1776 		ui_poll_user_input(fileno(stdin), 10000);
1777 		ui_handle_event(getch());
1778 	}
1779 
1780 	cw_wait_for_tone_queue();
1781 
1782 	return EXIT_SUCCESS;
1783 }
1784 
1785 
1786 
1787 
1788 
cwcp_atexit(void)1789 void cwcp_atexit(void)
1790 {
1791 	ui_destroy();
1792 
1793 	if (generator) {
1794 		cw_complete_reset();
1795 		cw_generator_stop();
1796 		cw_generator_delete();
1797 	}
1798 
1799 	mode_clean();
1800 
1801 	cw_dictionaries_unload();
1802 
1803 	if (config) {
1804 		cw_config_delete(&config);
1805 	}
1806 
1807 	return;
1808 }
1809