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