1 /*
2 qrq - High speed morse trainer, similar to the DOS classic "Rufz"
3 
4 Copyright (C) 2006-2019  Fabian Kurz and contributors
5 
6 This program is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free Software
8 Foundation; either version 2 of the License, or (at your option) any later
9 version.
10 
11 This program is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
13 PARTICULAR PURPOSE.  See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License along with
16 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
17 Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 
19 */
20 #if WIN32
21 #define WIN_THREADS
22 typedef int AUDIO_HANDLE;
23 #endif
24 
25 #ifndef WIN_THREADS
26 #include <pthread.h>			/* CW output will be in a separate thread */
27 #endif
28 #include <ncurses.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <libgen.h>				/* basename */
32 #include <ctype.h>
33 #include <time.h>
34 #include <limits.h> 			/* PATH_MAX */
35 
36 #ifndef PATH_MAX				/* Not defined e.g. on GNU/hurd */
37 #define PATH_MAX 4096
38 #endif
39 
40 #include <dirent.h>
41 #include <math.h>
42 #include <fcntl.h>
43 #include <unistd.h>
44 #include <sys/stat.h>			/* mkdir */
45 #include <sys/types.h>
46 #include <errno.h>
47 #include <stdio.h>
48 #ifdef WIN32
49 #include <windows.h>
50 #endif
51 
52 #define PI M_PI
53 
54 #define SILENCE 0		/* Waveforms for the tone generator */
55 #define SINE 1
56 #define SAWTOOTH 2
57 #define SQUARE 3
58 
59 #ifndef DESTDIR
60 #	define DESTDIR "/usr"
61 #endif
62 
63 #ifndef VERSION
64 #  define VERSION "0.0.0"
65 #endif
66 
67 #ifdef CA
68 #include "coreaudio.h"
69 typedef void *AUDIO_HANDLE;
70 #endif
71 
72 #ifdef OSS
73 #include "oss.h"
74 #define write_audio(x, y, z) write(x, y, z)
75 #define close_audio(x) close(x)
76 typedef int AUDIO_HANDLE;
77 #endif
78 
79 #ifdef PA
80 #include "pulseaudio.h"
81 typedef void *AUDIO_HANDLE;
82 #endif
83 
84 /* callsign array will be dynamically allocated */
85 static char **calls = NULL;
86 
87 const static char *codetable[] = {
88 ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..",".---",
89 "-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",
90 ".--","-..-","-.--","--..","-----",".----","..---","...--","....-",".....",
91 "-....", "--...","---..","----."};
92 
93 /* List of available callbase files. Probably no need to do dynamic memory allocation for that list.... */
94 
95 static char cblist[100][PATH_MAX];
96 
97 static char mycall[15]="DJ1YFK";		/* mycall. will be read from qrqrc */
98 static char dspdevice[PATH_MAX]="/dev/dsp";	/* will also be read from qrqrc */
99 static int score = 0;					/* qrq score */
100 static int sending_complete;			/* global lock for "enter" while sending */
101 static int callnr = 0;					/* nr of actual call in attempt */
102 static int initialspeed=200;			/* initial speed. to be read from file*/
103 static int mincharspeed=0;				/* min. char. speed, below: farnsworth*/
104 static int speed=200;					/* current speed in cpm */
105 static int maxspeed=0;
106 static int freq=800;					/* current cw sidetone freq */
107 static int errornr=0;					/* number of errors in attempt */
108 static int p=0;							/* position of cursor, relative to x */
109 static int status=1;					/* 1= attempt, 2=config */
110 static int mode=1;						/* 0 = overwrite, 1 = insert */
111 static int j=0;							/* counter etc. */
112 static int constanttone=0;              /* if 1 don't change the pitch */
113 static int ctonefreq=800;               /* if constanttone=1 use this freq */
114 static int f6=0;						/* f6 = 1: allow unlimited repeats */
115 static int fixspeed=0;					/* keep speed fixed, regardless of err*/
116 static int unlimitedattempt=0;			/* attempt with all calls  of the DB */
117 static int attemptvalid=1;				/* 1 = not using any "cheats" */
118 static unsigned long int nrofcalls=0;
119 
120 long samplerate=44100;
121 static long long_i;
122 static int waveform = SINE;				/* waveform: (0 = none) */
123 static char wavename[10]="Sine    ";	/* Name of the waveform */
124 static double edge=2.0;						/* rise/fall time in milliseconds */
125 static int ed;							/* risetime, normalized to samplerate */
126 
127 static short buffer[88200];
128 static int full_buf[882000];  /* 20 second max buffer */
129 static int full_bufpos = 0;
130 
131 AUDIO_HANDLE dsp_fd;
132 
133 static int display_toplist();
134 static int calc_score (char * realcall, char * input, int speed, char * output, int f6pressed);
135 static int update_score();
136 static int show_error (char * realcall, char * wrongcall);
137 static int clear_display();
138 static int add_to_toplist(char * mycall, int score, int maxspeed);
139 static int read_config();
140 static int save_config();
141 static int tonegen(int freq, int length, int waveform);
142 static void *morse(void * arg);
143 static int add_to_buf(void* data, int size);
144 static int readline(WINDOW *win, int y, int x, char *line, int i);
145 static void thread_fail (int j);
146 static int check_toplist ();
147 static int find_files ();
148 static int statistics ();
149 static int read_callbase ();
150 static void find_callbases();
151 static void select_callbase ();
152 static void help ();
153 static void callbase_dialog();
154 static void parameter_dialog();
155 static int clear_parameter_display();
156 static void update_parameter_dialog();
157 static void start_summary_file();
158 static void close_summary_file();
159 
160 #ifdef WIN_THREADS
161 HANDLE cwthread;
162 #else
163 pthread_t cwthread;				/* thread for CW output, to enable
164 								   keyboard reading at the same time */
165 pthread_attr_t cwattr;
166 #endif
167 
168 char rcfilename[PATH_MAX]="";			/* filename and path to qrqrc */
169 char tlfilename[PATH_MAX]="";			/* filename and path to toplist */
170 char cbfilename[PATH_MAX]="";			/* filename and path to callbase */
171 char sumfilepath[PATH_MAX]="";			/* path where to save summary files for each attempt */
172 
173 char destdir[PATH_MAX]="";
174 
175 char summary[65536]="";                 /* detailled attempt summary, saved in a file */
176 int s_pos = 0;                          /* Position within summary */
177 
178 /* create windows */
179 WINDOW *top_w;					/* actual score					*/
180 WINDOW *mid_w;					/* callsign history/mistakes	*/
181 WINDOW *conf_w;					/* parameter config display	*/
182 WINDOW *bot_w;					/* user input line				*/
183 WINDOW *inf_w;					/* info window for param displ	*/
184 WINDOW *right_w;				/* highscore list/settings		*/
185 
186 
main(int argc,char * argv[])187 int main (int argc, char *argv[]) {
188 
189   /* if built as osx bundle set DESTDIR to Resources dir of bundle */
190 #ifdef OSX_BUNDLE
191   char tempdir[PATH_MAX]="";
192   char* p_slash = strrchr(argv[0], '/');
193   strncpy(tempdir, argv[0], p_slash - argv[0]);
194   p_slash = strrchr(tempdir, '/');
195   strncpy(destdir, tempdir, p_slash - tempdir);
196   strcat(destdir, "/Resources");
197 #else
198   strcpy(destdir, DESTDIR);
199 #endif
200 
201 	char abort = 0;
202 	char tmp[80]="";
203 	char input[15]="";
204 	int i=0,j=0,k=0;						/* counter etc. */
205 	char previouscall[80]="";
206 	int previousfreq = 0;
207 	int f6pressed=0;
208 
209 	if (argc > 1) {
210 		help();
211 	}
212 
213 	(void) initscr();
214 	cbreak();
215 	noecho();
216 	curs_set(FALSE);
217 	keypad(stdscr, TRUE);
218 	scrollok(stdscr, FALSE);
219 
220 	printw("qrq v%s - Copyright (C) 2006-2019 Fabian Kurz, DJ1YFK\n", VERSION);
221 	printw("This is free software, and you are welcome to redistribute it\n");
222 	printw("under certain conditions (see COPYING).\n");
223 
224 	refresh();
225 
226 	/* search for 'toplist', 'qrqrc' and callbase.qcb and put their locations
227 	 * into tlfilename, rcfilename, cbfilename */
228 	find_files();
229 
230 	/* check if the toplist is in the suitable format. as of 0.0.7, each line
231 	 * is 31 characters long, with the added time stamp */
232 	check_toplist();
233 
234 	/* buffer for audio */
235 	for (long_i=0;long_i<88200;long_i++) {
236 		buffer[long_i]=0;
237 	}
238 
239 	/* random seed from time */
240 	srand( (unsigned) time(NULL) );
241 
242 #ifndef WIN_THREADS
243 	/* Initialize cwthread. We have to wait for the cwthread to finish before
244 	 * the next cw output can be made, this will be done with pthread_join */
245 	pthread_attr_init(&cwattr);
246 	pthread_attr_setdetachstate(&cwattr, PTHREAD_CREATE_JOINABLE);
247 #endif
248 
249 	/****** Reading configuration file ******/
250 	printw("\nReading configuration file qrqrc \n");
251 	read_config();
252 
253 	attemptvalid = 1;
254 	if (f6 || fixspeed || unlimitedattempt) {
255 		attemptvalid = 0;
256 	}
257 
258 	/****** Reading callsign database ******/
259 	printw("\nReading callsign database... ");
260 	nrofcalls = read_callbase();
261 
262 	printw("done. %d calls read.\n\n", nrofcalls);
263 	printw("Press any key to continue...");
264 
265 	refresh();
266 	getch();
267 
268 	erase();
269 	refresh();
270 
271 	top_w = newwin(4, 60, 0, 0);
272 	mid_w = newwin(17, 60, 4, 0);
273 	conf_w = newwin(17, 60, 4, 0);
274 	bot_w = newwin(3, 60, 21, 0);
275 	inf_w = newwin(3, 60, 21, 0);
276 	right_w = newwin(24, 20, 0, 60);
277 
278 	werase(top_w);
279 	werase(mid_w);
280 	werase(conf_w);
281 	werase(bot_w);
282 	werase(inf_w);
283 	werase(right_w);
284 
285 	keypad(bot_w, TRUE);
286 	keypad(mid_w, TRUE);
287 	keypad(conf_w, TRUE);
288 
289 #ifdef WIN_THREADS
290 	cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,"QRQ",0, NULL);
291 #else
292 	/* no need to join here, this is the first possible time CW is sent */
293 	pthread_create(&cwthread, NULL, & morse, (void *) "QRQ");
294 #endif
295 
296 /* very outter loop */
297 while (1) {
298 
299 /* status 1 = running an attempt of 50 calls */
300 while (status == 1) {
301 	box(top_w,0,0);
302 	box(conf_w,0,0);
303 	box(mid_w,0,0);
304 	box(bot_w,0,0);
305 	box(inf_w,0,0);
306 	box(right_w,0,0);
307 	wattron(top_w,A_BOLD);
308 	mvwaddstr(top_w,1,1, "QRQ v");
309 	mvwaddstr(top_w,1,6, VERSION);
310 	wattroff(top_w, A_BOLD);
311 	mvwaddstr(top_w,1,11, " by Fabian Kurz, DJ1YFK");
312 	mvwaddstr(top_w,2,1, "Homepage and Toplist: http://fkurz.net/ham/qrq.html"
313 					"     ");
314 
315 	clear_display();
316 	wattron(mid_w,A_BOLD);
317 	mvwaddstr(mid_w,1,1, "Usage:");
318 	mvwaddstr(mid_w,10,2, "F6                          F10       ");
319 	wattroff(mid_w, A_BOLD);
320 	mvwaddstr(mid_w,2,2, "After entering your callsign, 50 random callsigns");
321 	mvwaddstr(mid_w,3,2, "from a database will be sent. After each callsign,");
322 	mvwaddstr(mid_w,4,2, "enter what you have heard. If you copied correctly,");
323 	mvwaddstr(mid_w,5,2, "full points are credited and the speed increases by");
324 	mvwaddstr(mid_w,6,2, "2 WpM -- otherwise the speed decreases and only a ");
325 	mvwaddstr(mid_w,7,2, "fraction of the points, depending on the number of");
326 	mvwaddstr(mid_w,8,2, "errors is credited.");
327 	mvwaddstr(mid_w,10,2, "F6 repeats a callsign once, F10 quits.");
328 	mvwaddstr(mid_w,12,2, "Settings can be changed with F5 (or in qrqrc).");
329 #ifndef WIN32
330 	mvwaddstr(mid_w,14,2, "Score statistics (requires gnuplot) with F7.");
331 #endif
332 
333 	wattron(right_w,A_BOLD);
334 	mvwaddstr(right_w,1, 6, "Toplist");
335 	wattroff(right_w,A_BOLD);
336 
337 	display_toplist();
338 
339 	p=0;						/* cursor to start position */
340 	wattron(bot_w,A_BOLD);
341 	mvwaddstr(bot_w, 1, 1, "Please enter your callsign:                      ");
342 	wattroff(bot_w,A_BOLD);
343 
344 	wrefresh(top_w);
345 	wrefresh(mid_w);
346 	wrefresh(bot_w);
347 	wrefresh(right_w);
348 
349 	/* reset */
350 	maxspeed = errornr = score = 0;
351 	speed = initialspeed;
352 
353 	/* prompt for own callsign */
354 	i = readline(bot_w, 1, 30, mycall, 1);
355 
356 	/* F5 -> Configure sound */
357 	if (i == 5) {
358 		parameter_dialog();
359 		break;
360 	}
361 	/* F6 -> play test CW */
362 	else if (i == 6) {
363 		freq = constanttone ? ctonefreq : 800;
364 #ifdef WIN_THREADS
365 		 WaitForSingleObject(cwthread,INFINITE);
366 		 CloseHandle(cwthread);
367 		 cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,"VVVTEST",0, NULL);
368 #else
369 		pthread_join(cwthread, NULL);
370 		j = pthread_create(&cwthread, NULL, &morse, (void *) "VVVTEST");
371 		thread_fail(j);
372 #endif
373 		break;
374 	}
375 	else if (i == 7) {
376 #ifndef WIN32
377 		statistics();
378 #endif
379 		break;
380 	}
381 
382 	if (strlen(mycall) == 0) {
383 		strcpy(mycall, "NOCALL");
384 	}
385 	else if (strlen(mycall) > 7) {		/* cut excessively long calls */
386 		mycall[7] = '\0';
387 	}
388 
389 	clear_display();
390 	wrefresh(mid_w);
391 
392 	/* update toplist (highlight may change) */
393 	display_toplist();
394 
395 	mvwprintw(top_w,1,1,"                                      ");
396 	mvwprintw(top_w,2,1,"                                               ");
397 	mvwprintw(top_w,1,1,"Callsign:");
398 	wattron(top_w,A_BOLD);
399 	mvwprintw(top_w,1,11, "%s", mycall);
400 	wattroff(top_w,A_BOLD);
401 	update_score();
402 	wrefresh(top_w);
403 
404 
405 	/* Reread callbase */
406 	nrofcalls = read_callbase();
407 
408 	/****** send 50 or unlimited calls, ask for input, score ******/
409     start_summary_file();
410 
411 	for (callnr=1; callnr < (unlimitedattempt ? nrofcalls : 51); callnr++) {
412 		/* Make sure to wait for the cwthread of the previous callsign, if
413 		 * necessary. */
414 #ifdef WIN_THREADS
415 		WaitForSingleObject(cwthread,INFINITE);
416 		CloseHandle(cwthread);
417 #else
418 		pthread_join(cwthread, NULL);
419 #endif
420 		/* select an unused callsign from the calls-array */
421 		do {
422 			i = (int) ((float) nrofcalls*rand()/(RAND_MAX+1.0));
423 		} while (calls[i] == NULL);
424 
425 		/* only relevant for callbases with less than 50 calls */
426 		if (nrofcalls == callnr) { 		/* Only one call left!" */
427 				callnr =  51; 			/* Get out after next one */
428 		}
429 
430 
431 
432 		/* output frequency handling a) random b) fixed */
433 		if ( constanttone == 0 ) {
434 				/* random freq, fraction of samplerate */
435 				freq = (int) (samplerate/(50+(40.0*rand()/(RAND_MAX+1.0))));
436 		}
437 		else { /* fixed frequency */
438 				freq = ctonefreq;
439 		}
440 
441 		mvwprintw(bot_w,1,1,"                                      ");
442 		mvwprintw(bot_w, 1, 1, "%3d/%s", callnr, unlimitedattempt ? "-" : "50");
443 		wrefresh(bot_w);
444 		tmp[0]='\0';
445 
446 		/* starting the morse output in a separate process to make keyboard
447 		 * input and echoing at the same time possible */
448 
449 		sending_complete = 0;
450 #ifdef WIN_THREADS
451 		cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,calls[i],0, NULL);
452 #else
453 		j = pthread_create(&cwthread, NULL, morse, calls[i]);
454 		thread_fail(j);
455 #endif
456 
457 		f6pressed=0;
458 
459 		while (!abort && (j = readline(bot_w, 1, 8, input,1)) > 4) {/* F5..F10 pressed */
460 
461 			switch (j) {
462 				case 6:		/* repeat call */
463 				if (f6pressed && (f6 == 0)) {
464 					continue;
465 				}
466 				f6pressed=1;
467 				/* wait for old cwthread to finish, then send call again */
468 
469 #ifdef WIN_THREADS
470 			WaitForSingleObject(cwthread,INFINITE);
471 			CloseHandle(cwthread);
472 			cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,calls[i],0, NULL);
473 #else
474 			pthread_join(cwthread, NULL);
475 			j = pthread_create(&cwthread, NULL, &morse, calls[i]);
476 			thread_fail(j);
477 #endif
478 					break; /* 6*/
479 				case 7:		/* repeat _previous_ call */
480 					if (callnr > 1) {
481 						k = freq;
482 						freq = previousfreq;
483 #ifdef WIN_THREADS
484 			WaitForSingleObject(cwthread,INFINITE);
485 			CloseHandle(cwthread);
486 			cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,previouscall,0, NULL);
487 			WaitForSingleObject(cwthread,INFINITE);
488 			CloseHandle(cwthread);
489 #else
490 			pthread_join(cwthread, NULL);
491 			j = pthread_create(&cwthread, NULL, &morse, previouscall);
492 			thread_fail(j);
493 			pthread_join(cwthread, NULL);
494 #endif
495 						/* NB: We must wait for the CW thread before
496 						 * we set the freq back -- this blocks keyboard
497 						 * input, but in this case it shouldn't matter */
498 						freq = k;
499 					}
500 					break;
501 				case 10:	/* abort attempt */
502 					abort = 1;
503 					continue;
504 					break;
505 			}
506 
507 		}
508 
509 
510 		if (abort) {
511 			abort = 0;
512 			input[0]='\0';
513 			break;
514 		}
515 
516 		tmp[0]='\0';
517 		score += calc_score(calls[i], input, speed, tmp, f6pressed);
518 		update_score();
519 		if (strcmp(tmp, "-")) {			/* made an error */
520 				show_error(calls[i], tmp);
521 		}
522 		input[0]='\0';
523 		strncpy(previouscall, calls[i], 80);
524 		previousfreq = freq;
525 		calls[i] = NULL;
526 	}
527 
528     close_summary_file();
529 
530 	/* attempt is over, send AR */
531 	callnr = 0;
532 
533 #ifdef WIN_THREADS
534 		 WaitForSingleObject(cwthread,INFINITE);
535 		 CloseHandle(cwthread);
536 		 cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,"+",0, NULL);
537 #else
538 		pthread_join(cwthread, NULL);
539 		j = pthread_create(&cwthread, NULL, &morse, (void *) "+");
540 		thread_fail(j);
541 #endif
542 
543 	add_to_toplist(mycall, score, maxspeed);
544 
545 	curs_set(0);
546 	wattron(bot_w,A_BOLD);
547 	mvwprintw(bot_w,1,1, "Attempt finished. Press any key to continue!");
548 	wattroff(bot_w,A_BOLD);
549 	wrefresh(bot_w);
550 	getch();
551 	mvwprintw(bot_w,1,1, "                                            ");
552 	curs_set(1);
553 
554 
555 } /* while (status == 1) */
556 
557 
558 } /* very outter loop */
559 
560 	getch();
561 	endwin();
562 	delwin(top_w);
563 	delwin(bot_w);
564 	delwin(mid_w);
565 	delwin(right_w);
566 	getch();
567 	return 0;
568 }
569 
570 
571 
572 /* (formerly status == 2). Change parameters */
573 
parameter_dialog()574 void parameter_dialog () {
575 
576 int j = 0;
577 
578 
579 update_parameter_dialog();
580 
581 while ((j = getch()) != 0) {
582 
583 	switch ((int) j) {
584 		case '+':							/* rise/falltime */
585 			if (edge <= 9.0) {
586 				edge += 0.1;
587 			}
588 			break;
589 		case '-':
590 			if (edge > 0.1) {
591 				edge -= 0.1;
592 			}
593 			break;
594 		case 'w':							/* change waveform */
595 			waveform = ((waveform + 1) % 3)+1;	/* toggle 1-2-3 */
596 			break;
597 		case 'k':							/* constanttone */
598 			if (ctonefreq >= 160) {
599 				ctonefreq -= 10;
600 			}
601 			else {
602 					constanttone = 0;
603 			}
604 			break;
605 		case 'l':
606 			if (constanttone == 0) {
607 				constanttone = 1;
608 			}
609 			else if (ctonefreq < 1600) {
610 				ctonefreq += 10;
611 			}
612 			break;
613 		case '0':
614 			if (constanttone == 1) {
615 				constanttone = 0;
616 			}
617 			else {
618 				constanttone = 1;
619 			}
620 			break;
621 		case 'f':
622 				f6 = (f6 ? 0 : 1);
623 			break;
624 		case 's':
625 				fixspeed = (fixspeed ? 0 : 1);
626 			break;
627 		case 'u':
628 				unlimitedattempt = (unlimitedattempt ? 0 : 1);
629 			break;
630 		case KEY_UP:
631 			initialspeed += 10;
632 			break;
633 		case KEY_DOWN:
634 			if (initialspeed > 10) {
635 				initialspeed -= 10;
636 			}
637 			break;
638 		case KEY_RIGHT:
639 			mincharspeed += 10;
640 			break;
641 		case KEY_LEFT:
642 			if (mincharspeed > 10) {
643 				mincharspeed -= 10;
644 			}
645 			break;
646 		case 'c':
647 			readline(conf_w, 5, 25, mycall, 1);
648 			if (strlen(mycall) == 0) {
649 				strcpy(mycall, "NOCALL");
650 			}
651 			else if (strlen(mycall) > 7) {	/* cut excessively long calls */
652 				mycall[7] = '\0';
653 			}
654 			p=0;							/* cursor position */
655 			break;
656 #ifdef OSS
657 		case 'e':
658 			readline(conf_w, 12, 25, dspdevice, 0);
659 			if (strlen(dspdevice) == 0) {
660 				strcpy(dspdevice, "/dev/dsp");
661 			}
662 			p=0;							/* cursor position */
663 			break;
664 #endif
665 		case 'd':							/* go to database browser */
666 			if (!callnr) {					/* Only allow outside of attempt */
667 				curs_set(1);
668 				callbase_dialog();
669 			}
670 			break;
671 		case KEY_F(2):
672 			save_config();
673 			mvwprintw(conf_w,15,39, "Config saved!");
674 			wrefresh(conf_w);
675 #ifdef WIN32
676 			Sleep(1000);
677 #else
678 			sleep(1);
679 #endif
680 			break;
681 		case KEY_F(6):
682 			freq = constanttone ? ctonefreq : 800;
683 #ifdef WIN_THREADS
684 		 WaitForSingleObject(cwthread,INFINITE);
685 		 CloseHandle(cwthread);
686 		 cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,"TESTING",0, NULL);
687 #else
688 		pthread_join(cwthread, NULL);
689 		j = pthread_create(&cwthread, NULL, &morse, (void *) "TESTING");
690 		thread_fail(j);
691 #endif
692 			break;
693 		case KEY_F(10):
694 		case KEY_F(3):
695 			curs_set(1);
696 			clear_parameter_display();
697 			wrefresh(conf_w);
698 			/* restore old windows */
699 			touchwin(mid_w);
700 			touchwin(bot_w);
701 			wrefresh(mid_w);
702 			wrefresh(bot_w);
703 			return;
704 	}
705 
706 	speed = initialspeed;
707 
708 	attemptvalid = 1;
709 	if (f6 || fixspeed || unlimitedattempt) {
710 		attemptvalid = 0;
711 	}
712 
713 	update_parameter_dialog();
714 
715 } /* while 1 (return only by F3/F10) */
716 
717 } /* parameter_dialog */
718 
719 
720 /* update_parameter_dialog
721  * repaints the whole config/parameter screen (F5) */
722 
723 
update_parameter_dialog()724 void update_parameter_dialog () {
725 
726 	clear_parameter_display();
727 	switch (waveform) {
728 		case SINE:
729 			strcpy(wavename, "Sine    ");
730 			break;
731 		case SAWTOOTH:
732 			strcpy(wavename, "Sawtooth");
733 			break;
734 		case SQUARE:
735 			strcpy(wavename, "Square  ");
736 			break;
737 	}
738 
739 	mvwaddstr(inf_w,1,1, "                                                         ");
740 	curs_set(0);
741 	wattron(conf_w,A_BOLD);
742 	mvwaddstr(conf_w,1,1, "Configuration:          Value                Change");
743 	mvwprintw(conf_w,14,2, "      F6                    F10            ");
744 	mvwprintw(conf_w,15,2, "      F2");
745 	wattroff(conf_w, A_BOLD);
746 	mvwprintw(conf_w,2,2, "Initial Speed:         %3d CpM / %3d WpM"
747 					"    up/down", initialspeed, initialspeed/5);
748 	mvwprintw(conf_w,3,2, "Min. character Speed:  %3d CpM / %3d WpM"
749 					"    left/right", mincharspeed, mincharspeed/5);
750 	mvwprintw(conf_w,4,2, "CW rise/falltime (ms): %1.1f           "
751 					"       +/-", edge);
752 	mvwprintw(conf_w,5,2, "Callsign:              %-14s"
753 					"       c", mycall);
754 	mvwprintw(conf_w,6,2, "CW pitch (0 = random): %-4d"
755 					"                 k/l or 0", (constanttone)?ctonefreq : 0);
756 	mvwprintw(conf_w,7,2, "CW waveform:           %-8s"
757 					"             w", wavename);
758 	mvwprintw(conf_w,8,2, "Allow unlimited F6*:   %-3s"
759 					"                  f", (f6 ? "yes" : "no"));
760 	mvwprintw(conf_w,9,2, "Fixed CW speed*:       %-3s"
761 					"                  s", (fixspeed ? "yes" : "no"));
762 	mvwprintw(conf_w,10,2, "Unlimited attempt*:    %-3s"
763 					"                  u", (unlimitedattempt ? "yes" : "no"));
764 	if (!callnr) {
765 		mvwprintw(conf_w,11,2, "Callsign database:     %-15s"
766 					"      d (%d)", basename(cbfilename),nrofcalls);
767 	}
768 #ifdef OSS
769 	mvwprintw(conf_w,12,2, "DSP device:            %-15s"
770 					"      e", dspdevice);
771 #endif
772 	mvwprintw(conf_w,14,2, "Press");
773 	mvwprintw(conf_w,14,11, "to play sample CW,");
774 	mvwprintw(conf_w,14,34, "to go back.");
775 	mvwprintw(conf_w,15,2, "Press");
776 	mvwprintw(conf_w,15,11, "to save config permanently.");
777 	mvwprintw(inf_w,1,1, "          * Makes scores ineligible for toplist");
778 	wrefresh(conf_w);
779 	wrefresh(inf_w);
780 
781 
782 } /* update_parameter_dialog */
783 
784 
785 
786 
callbase_dialog()787 void callbase_dialog () {
788 
789 	clear_parameter_display();
790 
791 	wattron(conf_w,A_BOLD);
792 	mvwaddstr(conf_w,1,1, "Change Callsign Database");
793 	wattroff(conf_w,A_BOLD);
794 #if WIN32
795 	mvwprintw(conf_w,3,1, ".qcb files found:");
796 #else
797 	mvwprintw(conf_w,3,1, ".qcb files found (in %s/share/qrq/ and ~/.qrq/):",destdir);
798 #endif
799 
800 	/* populate cblist */
801 	find_callbases();
802 	/* selection dialog */
803 	select_callbase();
804 	wrefresh(conf_w);
805 
806 	nrofcalls = read_callbase();
807 
808 
809 	return;	/* back to config menu */
810 }
811 
812 
813 
814 
815 
816 
817 
818 
819 
820 
821 
822 
823 
824 
825 /* reads a callsign etc. in *win at y/x and writes it to *line */
826 
readline(WINDOW * win,int y,int x,char * line,int capitals)827 static int readline(WINDOW *win, int y, int x, char *line, int capitals) {
828 	int c;						/* character we read */
829 	int i=0;
830 
831 	if (strlen(line) == 0) {p=0;}	/* cursor to start if no call in buffer */
832 
833 	if (mode == 1) {
834 		mvwaddstr(win,1,55,"INS");
835 	}
836 	else {
837 		mvwaddstr(win,1,55,"OVR");
838 	}
839 
840 	mvwaddstr(win,y,x,line);
841 	wmove(win,y,x+p);
842 	wrefresh(win);
843 	curs_set(TRUE);
844 
845 	while (1) {
846 		c = wgetch(win);
847 		if (c == '\n' && sending_complete)
848 			break;
849 
850 		if (((c > 64 && c < 91) || (c > 96 && c < 123) || (c > 47 && c < 58)
851 					 || c == '/' || c == ' ') && strlen(line) < 14) {
852 
853 			line[strlen(line)+1]='\0';
854 			if (capitals) {
855 				c = toupper(c);
856 			}
857 			if (mode == 1) {						/* insert */
858 				for(i=strlen(line);i > p; i--) {	/* move all chars by one */
859 					line[i] = line[i-1];
860 				}
861 			}
862 			line[p]=c;						/* insert into gap */
863 			p++;
864 		}
865 		else if ((c == KEY_BACKSPACE || c == 127 || c == 9 || c == 8)
866 						&& p != 0) {					/* BACKSPACE */
867 			for (i=p-1;i < strlen(line); i++) {
868 				line[i] =  line[i+1];
869 			}
870 			p--;
871 		}
872 		else if (c == KEY_DC && strlen(line) != 0) {		/* DELETE */
873 			p++;
874 			for (i=p-1;i < strlen(line); i++) {
875 				line[i] =  line[i+1];
876 			}
877 			p--;
878 		}
879 		else if (c == KEY_LEFT && p != 0) {
880 			p--;
881 		}
882 		else if (c == KEY_RIGHT && p < strlen(line)) {
883 			p++;
884 		}
885 		else if (c == KEY_HOME) {
886 			p = 0;
887 		}
888 		else if (c == KEY_END) {
889 			p = strlen(line);
890 		}
891 		else if (c == KEY_IC) {						/* INS/OVR */
892 			if (mode == 1) {
893 				mode = 0;
894 				mvwaddstr(win,1,55,"OVR");
895 			}
896 			else {
897 				mode = 1;
898 				mvwaddstr(win,1,55,"INS");
899 			}
900 		}
901 		else if (c == KEY_PPAGE && callnr && !attemptvalid) {
902 			speed += 5;
903 			update_score();
904 			wrefresh(top_w);
905 		}
906 		else if (c == KEY_NPAGE && callnr && !attemptvalid) {
907 			if (speed > 20) speed -= 5;
908 			update_score();
909 			wrefresh(top_w);
910 		}
911 		else if (c == KEY_F(5)) {
912 			parameter_dialog();
913 		}
914 		else if (c == KEY_F(6)) {
915 			return 6;
916 		}
917 		else if (c == KEY_F(7)) {
918 			return 7;
919 		}
920 		else if (c == KEY_F(10)) {				/* quit */
921 			if (callnr) {						/* quit attempt only */
922 				return 10;
923 			}
924 			/* else: quit program */
925 			endwin();
926 			printf("Thanks for using 'qrq'!\nYou can submit your"
927 					" highscore to http://fkurz.net/ham/qrqtop.php\n");
928 			/* make sure that no more output is running, then send 73 & quit */
929 			speed = 200; freq = 800;
930 #ifdef WIN_THREADS
931 		 WaitForSingleObject(cwthread,INFINITE);
932 		 CloseHandle(cwthread);
933 		 cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,"73",0, NULL);
934 		 WaitForSingleObject(cwthread,INFINITE);
935 		 CloseHandle(cwthread);
936 #else
937 		pthread_join(cwthread, NULL);
938 		j = pthread_create(&cwthread, NULL, &morse, (void *) "73");
939 		thread_fail(j);
940 			/* make sure the cw thread doesn't die with the main thread */
941 			/* Exit the whole main thread */
942 			pthread_join(cwthread, NULL);
943 #endif
944 			exit(0);
945 		}
946 
947 		mvwaddstr(win,y,x,"                ");
948 		mvwaddstr(win,y,x,line);
949 		wmove(win,y,x+p);
950 		wrefresh(win);
951 	}
952 	curs_set(FALSE);
953 	return 0;
954 }
955 
956 /* Read toplist and diplay first 10 entries */
display_toplist()957 static int display_toplist () {
958 	FILE * fh;
959 	int i=0;
960 	char tmp[35]="";
961 	if ((fh = fopen(tlfilename, "a+")) == NULL) {
962 		endwin();
963 		fprintf(stderr, "Couldn't read or create file '%s'!", tlfilename);
964 		exit(EXIT_FAILURE);
965 	}
966 	rewind(fh);				/* a+ -> end of file, we want the beginning */
967 	(void) fgets(tmp, 34, fh);		/* first line not used */
968 	while ((feof(fh) == 0) && i < 20) {
969 		i++;
970 		if (fgets(tmp, 34, fh) != NULL) {
971 			tmp[17]='\0';
972 			if (strstr(tmp, mycall)) {		/* highlight own call */
973 				wattron(right_w, A_BOLD);
974 			}
975 			mvwaddstr(right_w,i+2, 2, tmp);
976 			wattroff(right_w, A_BOLD);
977 		}
978 	}
979 	fclose(fh);
980 	wrefresh(right_w);
981 	return 0;
982 }
983 
984 /* calculate score depending on number of errors and speed.
985  * writes the correct call and entered call with highlighted errors to *output
986  * and returns the score for this call
987  *
988  * in training modes (unlimited attempts, f6, fixed speed), no points.
989  * */
calc_score(char * realcall,char * input,int spd,char * output,int f6pressed)990 static int calc_score (char * realcall, char * input, int spd, char * output, int f6pressed) {
991 	int i,x,m=0;
992     int score = 0;
993 
994 	x = strlen(realcall);
995 
996 	if (strcmp(input, realcall) == 0) {		 /* exact match! */
997 		output[0]='-';						/* * == OK, no mistake */
998 		output[1]='\0';
999 		if (speed > maxspeed) {maxspeed = speed;}
1000 		if (!fixspeed) speed += 10;
1001 		if (attemptvalid) {
1002             score =  2*x*spd;						/* score */
1003 		}
1004 	}
1005 	else {									/* assemble error string */
1006 		errornr += 1;
1007 		if (strlen(input) >= x) {x =  strlen(input);}
1008 		for (i=0;i < x;i++) {
1009 			if (realcall[i] != input[i]) {
1010 				m++;								/* mistake! */
1011 				output[i] = tolower(input[i]);		/* print as lower case */
1012 			}
1013 			else {
1014 				output[i] = input[i];
1015 			}
1016 		}
1017 		output[i]='\0';
1018 		if ((speed > 29) && !fixspeed) {speed -= 10;}
1019 
1020 		/* score when 1-3 mistakes was made */
1021 		if ((m < 4) && attemptvalid) {
1022 			score = (int) (2*x*spd)/(5*m);
1023 		}
1024 	}
1025 
1026     s_pos += sprintf(summary + s_pos, "%-16s %-16s %-16s %3d %3d %5d %c\r\n", realcall, input, output, spd, spd/5, score, f6pressed ? '*' : ' ');
1027 
1028     return score;
1029 }
1030 
start_summary_file()1031 static void start_summary_file () {
1032     s_pos = 0;
1033     s_pos += sprintf(summary + s_pos, "QRQ attempt by %s.\r\n\r\n", mycall);
1034     s_pos += sprintf(summary + s_pos, "%-16s %-16s %-16s %-3s %-3s %-5s %s\r\n", "Sent call", "Input", "Difference", "CpM", "WpM", "Score", "F6");
1035     s_pos += sprintf(summary + s_pos, "--------------------------------------------------------------------\r\n");
1036 }
1037 
close_summary_file()1038 static void close_summary_file () {
1039     FILE *fh;
1040     time_t t;
1041     struct tm *tmp;
1042     char time_fmt[256];
1043     char filename[PATH_MAX];
1044 
1045     t = time(NULL);
1046     tmp = localtime(&t);
1047     if (tmp == NULL) {
1048         return;
1049     }
1050 
1051     if (strftime(time_fmt, sizeof(time_fmt), "%Y%m%d_%H%M", tmp) == 0) {
1052         return;
1053     }
1054 
1055     s_pos += sprintf(summary + s_pos, "--------------------------------------------------------------------\r\n");
1056     s_pos += sprintf(summary + s_pos, "Score: %d, Max. speed (CpM/WpM): %d / %d\r\nSaved at: %s\r\n", score, maxspeed, maxspeed/5, time_fmt);
1057 
1058     snprintf(filename, PATH_MAX, "%s/%s-%s.txt", sumfilepath, mycall, time_fmt);
1059 
1060 	if ((fh = fopen(filename, "w")) == NULL) {
1061 		printf("Unable to open summary file (%s)!\r\n", filename);
1062 		exit(EXIT_FAILURE);
1063 	}
1064 
1065     fwrite(summary, 1, s_pos, fh);
1066     fclose(fh);
1067 
1068     for (int i = 12; i <= 15; i++) {
1069         mvwprintw(mid_w,i,2, "                                                         ");
1070     }
1071 
1072 	mvwprintw(mid_w,13,1, " Written detailled summary of this attempt to:");
1073 	mvwprintw(mid_w,14,2, filename);
1074     wrefresh(mid_w);
1075 
1076 }
1077 
1078 /* print score, current speed and max speed to window */
update_score()1079 static int update_score() {
1080 	mvwaddstr(top_w,1,20, "Score:                         ");
1081 	mvwaddstr(top_w,2,20, "Speed:     CpM/    WpM, Max:    /  ");
1082 	if (attemptvalid) {
1083 		mvwprintw(top_w, 1, 27, "%6d", score);
1084 	}
1085 	else {
1086 		mvwprintw(top_w, 1, 27, "[training mode]", score);
1087 	}
1088 	mvwprintw(top_w, 2, 27, "%3d", speed);
1089 	mvwprintw(top_w, 2, 35, "%3d", speed/5);
1090 	mvwprintw(top_w, 2, 49, "%3d", maxspeed);
1091 	mvwprintw(top_w, 2, 54, "%3d", maxspeed/5);
1092 	wrefresh(top_w);
1093 	return 0;
1094 }
1095 
1096 /* display the correct callsign and what the user entered, with mistakes
1097  * highlighted. */
show_error(char * realcall,char * wrongcall)1098 static int show_error (char * realcall, char * wrongcall) {
1099 	int x=2;
1100 	int y = errornr;
1101 	int i;
1102 
1103 	/* Screen is full of errors. Remove them and start at the beginning */
1104 	if (errornr == 31) {
1105 		for (i=1;i<16;i++) {
1106 			mvwaddstr(mid_w,i,2,"                                        "
1107 							 "          ");
1108 		}
1109 		errornr = y = 1;
1110 	}
1111 
1112 	/* Move to second column after 15 errors */
1113 	if (errornr > 15) {
1114 		x=30; y = (errornr % 16)+1;
1115 	}
1116 
1117 	mvwprintw(mid_w,y,x, "%-13s %-13s", realcall, wrongcall);
1118 	wrefresh(mid_w);
1119 	return 0;
1120 }
1121 
1122 /* clear error display */
clear_display()1123 static int clear_display() {
1124 	int i;
1125 	for (i=1;i<16;i++) {
1126 		mvwprintw(mid_w,i,1,"                                 "
1127 										"                        ");
1128 	}
1129 	return 0;
1130 }
1131 
1132 /* clear parameter display */
clear_parameter_display()1133 static int clear_parameter_display() {
1134 	int i;
1135 	for (i=1;i<16;i++) {
1136 		mvwprintw(conf_w,i,1,"                                 "
1137 										"                        ");
1138 	}
1139 	return 0;
1140 }
1141 
1142 
1143 /* write entry into toplist at the right place
1144  * going down from the top of the list until the score in the current line is
1145  * lower than the score made. then */
1146 
add_to_toplist(char * mycall,int score,int maxspeed)1147 static int add_to_toplist(char * mycall, int score, int maxspeed) {
1148 	FILE *fh;
1149 	char *part1, *part2;
1150 	char tmp[35]="";
1151 	char insertline[35]="DJ1YFK     36666 333 1111111111";		/* example */
1152 						/* call       pts   max timestamp */
1153 	int i=0, k=0, j=0;
1154 	int pos = 0;		/* position where first score < our score appears */
1155 	int timestamp = 0;
1156 	int len = 32;		/* length of a line. 32 for unix, 33 for Win */
1157 
1158 	/* For the training modes */
1159 	if (score == 0) {
1160 		return 0;
1161 	}
1162 
1163 	timestamp = (int) time(NULL);
1164 	sprintf(insertline, "%-10s%6d %3d %10d", mycall, score, maxspeed, timestamp);
1165 
1166 	if ((fh = fopen(tlfilename, "rb+")) == NULL) {
1167 		printf("Unable to open toplist file %s!\n", tlfilename);
1168 		exit(EXIT_FAILURE);
1169 	}
1170 
1171 	/* find out if we use CRLF or just LF */
1172 	fgets(tmp, 35, fh);
1173 	if (tmp[31] == '\r') {	/* CRLF */
1174 		len = 33;
1175 		strcat(insertline, "\r\n");
1176 	}
1177 	else {
1178 		len = 32;
1179 		strcat(insertline, "\n");
1180 	}
1181 
1182 	fseek(fh, 0, SEEK_END);
1183 	j = ftell(fh);
1184 
1185 	part1 = malloc((size_t) j);
1186 	part2 = malloc((size_t) j + len);	/* one additional entry */
1187 
1188 	rewind(fh);
1189 
1190 	/* read whole toplist */
1191 	fread(part1, sizeof(char), (size_t) j, fh);
1192 
1193 	/* find first score below "score"; scores at positions 10 + (i*len) */
1194 
1195 	do {
1196 		for (i = 0 ; i < 6 ; i++) {
1197 			tmp[i] = part1[i + (10 + pos*len)];
1198 		}
1199 		k = atoi(tmp);
1200 		pos++;
1201 	} while (score < k);
1202 
1203 	/* Found it! Insert own score here! */
1204 	memcpy(part2, part1, len * (pos-1));
1205 	memcpy(part2 + len * (pos - 1), insertline, len);
1206 	memcpy(part2 + len * pos , part1 + len * (pos -1), j - len * (pos - 1));
1207 
1208 	rewind(fh);
1209 	fwrite(part2, sizeof(char), (size_t) j + len, fh);
1210 	fclose(fh);
1211 
1212 	free(part1);
1213 	free(part2);
1214 
1215 	return 0;
1216 
1217 }
1218 
1219 
1220 /* Read config file
1221  *
1222  * TODO contains too much copypasta. write proper function to parse a key=value
1223  *
1224  * */
1225 
read_config()1226 static int read_config () {
1227 	FILE *fh;
1228 	char tmp[80]="";
1229 	int i=0;
1230 	int k=0;
1231 	int line=0;
1232 
1233 	if ((fh = fopen(rcfilename, "r")) == NULL) {
1234 		endwin();
1235 		fprintf(stderr, "Unable to open config file %s!\n", rcfilename);
1236 		exit(EXIT_FAILURE);
1237 	}
1238 
1239 	while ((feof(fh) == 0) && (fgets(tmp, 80, fh) != NULL)) {
1240 		i=0;
1241 		line++;
1242 		tmp[strlen(tmp)-1]='\0';
1243 
1244 		/* find callsign, speed etc.
1245 		 * only allow if the lines are beginning at zero, so stuff can be
1246 		 * commented out easily; return value if strstr must point to tmp*/
1247 		if(tmp == strstr(tmp,"callsign=")) {
1248 			while (isalnum(tmp[i] = toupper(tmp[9+i]))) {
1249 				i++;
1250 			}
1251 			tmp[i]='\0';
1252 			if (strlen(tmp) < 8) {				/* empty call allowed */
1253 				strcpy(mycall,tmp);
1254 				printw("  line  %2d: callsign: >%s<\n", line, mycall);
1255 			}
1256 			else {
1257 				printw("  line  %2d: callsign: >%s< too long. "
1258 								"Using default >%s<.\n", line, tmp, mycall);
1259 			}
1260 		}
1261 		else if (tmp == strstr(tmp,"initialspeed=")) {
1262 			while (isdigit(tmp[i] = tmp[13+i])) {
1263 				i++;
1264 			}
1265 			tmp[i]='\0';
1266 			i = atoi(tmp);
1267 			if (i > 9) {
1268 				initialspeed = speed = i;
1269 				printw("  line  %2d: initial speed: %d\n", line, initialspeed);
1270 			}
1271 			else {
1272 				printw("  line  %2d: initial speed: %d invalid (range: 10..oo)."
1273 								" Using default %d.\n",line,  i, initialspeed);
1274 			}
1275 		}
1276 		else if (tmp == strstr(tmp,"mincharspeed=")) {
1277 			while (isdigit(tmp[i] = tmp[13+i])) {
1278 				i++;
1279 			}
1280 			tmp[i]='\0';
1281 			if ((i = atoi(tmp)) > 0) {
1282 				mincharspeed = i;
1283 				printw("  line  %2d: min.char.speed: %d\n", line, mincharspeed);
1284 			} /* else ignore */
1285 		}
1286 		else if (tmp == strstr(tmp,"dspdevice=")) {
1287 			while (isgraph(tmp[i] = tmp[10+i])) {
1288 				i++;
1289 			}
1290 			tmp[i]='\0';
1291 			if (strlen(tmp) > 1) {
1292 				strcpy(dspdevice,tmp);
1293 				printw("  line  %2d: dspdevice: >%s<\n", line, dspdevice);
1294 			}
1295 			else {
1296 				printw("  line  %2d: dspdevice: >%s< invalid. "
1297 								"Using default >%s<.\n", line, tmp, dspdevice);
1298 			}
1299 		}
1300 		else if (tmp == strstr(tmp, "risetime=")) {
1301 			while (isdigit(tmp[i] = tmp[9+i]) || ((tmp[i] = tmp[9+i])) == '.') {
1302 				i++;
1303 			}
1304 			tmp[i]='\0';
1305 			edge = atof(tmp);
1306 			printw("  line  %2d: risetime: %f\n", line, edge);
1307 		}
1308 		else if (tmp == strstr(tmp, "waveform=")) {
1309 			if (isdigit(tmp[i] = tmp[9+i])) {	/* read 1 char only */
1310 				tmp[++i]='\0';
1311 				waveform = atoi(tmp);
1312 			}
1313 			if ((waveform <= 3) && (waveform > 0)) {
1314 				printw("  line  %2d: waveform: %d\n", line, waveform);
1315 			}
1316 			else {
1317 				printw("  line  %2d: waveform: %d invalid. Using default.\n",
1318 						 line, waveform);
1319 				waveform = SINE;
1320 			}
1321 		}
1322 		else if (tmp == strstr(tmp, "constanttone=")) {
1323 			while (isdigit(tmp[i] = tmp[13+i])) {
1324 				i++;
1325 			}
1326 			tmp[i]='\0';
1327 			k = 0;
1328 			k = atoi(tmp); 							/* constanttone */
1329 			if ( (k*k) > 1) {
1330 				printw("  line  %2d: constanttone: %s invalid. "
1331 							"Using default %d.\n", line, tmp, constanttone);
1332 			}
1333 			else {
1334 				constanttone = k ;
1335 				printw("  line  %2d: constanttone: %d\n", line, constanttone);
1336 			}
1337         }
1338         else if (tmp == strstr(tmp, "ctonefreq=")) {
1339 			while (isdigit(tmp[i] = tmp[10+i])) {
1340             	i++;
1341 			}
1342 			tmp[i]='\0';
1343 			k = 0;
1344 			k = atoi(tmp);							/* ctonefreq */
1345 			if ( (k > 1600) || (k < 100) ) {
1346 				printw("  line  %2d: ctonefreq: %s invalid. "
1347 					"Using default %d.\n", line, tmp, ctonefreq);
1348 			}
1349 			else {
1350 				ctonefreq = k ;
1351 				printw("  line  %2d: ctonefreq: %d\n", line, ctonefreq);
1352 			}
1353 		}
1354 		else if (tmp == strstr(tmp, "f6=")) {
1355 			f6=0;
1356 			if (tmp[3] == '1') {
1357 				f6 = 1;
1358 			}
1359 			printw("  line  %2d: unlimited f6: %s\n", line, (f6 ? "yes":"no"));
1360         }
1361 		else if (tmp == strstr(tmp, "fixspeed=")) {
1362 			fixspeed=0;
1363 			if (tmp[9] == '1') {
1364 				fixspeed = 1;
1365 			}
1366 			printw("  line  %2d: fixed speed:  %s\n", line, (fixspeed ? "yes":"no"));
1367         }
1368 		else if (tmp == strstr(tmp, "unlimitedattempt=")) {
1369 			unlimitedattempt=0;
1370 			if (tmp[17] == '1') {
1371 				unlimitedattempt= 1;
1372 			}
1373 			printw("  line  %2d: unlim. att.:  %s\n", line, (unlimitedattempt ? "yes":"no"));
1374         }
1375 		else if (tmp == strstr(tmp,"callbase=")) {
1376 			while (isgraph(tmp[i] = tmp[9+i])) {
1377 				i++;
1378 			}
1379 			tmp[i]='\0';
1380 			if (strlen(tmp) > 1) {
1381 				strcpy(cbfilename,tmp);
1382 				printw("  line  %2d: callbase:  >%s<\n", line, cbfilename);
1383 			}
1384 			else {
1385 				printw("  line  %2d: callbase:  >%s< invalid. "
1386 								"Using default >%s<.\n", line, tmp, cbfilename);
1387 			}
1388 		}
1389 		else if (tmp == strstr(tmp,"samplerate=")) {
1390 			while (isdigit(tmp[i] = tmp[11+i])) {
1391 				i++;
1392 			}
1393 			tmp[i]='\0';
1394 			samplerate = atoi(tmp);
1395 			printw("  line  %2d: sample rate: %d\n", line, samplerate);
1396 		}
1397 	}
1398 
1399 	fclose(fh);
1400 
1401 	printw("Finished reading qrqrc.\n");
1402 	return 0;
1403 }
1404 
1405 
morse(void * arg)1406 static void *morse(void *arg) {
1407 	char * text = arg;
1408 	int i,j;
1409 	int c, fulldotlen, dotlen, dashlen, charspeed, farnsworth, fwdotlen;
1410 	const char *code;
1411 
1412 #if WIN32 /* WinMM simple support by Lukasz Komsta, SP8QED */
1413 	HWAVEOUT		h;
1414 	WAVEFORMATEX	wf;
1415 	WAVEHDR			wh;
1416 	HANDLE			d;
1417 
1418 	wf.wFormatTag = WAVE_FORMAT_PCM;
1419 	wf.nChannels = 1;
1420 	wf.wBitsPerSample = 16;
1421 	wf.nSamplesPerSec = samplerate * 2;
1422 	wf.nBlockAlign = wf.nChannels * wf.wBitsPerSample / 8;
1423 	wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
1424 	wf.cbSize = 0;
1425 	d = CreateEvent(0, FALSE, FALSE, 0);
1426 	if(waveOutOpen(&h, 0, &wf, (DWORD) d, 0, CALLBACK_EVENT) != MMSYSERR_NOERROR);
1427 
1428 #else
1429 	/* opening the DSP device */
1430 	dsp_fd = open_dsp(dspdevice);
1431 #endif
1432 	/* set bufpos to 0 */
1433 
1434 	full_bufpos = 0;
1435 
1436 	/* Some silence; otherwise the call starts right after pressing enter */
1437 	tonegen(0, samplerate/4, SILENCE);
1438 
1439 	/* Farnsworth? */
1440 	if (speed < mincharspeed) {
1441 			charspeed = mincharspeed;
1442 			farnsworth = 1;
1443 			fwdotlen = (int) (samplerate * 6/speed);
1444 	}
1445 	else {
1446 		charspeed = speed;
1447 		farnsworth = 0;
1448 	}
1449 
1450 	/* speed is in LpM now, so we have to calculate the dot-length in
1451 	 * milliseconds using the well-known formula  dotlength= 60/(wpm*50)
1452 	 * and then to samples */
1453 
1454 	dotlen = (int) (samplerate * 6/charspeed);
1455 	fulldotlen = dotlen;
1456 	dashlen = 3*dotlen;
1457 
1458 	/* edge = length of rise/fall time in ms. ed = in samples */
1459 
1460 	ed = (int) (samplerate * (edge/1000.0));
1461 
1462 	/* the signal needs "ed" samples to reach the full amplitude and
1463 	 * at the end another "ed" samples to reach zero. The dots and
1464 	 * dashes therefore are becoming longer by "ed" and the pauses
1465 	 * after them are shortened accordingly by "ed" samples */
1466 
1467 	for (i = 0; i < strlen(text); i++) {
1468 		c = text[i];
1469 		if (isalpha(c)) {
1470 			code = codetable[c-65];
1471 		}
1472 		else if (isdigit(c)) {
1473 			code = codetable[c-22];
1474 		}
1475 		else if (c == '/') {
1476 			code = "-..-.";
1477 		}
1478 		else if (c == '+') {
1479 			code = ".-.-.";
1480 		}
1481         else if (c == ' ') {        /* space */
1482             code = " ";
1483         }
1484 		else {						/* not supposed to happen! */
1485 			code = "..--..";
1486 		}
1487 
1488 		/* code is now available as string with - and . */
1489 
1490 		for (j = 0; j < strlen(code) ; j++) {
1491 			c = code[j];
1492 			if (c == '.') {
1493 				tonegen(freq, dotlen + ed, waveform);
1494 				tonegen(0, fulldotlen - ed, SILENCE);
1495 			}
1496 			else if (c == '-') {
1497 				tonegen(freq, dashlen + ed, waveform);
1498 				tonegen(0, fulldotlen - ed, SILENCE);
1499 			}
1500             else {  /* space */
1501 				tonegen(0, dotlen, SILENCE);
1502             }
1503 		}
1504 		if (farnsworth) {
1505 			tonegen(0, 3*fwdotlen - fulldotlen, SILENCE);
1506 		}
1507 		else {
1508 			tonegen(0, 2*fulldotlen, SILENCE);
1509 		}
1510 	}
1511 
1512 
1513 #if !defined(PA) && !defined(CA)
1514 	add_to_buf(buffer, 88200);
1515 #endif
1516 
1517 #if WIN32
1518 	wh.lpData = (char*) &full_buf[0];
1519 	wh.dwBufferLength = full_bufpos - 2;
1520 	wh.dwFlags = 0;
1521 	wh.dwLoops = 0;
1522 	waveOutPrepareHeader(h, &wh, sizeof(wh));
1523 	ResetEvent(d);
1524 	waveOutWrite(h, &wh, sizeof(wh));
1525 	if(WaitForSingleObject(d, INFINITE) != WAIT_OBJECT_0);
1526 	waveOutUnprepareHeader(h, &wh, sizeof(wh));
1527 	waveOutClose(h);
1528 	CloseHandle(d);
1529 #else
1530 	write_audio(dsp_fd, &full_buf[0], full_bufpos);
1531 	close_audio(dsp_fd);
1532 #endif
1533 	sending_complete = 1;
1534 	return NULL;
1535 }
1536 
add_to_buf(void * data,int size)1537 static int add_to_buf(void* data, int size)
1538 {
1539 	memcpy(&full_buf[full_bufpos / sizeof(int)], data, size);
1540 	full_bufpos += size;
1541 	return 0;
1542 }
1543 
1544 /* tonegen generates a sinus tone of frequency 'freq' and length 'len' (samples)
1545  * based on 'samplerate', 'edge' (rise/falltime) */
1546 
tonegen(int freq,int len,int waveform)1547 static int tonegen (int freq, int len, int waveform) {
1548 	int x=0;
1549 	int out;
1550 	double val=0;
1551 
1552 	for (x=0; x < len-1; x++) {
1553 		switch (waveform) {
1554 			case SINE:
1555 				val = sin(2*PI*freq*x/samplerate);
1556 				break;
1557 			case SAWTOOTH:
1558 				val=((1.0*freq*x/samplerate)-floor(1.0*freq*x/samplerate))-0.5;
1559 				break;
1560 			case SQUARE:
1561 				val = ceil(sin(2*PI*freq*x/samplerate))-0.5;
1562 				break;
1563 			case SILENCE:
1564 				val = 0;
1565 		}
1566 
1567 
1568 		if (x < ed) { val *= pow(sin(PI*x/(2.0*ed)),2); }	/* rising edge */
1569 
1570 		if (x > (len-ed)) {								/* falling edge */
1571 				val *= pow(sin(2*PI*(x-(len-ed)+ed)/(4*ed)),2);
1572 		}
1573 
1574 		out = (int) (val * 32500.0);
1575 #ifndef PA
1576 		out = out + (out<<16);	/* stereo only for OSS & CoreAudio*/
1577 #endif
1578 		add_to_buf(&out, sizeof(out));
1579 	}
1580 	return 0;
1581 }
1582 
1583 /* Save config file
1584  *
1585  * Tries to keep the old format (including comments, etc.) and adds
1586  * config options that were not used yet in the file to the end
1587  * */
1588 
save_config()1589 static int save_config () {
1590 	FILE *fh;
1591 	char tmp[4096]="";
1592 	char confopts[12][80] = {
1593 		"\ncallsign=",
1594 		"\ncallbase=",
1595 		"\ndspdevice=",
1596 		"\ninitialspeed=",
1597 		"\nmincharspeed=",
1598 		"\nwaveform=",
1599 		"\nconstanttone=",
1600 		"\nctonefreq=",
1601 		"\nfixspeed=",
1602 		"\nunlimitedattempt=",
1603 		"\nf6=",
1604 		"\nrisetime="
1605 	};
1606 	char *conf1;
1607 	char *conf2;
1608 	char *find, *findend;
1609 	int i, len, conf1len, conf2len;
1610 	int j;
1611 
1612 	conf2 = malloc(1);
1613 
1614 	if ((fh = fopen(rcfilename, "rb")) == NULL) {
1615 		endwin();
1616 		fprintf(stderr, "Unable to open config file '%s'!\n", rcfilename);
1617 		exit(EXIT_FAILURE);
1618 	}
1619 
1620 	fseek(fh, 0, SEEK_END);
1621 	j = (int) ftell(fh);
1622 	conf1 = malloc((size_t) j+1);
1623 
1624 	rewind(fh);
1625 	i = fread(conf1, sizeof(char), (size_t) j, fh);
1626 	conf1[j] = '\0';
1627 	conf1len = j;
1628 
1629 	fclose(fh);
1630 
1631 	/* The whole config file is now in conf1
1632 	 *
1633 	 * For each config option, search&replace it with the current value.
1634 	 * Only accept key=value pairs if the key starts on pos 0 of the line
1635 	 * */
1636 
1637 	//endwin();
1638 	for (i = 0; i < 12; i++) {
1639 		/* assemble new string for this conf option*/
1640 		switch (i) {
1641 			case 0:
1642 				sprintf(tmp, "%s%s ", confopts[i], mycall);
1643 				break;
1644 			case 1:
1645 				sprintf(tmp, "%s%s ", confopts[i], cbfilename);
1646 				break;
1647 			case 2:
1648 				sprintf(tmp, "%s%s ", confopts[i], dspdevice);
1649 				break;
1650 			case 3:
1651 				sprintf(tmp, "%s%d ", confopts[i], initialspeed);
1652 				break;
1653 			case 4:
1654 				sprintf(tmp, "%s%d ", confopts[i], mincharspeed);
1655 				break;
1656 			case 5:
1657 				sprintf(tmp, "%s%d ", confopts[i], waveform);
1658 				break;
1659 			case 6:
1660 				sprintf(tmp, "%s%d ", confopts[i], constanttone);
1661 				break;
1662 			case 7:
1663 				sprintf(tmp, "%s%d ", confopts[i], ctonefreq);
1664 				break;
1665 			case 8:
1666 				sprintf(tmp, "%s%d ", confopts[i], fixspeed);
1667 				break;
1668 			case 9:
1669 				sprintf(tmp, "%s%d ", confopts[i], unlimitedattempt);
1670 				break;
1671 			case 10:
1672 				sprintf(tmp, "%s%d ", confopts[i], f6);
1673 				break;
1674 			case 11:
1675 				sprintf(tmp, "%s%f ", confopts[i], edge);
1676 				break;
1677 		}
1678 
1679 		/* Conf option already in rc-file? */
1680 		if ((find = strstr(conf1, confopts[i])) != NULL) {
1681 			/* determine length. */
1682 			findend = find;
1683 			findend++;	/* starts with \n, always skip it */
1684 			while (!isspace(*findend++));
1685 			len = findend - find;
1686 
1687 			/* old size of conf1: conf1len (see above)
1688 			 * new size: conf1len - len + strlen(tmp)*/
1689 			conf2len = conf1len - len + strlen(tmp);
1690 			conf2 = realloc(conf2, (size_t) conf2len);
1691 			memcpy(conf2, conf1, (find - conf1));
1692 			memcpy(conf2 + (find - conf1), tmp, strlen(tmp));
1693 			memcpy(conf2 + (find - conf1) + strlen(tmp), findend,
1694 					(size_t) conf2len - (find - conf1) - strlen(tmp));
1695 		}
1696 		/* otherwise, add to the end */
1697 		else {
1698 			/* CR LF or LF only? */
1699 			if (strstr(conf1, "\r")) {
1700 				strcat(tmp, "\r");
1701 			}
1702 			strcat(tmp, "\n");
1703 			conf2len = conf1len + strlen(tmp) - 1;
1704 			conf2 = realloc(conf2, conf2len);
1705 			memcpy(conf2, conf1, conf1len-1);  // excl. \0
1706 			memcpy(conf2 + conf1len - 1, tmp, strlen(tmp)); // (incl. \0)
1707 		}
1708 		conf1 = realloc(conf1, (size_t) conf2len);
1709 		conf1len = conf2len;
1710 		if (conf1 == NULL) {
1711 			exit(0);
1712 		}
1713 		memcpy(conf1, conf2, conf1len);
1714 	}
1715 
1716 	if ((fh = fopen(rcfilename, "wb")) == NULL) {
1717 		endwin();
1718 		fprintf(stderr, "Unable to open config file '%s'!\n", rcfilename);
1719 		exit(EXIT_FAILURE);
1720 	}
1721 
1722 	fwrite(conf1, conf1len, sizeof(char), fh);
1723 	fclose(fh);
1724 
1725 	free(conf1);
1726 	free(conf2);
1727 
1728 	return 0;
1729 }
1730 
thread_fail(int j)1731 static void thread_fail (int j) {
1732 	if (j) {
1733 		endwin();
1734 		perror("Error: Unable to create cwthread!\n");
1735 		exit(EXIT_FAILURE);
1736 	}
1737 }
1738 
1739 /* Add timestamps to toplist file if not there yet */
check_toplist()1740 static int check_toplist () {
1741 	char line[80]="";
1742 	char tmp[80]="";
1743 	FILE *fh;
1744 	FILE *fh2;
1745 
1746 	if ((fh = fopen(tlfilename, "r+")) == NULL) {
1747 		endwin();
1748 		perror("Unable to open toplist file 'toplist'!\n");
1749 		exit(EXIT_FAILURE);
1750 	}
1751 
1752 	fgets(tmp, 35, fh);
1753 
1754 	rewind(fh);
1755 
1756 	if (strlen(tmp) == 21) {
1757 			printw("Toplist file in old format. Converting...");
1758 			strcpy(tmp, "cp -f ");
1759 			strcat(tmp, tlfilename);
1760 			strcat(tmp, " /tmp/qrq-toplist");
1761 			if (system(tmp)) {
1762 					printw("Failed to copy to /tmp/qrq-toplist\n");
1763 					getch();
1764 					endwin();
1765 					exit(EXIT_FAILURE);
1766 			}
1767 
1768 			fh2 = fopen("/tmp/qrq-toplist", "r+"); 		/* should work ... */
1769 
1770 			while ((feof(fh2) == 0) && (fgets(line, 35, fh2) != NULL)) {
1771 					line[20]=' ';
1772 					strcpy(tmp, line);
1773 					strcat(tmp, "1181234567\n");
1774 					fputs(tmp, fh);
1775 			}
1776 
1777 			printw(" done!\n");
1778 
1779 			fclose(fh2);
1780 
1781 	}
1782 
1783 	fclose(fh);
1784 
1785 	return 0;
1786 }
1787 
1788 
1789 
1790 /* See where our files are. We need 'callbase.qcb', 'qrqrc' and 'toplist'.
1791  * The can be:
1792  * 1) In the current directory -> use them
1793  * 2) In ~/.qrq/  -> use toplist and qrqrc from there and callbase from
1794  *    DESTDIR/share/qrq/
1795  * 3) in DESTDIR/share/qrq/ -> create ~/.qrq/ and copy qrqrc and toplist
1796  *    there.
1797  * 4) Nowhere --> Exit.*/
find_files()1798 static int find_files () {
1799 
1800 	FILE *fh;
1801 	const char *homedir = NULL;
1802 	char tmp_rcfilename[PATH_MAX] = "";
1803 	char tmp_tlfilename[PATH_MAX] = "";
1804 	char tmp_cbfilename[PATH_MAX] = "";
1805 
1806 	printw("\nChecking for necessary files (qrqrc, toplist, callbase)...\n");
1807 
1808 	if (((fh = fopen("qrqrc", "r")) == NULL) ||
1809 		((fh = fopen("toplist", "r")) == NULL) ||
1810 		((fh = fopen("callbase.qcb", "r")) == NULL)) {
1811 
1812 		if ((homedir = getenv("HOME")) != NULL) {
1813     		printw("... not found in current directory. Checking %s/.qrq/...\n", homedir);
1814     		refresh();
1815 	    	strcat(rcfilename, homedir);
1816 		}
1817 		else {
1818 		    printw("... not found in current directory. Checking ./.qrq/...\n");
1819     		refresh();
1820 	    	strcat(rcfilename, ".");
1821 		}
1822 
1823 		strcat(rcfilename, "/.qrq/qrqrc");
1824 
1825 		/* check if there is ~/.qrq/qrqrc. If it's there, it's safe to assume
1826 		 * that toplist also exists at the same place and callbase exists in
1827 		 * DESTDIR/share/qrq/. */
1828 
1829 		if ((fh = fopen(rcfilename, "r")) == NULL ) {
1830 			printw("... not found in %s/.qrq/. Checking %s/share/qrq..."
1831 							"\n", homedir, destdir);
1832 			/* check for the files in DESTDIR/share/qrq/. if exists, copy
1833 			 * qrqrc and toplist to ~/.qrq/  */
1834 
1835 			strcpy(tmp_rcfilename, destdir);
1836 			strcat(tmp_rcfilename, "/share/qrq/qrqrc");
1837 			strcpy(tmp_tlfilename, destdir);
1838 			strcat(tmp_tlfilename, "/share/qrq/toplist");
1839 			strcpy(tmp_cbfilename, destdir);
1840 			strcat(tmp_cbfilename, "/share/qrq/callbase.qcb");
1841 
1842 			if (((fh = fopen(tmp_rcfilename, "r")) == NULL) ||
1843 				((fh = fopen(tmp_tlfilename, "r")) == NULL) ||
1844 				 ((fh = fopen(tmp_cbfilename, "r")) == NULL)) {
1845 				printw("Sorry: Couldn't find 'qrqrc', 'toplist' and"
1846 			   			" 'callbase.qcb' anywhere. Exit.\n");
1847 				getch();
1848 				endwin();
1849 				exit(EXIT_FAILURE);
1850 			}
1851 			else {			/* finally found it in DESTDIR/share/qrq/ ! */
1852 				/* abusing rcfilename here for something else temporarily */
1853 				printw("Found files in %s/share/qrq/."
1854 						"\nCreating directory %s/.qrq/ and copy qrqrc and"
1855 						" toplist there.\n", destdir, homedir);
1856 				strcpy(rcfilename, homedir);
1857 				strcat(rcfilename, "/.qrq/");
1858 #ifdef WIN32
1859 				j = mkdir(rcfilename);
1860 #else
1861 				j = mkdir(rcfilename,  0777);
1862 #endif
1863 				if (j && (errno != EEXIST)) {
1864 					printw("Failed to create %s! Exit.\n", rcfilename);
1865 					getch();
1866 					endwin();
1867 					exit(EXIT_FAILURE);
1868 				}
1869 
1870 				/* OK, now we created the directory, we can read in
1871 				 * DESTDIR/local/, so I assume copying files won't cause any
1872 				 * problem, with system()... */
1873 
1874 				strcpy(rcfilename, "install -m 644 ");
1875 				strcat(rcfilename, tmp_tlfilename);
1876 				strcat(rcfilename, " ");
1877 				strcat(rcfilename, homedir);
1878 				strcat(rcfilename, "/.qrq/ 2> /dev/null");
1879 				if (system(rcfilename)) {
1880 					printw("Failed to copy toplist file: %s\n", rcfilename);
1881 					getch();
1882 					endwin();
1883 					exit(EXIT_FAILURE);
1884 				}
1885 				strcpy(rcfilename, "install -m 644 ");
1886 				strcat(rcfilename, tmp_rcfilename);
1887 				strcat(rcfilename, " ");
1888 				strcat(rcfilename, homedir);
1889 				strcat(rcfilename, "/.qrq/ 2> /dev/null");
1890 				if (system(rcfilename)) {
1891 					printw("Failed to copy qrqrc file: %s\n", rcfilename);
1892 					getch();
1893 					endwin();
1894 					exit(EXIT_FAILURE);
1895 				}
1896 				printw("Files copied. You might want to edit "
1897 						"qrqrc according to your needs.\n", homedir);
1898 				strcpy(rcfilename, homedir);
1899 				strcat(rcfilename, "/.qrq/qrqrc");
1900 				strcpy(tlfilename, homedir);
1901 				strcat(tlfilename, "/.qrq/toplist");
1902 				strcpy(cbfilename, tmp_cbfilename);
1903                 strcpy(sumfilepath, homedir);
1904                 strcat(sumfilepath, "/.qrq/Summary");
1905 			} /* found in DESTDIR/share/qrq/ */
1906 		}
1907 		else {
1908 			printw("... found files in %s/.qrq/.\n", homedir);
1909 			strcpy(tlfilename, homedir);
1910 			strcat(tlfilename, "/.qrq/toplist");
1911 			strcpy(cbfilename, destdir);
1912 			strcat(cbfilename, "/share/qrq/callbase.qcb");
1913             strcpy(sumfilepath, homedir);
1914             strcat(sumfilepath, "/.qrq/Summary");
1915 		}
1916 	}
1917 	else {
1918 		printw("... found in current directory.\n");
1919 		strcpy(rcfilename, "qrqrc");
1920 		strcpy(tlfilename, "toplist");
1921 		strcpy(cbfilename, "callbase.qcb");
1922         strcpy(sumfilepath, "Summary");
1923 	}
1924 #ifdef WIN32
1925     mkdir(sumfilepath);
1926 #else
1927     mkdir(sumfilepath, 0777);
1928 #endif
1929 	refresh();
1930 	fclose(fh);
1931 	return 0;
1932 }
1933 
1934 
statistics()1935 static int statistics () {
1936 		char line[80]="";
1937 
1938 		int time = 0;
1939 		int score = 0;
1940 		int count= 0;
1941 
1942 		FILE *fh;
1943 		FILE *fh2;
1944 
1945 		if ((fh = fopen(tlfilename, "r")) == NULL) {
1946 				fprintf(stderr, "Unable to open toplist.");
1947 				exit(0);
1948 		}
1949 
1950 		if ((fh2 = fopen("/tmp/qrq-plot", "w+")) == NULL) {
1951 				fprintf(stderr, "Unable to open /tmp/qrq-plot.");
1952 				exit(0);
1953 		}
1954 
1955 		fprintf(fh2, "set yrange [0:]\nset xlabel \"Date/Time\"\n"
1956 					"set title \"QRQ scores for %s. Press 'q' to "
1957 					"close this window.\"\n"
1958 					"set ylabel \"Score\"\nset xdata time\nset "
1959 					" timefmt \"%%s\"\n "
1960 					"plot \"-\" using 1:2 title \"\"\n", mycall);
1961 
1962 		while ((feof(fh) == 0) && (fgets(line, 80, fh) != NULL)) {
1963 				if ((strstr(line, mycall) != NULL)) {
1964 					count++;
1965 					sscanf(line, "%*s %d %*d %d", &score, &time);
1966 					fprintf(fh2, "%d %d\n", time, score);
1967 				}
1968 		}
1969 
1970 		if (!count) {
1971 			fprintf(fh2, "0 0\n");
1972 		}
1973 
1974 		fprintf(fh2, "end\npause 10000");
1975 
1976 		fclose(fh);
1977 		fclose(fh2);
1978 
1979 		system("gnuplot -p /tmp/qrq-plot 2> /dev/null &");
1980 	return 0;
1981 }
1982 
1983 
read_callbase()1984 int read_callbase () {
1985 	FILE *fh;
1986 	int c,i;
1987 	int maxlen=0;
1988 	char tmp[80] = "";
1989 	int nr=0;
1990 
1991 	if ((fh = fopen(cbfilename, "r")) == NULL) {
1992 		endwin();
1993 		fprintf(stderr, "Error: Couldn't read callsign database ('%s')!\n",
1994 						cbfilename);
1995 		exit(EXIT_FAILURE);
1996 	}
1997 
1998 	/* count the lines/calls and lengths */
1999 	i=0;
2000 	while ((c = getc(fh)) != EOF) {
2001 		i++;
2002 		if (c == '\n') {
2003 			nr++;
2004 			maxlen = (i > maxlen) ? i : maxlen;
2005 			i = 0;
2006 		}
2007 	}
2008 	maxlen++;
2009 
2010 	if (!nr) {
2011 		endwin();
2012 		printf("\nError: Callsign database empty, no calls read. Exiting.\n");
2013 		exit(EXIT_FAILURE);
2014 	}
2015 
2016 	/* allocate memory for calls array, free if needed */
2017     if (calls) {
2018         for (i = 0; i < nrofcalls; i++) {
2019             free(calls[i]);
2020             calls[i] = NULL;
2021         }
2022         free(calls);
2023         calls = NULL;
2024     }
2025 
2026 	if ((calls = (char **) malloc( (size_t) sizeof(char *)*nr )) == NULL) {
2027 		fprintf(stderr, "Error: Couldn't allocate %d bytes!\n",
2028 						(int) sizeof(char)*nr);
2029 		exit(EXIT_FAILURE);
2030 	}
2031 
2032 	/* Allocate each element of the array with size maxlen */
2033 	for (c=0; c < nr; c++) {
2034 		if ((calls[c] = (char *) malloc (maxlen * sizeof(char))) == NULL) {
2035 			fprintf(stderr, "Error: Couldn't allocate %d bytes!\n", maxlen);
2036 			exit(EXIT_FAILURE);
2037 		}
2038 	}
2039 
2040 	rewind(fh);
2041 
2042 	nr=0;
2043 	while (fgets(tmp,maxlen,fh) != NULL) {
2044 		for (i = 0; i < strlen(tmp); i++) {
2045 				tmp[i] = toupper(tmp[i]);
2046 		}
2047 		tmp[i-1]='\0';				/* remove newline */
2048 		if (tmp[i-2] == '\r') {		/* also for DOS files */
2049 			tmp[i-2] = '\0';
2050 		}
2051 		strcpy(calls[nr],tmp);
2052 		nr++;
2053 		if (nr == c) 			/* may happen if call file corrupted */
2054 				break;
2055 	}
2056 	fclose(fh);
2057 
2058 
2059 	return nr;
2060 
2061 }
2062 
find_callbases()2063 void find_callbases () {
2064 	DIR *dir;
2065 	struct dirent *dp;
2066 	char tmp[PATH_MAX];
2067 	char path[3][PATH_MAX];
2068 	int i=0,j=0,k=0;
2069 
2070 #ifndef WIN32
2071 		strcpy(path[0], getenv("PWD"));
2072 		strcat(path[0], "/");
2073 		strcpy(path[1], getenv("HOME"));
2074 		strcat(path[1], "/.qrq/");
2075 		strcpy(path[2], destdir);
2076 		strcat(path[2], "/share/qrq/");
2077 #else
2078 		strcpy(path[0], "./");
2079 		strcpy(path[1], getenv("APPDATA"));
2080 		strcat(path[1], "/qrq/");
2081 		strcpy(path[2], "c:\\");
2082 #endif
2083 
2084 	for (i=0; i < 100; i++) {
2085 		strcpy(cblist[i], "");
2086 	}
2087 
2088 	/* foreach paths...  */
2089 	for (k = 0; k < 3; k++) {
2090 
2091 		if (!(dir = opendir(path[k]))) {
2092 			continue;
2093 		}
2094 
2095 		while ((dp = readdir(dir))) {
2096 			strcpy(tmp, dp->d_name);
2097 			i = strlen(tmp);
2098 			/* find *.qcb files ...  */
2099 			if (i>4 && tmp[i-1] == 'b' && tmp[i-2] == 'c' && tmp[i-3] == 'q') {
2100 				strcpy(cblist[j], path[k]);
2101 				strcat(cblist[j], tmp);
2102 				j++;
2103 			}
2104 		}
2105 	} /* for paths */
2106 }
2107 
2108 
2109 
select_callbase()2110 void select_callbase () {
2111 	int i = 0, j = 0, k = 0;
2112 	int c = 0;		/* cursor position   */
2113 	int p = 0;		/* page a 10 entries */
2114 	char* cblist_ptr;
2115 
2116 
2117 	curs_set(FALSE);
2118 
2119 	/* count files */
2120 	while (strcmp(cblist[i], "")) i++;
2121 
2122 	if (!i) {
2123 		mvwprintw(conf_w,10,4, "No qcb-files found!");
2124 		wrefresh(conf_w);
2125 #ifdef WIN32
2126 		Sleep(1000);
2127 #else
2128 		sleep(1);
2129 #endif
2130 		return;
2131 	}
2132 
2133 	/* loop for key unput */
2134 	while (1) {
2135 
2136 	/* cls */
2137 	for (j = 5; j < 16; j++) {
2138 			mvwprintw(conf_w,j,2, "                                         ");
2139 	}
2140 
2141 	/* display 10 files, highlight cursor position */
2142 	for (j = p*10; j < (p+1)*10; j++) {
2143 		if (j <= i) {
2144 				cblist_ptr = cblist[j];
2145 				mvwprintw(conf_w,5+(j - p*10 ),2, "  %s       ", cblist_ptr);
2146 		}
2147 		if (c == j) {						/* cursor */
2148 			mvwprintw(conf_w,5+(j - p*10),2, ">");
2149 		}
2150 	}
2151 
2152 	wrefresh(conf_w);
2153 
2154 	k = getch();
2155 
2156 	switch ((int) k) {
2157 		case KEY_UP:
2158 			c = (c > 0) ? (c-1) : c;
2159 			if (!((c+1) % 10)) {	/* scroll down */
2160 				p = (p > 0) ? (p-1) : p;
2161 			}
2162 			break;
2163 		case KEY_DOWN:
2164 			c = (c < i-1) ? (c+1) : c;
2165 			if (c && !(c % 10)) {	/* scroll down */
2166 				p++;
2167 			}
2168 			break;
2169 		case '\n':
2170 			strcpy(cbfilename, cblist[c]);
2171 			nrofcalls = read_callbase();
2172 			return;
2173 			break;
2174 	}
2175 
2176 	wrefresh(conf_w);
2177 
2178 	} /* while 1 */
2179 
2180 	curs_set(TRUE);
2181 
2182 }
2183 
2184 
2185 
help()2186 void help () {
2187 		printf("qrq v%s  (c) 2006-2019 Fabian Kurz, DJ1YFK. "
2188 					"http://fkurz.net/ham/qrq.html\n", VERSION);
2189 		printf("High speed morse telegraphy trainer, similar to"
2190 					" RUFZ.\n\n");
2191 		printf("This is free software, and you are welcome to"
2192 						" redistribute it\n");
2193 		printf("under certain conditions (see COPYING).\n\n");
2194 		printf("Start 'qrq' without any command line arguments for normal"
2195 					" operation.\n\n");
2196 #ifdef BUILD_INFO
2197         printf("Build info for this executable:\n%s\n", BUILD_INFO);
2198 #endif
2199 		exit(0);
2200 }
2201 
2202 
2203 /* vim: noai:ts=4:sw=4
2204 */
2205