mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 18:55:19 +00:00 
			
		
		
		
	
		
			
	
	
		
			1035 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			1035 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*
 | ||
|  |  * Asterisk -- An open source telephony toolkit. | ||
|  |  * | ||
|  |  * Copyright (C) 2005 - 2006, Russell Bryant | ||
|  |  * | ||
|  |  * Russell Bryant <russell@digium.com> | ||
|  |  * | ||
|  |  * See http://www.asterisk.org for more information about
 | ||
|  |  * the Asterisk project. Please do not directly contact | ||
|  |  * any of the maintainers of this project for assistance; | ||
|  |  * the project provides a web site, mailing lists and IRC | ||
|  |  * channels for your use. | ||
|  |  * | ||
|  |  * This program is free software, distributed under the terms of | ||
|  |  * the GNU General Public License Version 2. See the LICENSE file | ||
|  |  * at the top of the source tree. | ||
|  |  */ | ||
|  | 
 | ||
|  | /*
 | ||
|  |  * \file | ||
|  |  * | ||
|  |  * \author Russell Bryant <russell@digium.com> | ||
|  |  *  | ||
|  |  * \brief curses frontend for selection maintenance | ||
|  |  */ | ||
|  | 
 | ||
|  | #include <stdlib.h>
 | ||
|  | #include <stdio.h>
 | ||
|  | #include <sys/types.h>
 | ||
|  | #include <unistd.h>
 | ||
|  | #include <string.h>
 | ||
|  | #include <signal.h>
 | ||
|  | #include <time.h>
 | ||
|  | #include <curses.h>
 | ||
|  | 
 | ||
|  | #include "menuselect.h"
 | ||
|  | 
 | ||
|  | #define MENU_HELP	"Press 'h' for help."
 | ||
|  | 
 | ||
|  | #define TITLE_HEIGHT	7
 | ||
|  | 
 | ||
|  | #define MIN_X		80
 | ||
|  | #define MIN_Y		27
 | ||
|  | 
 | ||
|  | #define PAGE_OFFSET	10
 | ||
|  | 
 | ||
|  | #define SCROLL_NONE     0
 | ||
|  | #define SCROLL_DOWN     1
 | ||
|  | 
 | ||
|  | #define SCROLL_DOWN_INDICATOR "... More ..."
 | ||
|  | 
 | ||
|  | #define MIN(a, b) ({ typeof(a) __a = (a); typeof(b) __b = (b); ((__a > __b) ? __b : __a);})
 | ||
|  | #define MAX(a, b) ({ typeof(a) __a = (a); typeof(b) __b = (b); ((__a < __b) ? __b : __a);})
 | ||
|  | 
 | ||
|  | extern int changes_made; | ||
|  | 
 | ||
|  | /*! Maximum number of characters horizontally */ | ||
|  | static int max_x = 0; | ||
|  | /*! Maximum number of characters vertically */ | ||
|  | static int max_y = 0; | ||
|  | 
 | ||
|  | static const char * const help_info[] = { | ||
|  | 	"scroll              => up/down arrows", | ||
|  | 	"toggle selection    => Enter", | ||
|  | 	"select              => y", | ||
|  | 	"deselect            => n", | ||
|  | 	"select all          => F8", | ||
|  | 	"deselect all        => F7", | ||
|  | 	"back                => left arrow", | ||
|  | 	"quit                => q", | ||
|  | 	"save and quit       => x", | ||
|  | 	"", | ||
|  | 	"XXX means dependencies have not been met", | ||
|  | 	"    or a conflict exists", | ||
|  | 	"", | ||
|  | 	"< > means a dependency has been deselected", | ||
|  | 	"    and will be automatically re-selected", | ||
|  | 	"    if this item is selected", | ||
|  | 	"", | ||
|  | 	"( ) means a conflicting item has been", | ||
|  | 	"    selected", | ||
|  | }; | ||
|  | 
 | ||
|  | /*! \brief Handle a window resize in xterm */ | ||
|  | static void _winch_handler(int sig) | ||
|  | { | ||
|  | 	getmaxyx(stdscr, max_y, max_x); | ||
|  | 
 | ||
|  | 	if (max_x < MIN_X || max_y < MIN_Y) { | ||
|  | 		fprintf(stderr, "Terminal must be at least %d x %d.\n", MIN_X, MIN_Y); | ||
|  | 		max_x = MIN_X - 1; | ||
|  | 		max_y = MIN_Y - 1; | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | static struct sigaction winch_handler = { | ||
|  | 	.sa_handler = _winch_handler, | ||
|  | }; | ||
|  | 
 | ||
|  | /*! \brief Handle a SIGQUIT */ | ||
|  | static void _sigint_handler(int sig) | ||
|  | { | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | static struct sigaction sigint_handler = { | ||
|  | 	.sa_handler = _sigint_handler, | ||
|  | }; | ||
|  | 
 | ||
|  | /*! \brief Display help information */ | ||
|  | static void show_help(WINDOW *win) | ||
|  | { | ||
|  | 	int i; | ||
|  | 
 | ||
|  | 	wclear(win); | ||
|  | 	for (i = 0; i < (sizeof(help_info) / sizeof(help_info[0])); i++) { | ||
|  | 		wmove(win, i, max_x / 2 - 15); | ||
|  | 		waddstr(win, (char *) help_info[i]); | ||
|  | 	} | ||
|  | 	wrefresh(win); | ||
|  | 	getch(); /* display the help until the user hits a key */ | ||
|  | } | ||
|  | 
 | ||
|  | static int really_quit(WINDOW *win) | ||
|  | { | ||
|  | 	int c; | ||
|  | 	wclear(win); | ||
|  | 	wmove(win, 2, max_x / 2 - 15); | ||
|  | 	waddstr(win, "ARE YOU SURE?"); | ||
|  |         wmove(win, 3, max_x / 2 - 12); | ||
|  | 	waddstr(win, "--- It appears you have made some changes, and"); | ||
|  | 	wmove(win, 4, max_x / 2 - 12); | ||
|  | 	waddstr(win, "you have opted to Quit without saving these changes!"); | ||
|  | 	wmove(win, 6, max_x / 2 - 12); | ||
|  | 	waddstr(win, "  Please Enter Y to exit without saving;"); | ||
|  | 	wmove(win, 7, max_x / 2 - 12); | ||
|  | 	waddstr(win, "  Enter N to cancel your decision to quit,"); | ||
|  | 	wmove(win, 8, max_x / 2 - 12); | ||
|  | 	waddstr(win, "     and keep working in menuselect, or"); | ||
|  | 	wmove(win, 9, max_x / 2 - 12); | ||
|  | 	waddstr(win, "  Enter S to save your changes, and exit"); | ||
|  | 	wmove(win, 10, max_x / 2 - 12); | ||
|  | 	wrefresh(win); | ||
|  | 	while ((c=getch())) { | ||
|  | 		if (c == 'Y' || c == 'y') { | ||
|  | 			c = 'q'; | ||
|  | 			break; | ||
|  | 		} | ||
|  | 		if (c == 'S' || c == 's') { | ||
|  | 			c = 'S'; | ||
|  | 			break; | ||
|  | 		} | ||
|  | 		if (c == 'N' || c == 'n') { | ||
|  | 			c = '%'; | ||
|  | 			break; | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return c; | ||
|  | } | ||
|  | 
 | ||
|  | static void draw_main_menu(WINDOW *menu, int curopt) | ||
|  | { | ||
|  | 	struct category *cat; | ||
|  | 	char buf[64]; | ||
|  | 	int i = 0; | ||
|  | 
 | ||
|  | 	wclear(menu); | ||
|  | 
 | ||
|  | 	AST_LIST_TRAVERSE(&categories, cat, list) { | ||
|  | 		wmove(menu, i++, max_x / 2 - 10); | ||
|  | 		snprintf(buf, sizeof(buf), " %s", strlen_zero(cat->displayname) ? cat->name : cat->displayname); | ||
|  | 		waddstr(menu, buf); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	wmove(menu, curopt, (max_x / 2) - 15); | ||
|  | 	waddstr(menu, "--->"); | ||
|  | 	wmove(menu, 0, 0); | ||
|  | 
 | ||
|  | 	wrefresh(menu); | ||
|  | } | ||
|  | 
 | ||
|  | static void display_mem_info(WINDOW *menu, struct member *mem, int start, int end) | ||
|  | { | ||
|  | 	char buf[64]; | ||
|  | 	struct reference *dep; | ||
|  | 	struct reference *con; | ||
|  | 	struct reference *use; | ||
|  | 
 | ||
|  | 	wmove(menu, end - start + 2, max_x / 2 - 16); | ||
|  | 	wclrtoeol(menu); | ||
|  | 	wmove(menu, end - start + 3, max_x / 2 - 16); | ||
|  | 	wclrtoeol(menu); | ||
|  | 	wmove(menu, end - start + 4, max_x / 2 - 16); | ||
|  | 	wclrtoeol(menu); | ||
|  | 	wmove(menu, end - start + 5, max_x / 2 - 16); | ||
|  | 	wclrtoeol(menu); | ||
|  | 	wmove(menu, end - start + 6, max_x / 2 - 16); | ||
|  | 	wclrtoeol(menu); | ||
|  | 
 | ||
|  | 	if (mem->displayname) { | ||
|  | 		wmove(menu, end - start + 2, max_x / 2 - 16); | ||
|  | 		waddstr(menu, (char *) mem->displayname); | ||
|  | 	} | ||
|  | 	if (!AST_LIST_EMPTY(&mem->deps)) { | ||
|  | 		wmove(menu, end - start + 3, max_x / 2 - 16); | ||
|  | 		strcpy(buf, "Depends on: "); | ||
|  | 		AST_LIST_TRAVERSE(&mem->deps, dep, list) { | ||
|  | 			strncat(buf, dep->displayname, sizeof(buf) - strlen(buf) - 1); | ||
|  | 			strncat(buf, dep->member ? "(M)" : "(E)", sizeof(buf) - strlen(buf) - 1); | ||
|  | 			if (AST_LIST_NEXT(dep, list)) | ||
|  | 				strncat(buf, ", ", sizeof(buf) - strlen(buf) - 1); | ||
|  | 		} | ||
|  | 		waddstr(menu, buf); | ||
|  | 	} | ||
|  | 	if (!AST_LIST_EMPTY(&mem->uses)) { | ||
|  | 		wmove(menu, end - start + 4, max_x / 2 - 16); | ||
|  | 		strcpy(buf, "Can use: "); | ||
|  | 		AST_LIST_TRAVERSE(&mem->uses, use, list) { | ||
|  | 			strncat(buf, use->displayname, sizeof(buf) - strlen(buf) - 1); | ||
|  | 			strncat(buf, use->member ? "(M)" : "(E)", sizeof(buf) - strlen(buf) - 1); | ||
|  | 			if (AST_LIST_NEXT(use, list)) | ||
|  | 				strncat(buf, ", ", sizeof(buf) - strlen(buf) - 1); | ||
|  | 		} | ||
|  | 		waddstr(menu, buf); | ||
|  | 	} | ||
|  | 	if (!AST_LIST_EMPTY(&mem->conflicts)) { | ||
|  | 		wmove(menu, end - start + 5, max_x / 2 - 16); | ||
|  | 		strcpy(buf, "Conflicts with: "); | ||
|  | 		AST_LIST_TRAVERSE(&mem->conflicts, con, list) { | ||
|  | 			strncat(buf, con->displayname, sizeof(buf) - strlen(buf) - 1); | ||
|  | 			strncat(buf, con->member ? "(M)" : "(E)", sizeof(buf) - strlen(buf) - 1); | ||
|  | 			if (AST_LIST_NEXT(con, list)) | ||
|  | 				strncat(buf, ", ", sizeof(buf) - strlen(buf) - 1); | ||
|  | 		} | ||
|  | 		waddstr(menu, buf); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (!mem->is_separator) { /* Separators lack support levels */ | ||
|  | 		{ /* support level */ | ||
|  | 			wmove(menu, end - start + 6, max_x / 2 - 16); | ||
|  | 			snprintf(buf, sizeof(buf), "Support Level: %s", mem->support_level); | ||
|  | 			if (mem->replacement && *mem->replacement) { | ||
|  | 				char buf2[64]; | ||
|  | 				snprintf(buf2, sizeof(buf2), ", Replaced by: %s", mem->replacement); | ||
|  | 				strncat(buf, buf2, sizeof(buf) - strlen(buf) - 1); | ||
|  | 			} | ||
|  | 			waddstr(menu, buf); | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | static void draw_category_menu(WINDOW *menu, struct category *cat, int start, int end, int curopt, int changed, int flags) | ||
|  | { | ||
|  | 	int i = 0; | ||
|  | 	int j = 0; | ||
|  | 	struct member *mem; | ||
|  | 	char buf[64]; | ||
|  | 
 | ||
|  | 	if (!changed) { | ||
|  | 		/* If all we have to do is move the cursor, 
 | ||
|  | 		 * then don't clear the screen and start over */ | ||
|  | 		AST_LIST_TRAVERSE(&cat->members, mem, list) { | ||
|  | 			i++; | ||
|  | 			if (curopt + 1 == i) { | ||
|  | 				display_mem_info(menu, mem, start, end); | ||
|  | 				break; | ||
|  | 			} | ||
|  | 		} | ||
|  | 		wmove(menu, curopt - start, max_x / 2 - 9); | ||
|  | 		wrefresh(menu); | ||
|  | 		return; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	wclear(menu); | ||
|  | 
 | ||
|  | 	i = 0; | ||
|  | 	AST_LIST_TRAVERSE(&cat->members, mem, list) { | ||
|  | 		if (i < start) { | ||
|  | 			i++; | ||
|  | 			continue; | ||
|  | 		} | ||
|  | 		wmove(menu, j++, max_x / 2 - 10); | ||
|  | 		i++; | ||
|  | 		if ((mem->depsfailed == HARD_FAILURE) || (mem->conflictsfailed == HARD_FAILURE)) { | ||
|  | 			snprintf(buf, sizeof(buf), "XXX %s", mem->name); | ||
|  | 		} else if (mem->is_separator) { | ||
|  | 			snprintf(buf, sizeof(buf), "    --- %s ---", mem->name); | ||
|  | 		} else if (mem->depsfailed == SOFT_FAILURE) { | ||
|  | 			snprintf(buf, sizeof(buf), "<%s> %s", mem->enabled ? "*" : " ", mem->name); | ||
|  | 		} else if (mem->conflictsfailed == SOFT_FAILURE) { | ||
|  | 			snprintf(buf, sizeof(buf), "(%s) %s", mem->enabled ? "*" : " ", mem->name); | ||
|  | 		} else { | ||
|  | 			snprintf(buf, sizeof(buf), "[%s] %s", mem->enabled ? "*" : " ", mem->name); | ||
|  | 		} | ||
|  | 		waddstr(menu, buf); | ||
|  | 		 | ||
|  | 		if (curopt + 1 == i) | ||
|  | 			display_mem_info(menu, mem, start, end); | ||
|  | 
 | ||
|  | 		if (i == end - (flags & SCROLL_DOWN ? 1 : 0)) | ||
|  | 			break; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (flags & SCROLL_DOWN) { | ||
|  | 		wmove(menu, j, max_x / 2 - sizeof(SCROLL_DOWN_INDICATOR) / 2); | ||
|  | 		waddstr(menu, SCROLL_DOWN_INDICATOR); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	wmove(menu, curopt - start, max_x / 2 - 9); | ||
|  | 	wrefresh(menu); | ||
|  | } | ||
|  | 
 | ||
|  | static void play_space(void); | ||
|  | 
 | ||
|  | static int move_up(int *current, int itemcount, int delta, int *start, int *end, int scroll) | ||
|  | { | ||
|  | 	if (*current > 0) { | ||
|  | 		*current = MAX(*current - delta, 0); | ||
|  | 		if (*current < *start) { | ||
|  | 			int diff = *start - MAX(*start - delta, 0); | ||
|  | 			*start  -= diff; | ||
|  | 			*end    -= diff; | ||
|  | 			return 1; | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static int move_down(int *current, int itemcount, int delta, int *start, int *end, int scroll) | ||
|  | { | ||
|  | 	if (*current < itemcount) { | ||
|  | 		*current = MIN(*current + delta, itemcount); | ||
|  | 		if (*current > *end - 1 - (scroll & SCROLL_DOWN ? 1 : 0)) { | ||
|  | 			int diff = MIN(*end + delta - 1, itemcount) - *end + 1; | ||
|  | 			*start  += diff; | ||
|  | 			*end    += diff; | ||
|  | 			return 1; | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static int run_category_menu(WINDOW *menu, int cat_num) | ||
|  | { | ||
|  | 	struct category *cat; | ||
|  | 	int i = 0; | ||
|  | 	int start = 0; | ||
|  | 	int end = max_y - TITLE_HEIGHT - 8; | ||
|  | 	int c; | ||
|  | 	int curopt = 0; | ||
|  | 	int maxopt; | ||
|  | 	int changed = 1; | ||
|  | 	int scroll = SCROLL_NONE; | ||
|  | 
 | ||
|  | 	AST_LIST_TRAVERSE(&categories, cat, list) { | ||
|  | 		if (i++ == cat_num) | ||
|  | 			break; | ||
|  | 	} | ||
|  | 	if (!cat) | ||
|  | 		return -1; | ||
|  | 
 | ||
|  | 	maxopt = count_members(cat) - 1; | ||
|  | 
 | ||
|  | 	if (maxopt > end) { | ||
|  | 		scroll = SCROLL_DOWN; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	draw_category_menu(menu, cat, start, end, curopt, changed, scroll); | ||
|  | 
 | ||
|  | 	while ((c = getch())) { | ||
|  | 		changed = 0; | ||
|  | 		switch (c) { | ||
|  | 		case KEY_UP: | ||
|  | 			changed = move_up(&curopt, maxopt, 1, &start, &end, scroll); | ||
|  | 			break; | ||
|  | 		case KEY_DOWN: | ||
|  | 			changed = move_down(&curopt, maxopt, 1, &start, &end, scroll); | ||
|  | 			break; | ||
|  | 		case KEY_PPAGE: | ||
|  | 			changed = move_up( | ||
|  | 				&curopt, | ||
|  | 				maxopt, | ||
|  | 				MIN(PAGE_OFFSET, max_y - TITLE_HEIGHT - 6 - (scroll & SCROLL_DOWN ? 1 : 0)), | ||
|  | 				&start, | ||
|  | 				&end, | ||
|  | 				scroll); | ||
|  | 			break; | ||
|  | 		case KEY_NPAGE: | ||
|  | 			changed = move_down( | ||
|  | 				&curopt, | ||
|  | 				maxopt, | ||
|  | 				MIN(PAGE_OFFSET, max_y - TITLE_HEIGHT - 6 - (scroll & SCROLL_DOWN ? 1 : 0)), | ||
|  | 				&start, | ||
|  | 				&end, | ||
|  | 				scroll); | ||
|  | 			break; | ||
|  | 		case KEY_HOME: | ||
|  | 			changed = move_up(&curopt, maxopt, curopt, &start, &end, scroll); | ||
|  | 			break; | ||
|  | 		case KEY_END: | ||
|  | 			changed = move_down(&curopt, maxopt, maxopt - curopt, &start, &end, scroll); | ||
|  | 			break; | ||
|  | 		case KEY_LEFT: | ||
|  | 		case 27:	/* Esc key */ | ||
|  | 			return 0; | ||
|  | 		case KEY_RIGHT: | ||
|  | 		case KEY_ENTER: | ||
|  | 		case '\n': | ||
|  | 		case ' ': | ||
|  | 			toggle_enabled_index(cat, curopt); | ||
|  | 			changed = 1; | ||
|  | 			break; | ||
|  | 		case 'y': | ||
|  | 		case 'Y': | ||
|  | 			set_enabled(cat, curopt); | ||
|  | 			changed = 1; | ||
|  | 			break; | ||
|  | 		case 'n': | ||
|  | 		case 'N': | ||
|  | 			clear_enabled(cat, curopt); | ||
|  | 			changed = 1; | ||
|  | 			break; | ||
|  | 		case 'h': | ||
|  | 		case 'H': | ||
|  | 			show_help(menu); | ||
|  | 			changed = 1; | ||
|  | 			break; | ||
|  | 		case KEY_F(7): | ||
|  | 			set_all(cat, 0); | ||
|  | 			changed = 1; | ||
|  | 			break; | ||
|  | 		case KEY_F(8): | ||
|  | 			set_all(cat, 1); | ||
|  | 			changed = 1; | ||
|  | 		default: | ||
|  | 			break;	 | ||
|  | 		} | ||
|  | 		if (c == 'x' || c == 'X' || c == 'Q' || c == 'q') | ||
|  | 			break; | ||
|  | 
 | ||
|  | 		if (end <= maxopt) { | ||
|  | 			scroll |= SCROLL_DOWN; | ||
|  | 		} else { | ||
|  | 			scroll &= ~SCROLL_DOWN; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		draw_category_menu(menu, cat, start, end, curopt, changed, scroll); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	wrefresh(menu); | ||
|  | 
 | ||
|  | 	return c; | ||
|  | } | ||
|  | 
 | ||
|  | static void draw_title_window(WINDOW *title) | ||
|  | { | ||
|  | 	char titlebar[strlen(menu_name) + 9]; | ||
|  | 
 | ||
|  | 	memset(titlebar, '*', sizeof(titlebar) - 1); | ||
|  | 	titlebar[sizeof(titlebar) - 1] = '\0'; | ||
|  | 	wclear(title); | ||
|  | 	wmove(title, 1, (max_x / 2) - (strlen(titlebar) / 2)); | ||
|  | 	waddstr(title, titlebar); | ||
|  | 	wmove(title, 2, (max_x / 2) - (strlen(menu_name) / 2)); | ||
|  | 	waddstr(title, (char *) menu_name); | ||
|  | 	wmove(title, 3, (max_x / 2) - (strlen(titlebar) / 2)); | ||
|  | 	waddstr(title, titlebar); | ||
|  | 	wmove(title, 5, (max_x / 2) - (strlen(MENU_HELP) / 2)); | ||
|  | 	waddstr(title, MENU_HELP); | ||
|  | 	wrefresh(title); | ||
|  | } | ||
|  | 
 | ||
|  | int run_menu(void) | ||
|  | { | ||
|  | 	WINDOW *title; | ||
|  | 	WINDOW *menu; | ||
|  | 	int maxopt; | ||
|  | 	int curopt = 0; | ||
|  | 	int c; | ||
|  | 	int res = 0; | ||
|  | 
 | ||
|  | 	setenv("ESCDELAY", "0", 1); /* So that ESC is processed immediately */ | ||
|  | 
 | ||
|  | 	initscr(); | ||
|  | 	getmaxyx(stdscr, max_y, max_x); | ||
|  | 	sigaction(SIGWINCH, &winch_handler, NULL); /* handle window resizing in xterm */ | ||
|  | 	sigaction(SIGINT, &sigint_handler, NULL); /* handle window resizing in xterm */ | ||
|  | 
 | ||
|  | 	if (max_x < MIN_X || max_y < MIN_Y) { | ||
|  | 		fprintf(stderr, "Terminal must be at least %d x %d.\n", MIN_X, MIN_Y); | ||
|  | 		endwin(); | ||
|  | 		return -1; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	cbreak(); /* don't buffer input until the enter key is pressed */ | ||
|  | 	noecho(); /* don't echo user input to the screen */ | ||
|  | 	keypad(stdscr, TRUE); /* allow the use of arrow keys */ | ||
|  | 	clear(); | ||
|  | 	refresh(); | ||
|  | 
 | ||
|  | 	maxopt = count_categories() - 1; | ||
|  | 	 | ||
|  | 	/* We have two windows - the title window at the top, and the menu window gets the rest */ | ||
|  | 	title = newwin(TITLE_HEIGHT, max_x, 0, 0); | ||
|  | 	menu = newwin(max_y - TITLE_HEIGHT, max_x, TITLE_HEIGHT, 0); | ||
|  | 	draw_title_window(title);	 | ||
|  | 	draw_main_menu(menu, curopt); | ||
|  | 	 | ||
|  | 	while ((c = getch())) { | ||
|  | 		switch (c) { | ||
|  | 		case KEY_UP: | ||
|  | 			if (curopt > 0) | ||
|  | 				curopt--; | ||
|  | 			break; | ||
|  | 		case KEY_DOWN: | ||
|  | 			if (curopt < maxopt) | ||
|  | 				curopt++; | ||
|  | 			break; | ||
|  | 		case KEY_HOME: | ||
|  | 			curopt = 0; | ||
|  | 			break; | ||
|  | 		case KEY_END: | ||
|  | 			curopt = maxopt; | ||
|  | 			break; | ||
|  | 		case KEY_RIGHT: | ||
|  | 		case KEY_ENTER: | ||
|  | 		case '\n': | ||
|  | 		case ' ': | ||
|  | 			c = run_category_menu(menu, curopt); | ||
|  | 			break; | ||
|  | 		case 'h': | ||
|  | 		case 'H': | ||
|  | 			show_help(menu); | ||
|  | 			break; | ||
|  | 		case 'i': | ||
|  | 		case 'I': | ||
|  | 			play_space(); | ||
|  | 			draw_title_window(title); | ||
|  | 		default: | ||
|  | 			break;	 | ||
|  | 		} | ||
|  | 		if (c == 'q' || c == 'Q' || c == 27 || c == 3) { | ||
|  | 			if (changes_made) { | ||
|  | 				c = really_quit(menu); | ||
|  | 				if (c == 'q') { | ||
|  | 					res = -1; | ||
|  | 					break; | ||
|  | 				} | ||
|  | 			} else { | ||
|  | 				res = -1; | ||
|  | 				break; | ||
|  | 			} | ||
|  | 		} | ||
|  | 		if (c == 'x' || c == 'X' || c == 's' || c == 'S') | ||
|  | 			break;	 | ||
|  | 		draw_main_menu(menu, curopt); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	endwin(); | ||
|  | 
 | ||
|  | 	return res; | ||
|  | } | ||
|  | 
 | ||
|  | enum blip_type { | ||
|  | 	BLIP_TANK = 0, | ||
|  | 	BLIP_SHOT, | ||
|  | 	BLIP_BOMB, | ||
|  | 	BLIP_ALIEN, | ||
|  | 	BLIP_BARRIER, | ||
|  | 	BLIP_UFO | ||
|  | }; | ||
|  | 
 | ||
|  | struct blip { | ||
|  | 	enum blip_type type; | ||
|  | 	int x; | ||
|  | 	int y; | ||
|  | 	int ox; | ||
|  | 	int oy; | ||
|  | 	int goingleft; | ||
|  | 	int health; | ||
|  | 	AST_LIST_ENTRY(blip) entry; | ||
|  | }; | ||
|  | 
 | ||
|  | static AST_LIST_HEAD_NOLOCK(, blip) blips; | ||
|  | 
 | ||
|  | static int respawn = 0; | ||
|  | static int score = 0; | ||
|  | static int num_aliens = 0; | ||
|  | static int alien_sleeptime = 0; | ||
|  | struct blip *ufo = NULL; | ||
|  | struct blip *tank = NULL; | ||
|  | 
 | ||
|  | /*! Probability of a bomb, out of 100 */ | ||
|  | #define BOMB_PROB   1
 | ||
|  | 
 | ||
|  | static int add_barrier(int x, int y) | ||
|  | { | ||
|  | 	struct blip *cur = NULL; | ||
|  | 
 | ||
|  | 	cur = calloc(1,sizeof(struct blip)); | ||
|  | 	if(!cur) { | ||
|  | 		return -1; | ||
|  | 	} | ||
|  | 	cur->type=BLIP_BARRIER; | ||
|  | 	cur->x = x; | ||
|  | 	cur->y=max_y - y; | ||
|  | 	cur->health = 1; | ||
|  | 	AST_LIST_INSERT_HEAD(&blips, cur,entry); | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static int init_blips(void) | ||
|  | { | ||
|  | 	int i, j; | ||
|  | 	struct blip *cur; | ||
|  | 	int offset = 4; | ||
|  | 
 | ||
|  | 	srandom(time(NULL) + getpid()); | ||
|  | 
 | ||
|  | 	/* make tank */ | ||
|  | 	cur = calloc(1, sizeof(struct blip)); | ||
|  | 	if (!cur) | ||
|  | 		return -1; | ||
|  | 	cur->type = BLIP_TANK; | ||
|  | 	cur->x = max_x / 2; | ||
|  | 	cur->y = max_y - 1; | ||
|  | 	AST_LIST_INSERT_HEAD(&blips, cur, entry); | ||
|  | 	tank = cur; | ||
|  | 
 | ||
|  | 	/* 3 rows of 10 aliens */ | ||
|  | 	num_aliens = 0; | ||
|  | 	for (i = 0; i < 3; i++) { | ||
|  | 		for (j = 0; j < 10; j++) { | ||
|  | 			cur = calloc(1, sizeof(struct blip)); | ||
|  | 			if (!cur) | ||
|  | 				return -1; | ||
|  | 			cur->type = BLIP_ALIEN; | ||
|  | 			cur->x = (j * 2) + 1; | ||
|  | 			cur->y = (i * 2) + 2; | ||
|  | 			AST_LIST_INSERT_HEAD(&blips, cur, entry); | ||
|  | 			num_aliens++; | ||
|  | 		} | ||
|  | 	} | ||
|  | 	for(i=0; i < 4; i++) { | ||
|  | 		if (i > 0) | ||
|  | 			offset += 5 + ((max_x) -28) / 3; | ||
|  | 		add_barrier(offset + 1, 6); | ||
|  | 		add_barrier(offset + 2, 6); | ||
|  | 		add_barrier(offset + 3, 6); | ||
|  | 
 | ||
|  | 		add_barrier(offset, 5); | ||
|  | 		add_barrier(offset + 1, 5); | ||
|  | 		add_barrier(offset + 2, 5); | ||
|  | 		add_barrier(offset + 3, 5); | ||
|  | 		add_barrier(offset + 4, 5); | ||
|  | 
 | ||
|  | 		add_barrier(offset, 4); | ||
|  | 		add_barrier(offset + 1, 4); | ||
|  | 		add_barrier(offset + 3, 4); | ||
|  | 		add_barrier(offset + 4, 4); | ||
|  | 	} | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static inline chtype type2chtype(enum blip_type type) | ||
|  | { | ||
|  | 	switch (type) { | ||
|  | 	case BLIP_TANK: | ||
|  | 		return 'A'; | ||
|  | 	case BLIP_ALIEN: | ||
|  | 		return 'X'; | ||
|  | 	case BLIP_SHOT: | ||
|  | 		return '|'; | ||
|  | 	case BLIP_BOMB: | ||
|  | 		return 'o'; | ||
|  | 	case BLIP_BARRIER: | ||
|  | 		return '*'; | ||
|  | 	case BLIP_UFO: | ||
|  | 		return '@'; | ||
|  | 	default: | ||
|  | 		break; | ||
|  | 	} | ||
|  | 	return '?'; | ||
|  | } | ||
|  | 
 | ||
|  | static int repaint_screen(void) | ||
|  | { | ||
|  | 	struct blip *cur; | ||
|  | 
 | ||
|  | 	wmove(stdscr, 0, 0); | ||
|  | 	wprintw(stdscr, "Score: %d", score); | ||
|  | 
 | ||
|  | 	AST_LIST_TRAVERSE(&blips, cur, entry) { | ||
|  | 		if (cur->x != cur->ox || cur->y != cur->oy) { | ||
|  | 			wmove(stdscr, cur->oy, cur->ox); | ||
|  | 			waddch(stdscr, ' '); | ||
|  | 			wmove(stdscr, cur->y, cur->x); | ||
|  | 			waddch(stdscr, type2chtype(cur->type));	 | ||
|  | 			cur->ox = cur->x; | ||
|  | 			cur->oy = cur->y; | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	wmove(stdscr, 0, max_x - 1); | ||
|  | 
 | ||
|  | 	wrefresh(stdscr); | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static int tank_move_left(void) | ||
|  | { | ||
|  | 	if (tank->x > 0) | ||
|  | 		tank->x--; | ||
|  | 	 | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static int tank_move_right(void) | ||
|  | { | ||
|  | 	if (tank->x < (max_x - 1)) | ||
|  | 		tank->x++; | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static int count_shots(void) | ||
|  | { | ||
|  | 	struct blip *cur; | ||
|  | 	int count = 0; | ||
|  | 
 | ||
|  | 	AST_LIST_TRAVERSE(&blips, cur, entry) { | ||
|  | 		if (cur->type == BLIP_SHOT) | ||
|  | 			count++; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return count; | ||
|  | } | ||
|  | 
 | ||
|  | static int tank_shoot(void) | ||
|  | { | ||
|  | 	struct blip *shot; | ||
|  | 
 | ||
|  | 	if (count_shots() == 3) | ||
|  | 		return 0; | ||
|  | 
 | ||
|  | 	score--; | ||
|  | 
 | ||
|  | 	shot = calloc(1, sizeof(struct blip)); | ||
|  | 	if (!shot) | ||
|  | 		return -1; | ||
|  | 	shot->type = BLIP_SHOT; | ||
|  | 	shot->x = tank->x; | ||
|  | 	shot->y = max_y - 2; | ||
|  | 	AST_LIST_INSERT_HEAD(&blips, shot, entry); | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static int remove_blip(struct blip *blip) | ||
|  | { | ||
|  | 	if (!blip) { | ||
|  | 		return -1; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	AST_LIST_REMOVE(&blips, blip, entry); | ||
|  | 
 | ||
|  | 	if (blip->type == BLIP_ALIEN) { | ||
|  | 		num_aliens--; | ||
|  | 	} | ||
|  | 	wmove(stdscr, blip->oy, blip->ox); | ||
|  | 	waddch(stdscr, ' '); | ||
|  | 	free(blip); | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static int move_aliens(void) | ||
|  | { | ||
|  | 	struct blip *cur; | ||
|  | 	struct blip *current_barrier; | ||
|  | 
 | ||
|  | 	AST_LIST_TRAVERSE(&blips, cur, entry) { | ||
|  | 		if (cur->type != BLIP_ALIEN) { | ||
|  | 			/* do nothing if it's not an alien */ | ||
|  | 			continue; | ||
|  | 		} | ||
|  | 		if (cur->goingleft && (cur->x == 0)) { | ||
|  | 			cur->y++; | ||
|  | 			cur->goingleft = 0; | ||
|  | 		} else if (!cur->goingleft && cur->x == (max_x - 1)) { | ||
|  | 			cur->y++; | ||
|  | 			cur->goingleft = 1; | ||
|  | 		} else if (cur->goingleft) { | ||
|  | 			cur->x--; | ||
|  | 		} else { | ||
|  | 			cur->x++; | ||
|  | 		} | ||
|  | 		/* Alien into the tank == game over */ | ||
|  | 		if (cur->x == tank->x && cur->y == tank->y) | ||
|  | 			return 1; | ||
|  | 		AST_LIST_TRAVERSE(&blips, current_barrier, entry){ | ||
|  | 			if(current_barrier->type!=BLIP_BARRIER) | ||
|  | 				continue; | ||
|  | 			if(cur->y == current_barrier->y && cur->x == current_barrier -> x) | ||
|  | 				remove_blip(current_barrier); | ||
|  | 		} | ||
|  | 		if (random() % 100 < BOMB_PROB && cur->y != max_y) { | ||
|  | 			struct blip *bomb = calloc(1, sizeof(struct blip)); | ||
|  | 			if (!bomb) | ||
|  | 				continue; | ||
|  | 			bomb->type = BLIP_BOMB; | ||
|  | 			bomb->x = cur->x; | ||
|  | 			bomb->y = cur->y + 1; | ||
|  | 			AST_LIST_INSERT_HEAD(&blips, bomb, entry); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static int move_bombs(void) | ||
|  | { | ||
|  | 	struct blip *cur; | ||
|  | 	struct blip *current_barrier; | ||
|  | 
 | ||
|  | 	AST_LIST_TRAVERSE(&blips, cur, entry) { | ||
|  | 		int mark = 0; | ||
|  | 		if (cur->type != BLIP_BOMB) | ||
|  | 			continue; | ||
|  | 		cur->y++; | ||
|  | 		if (cur->x == tank->x && cur->y == tank->y) { | ||
|  | 			return 1; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		AST_LIST_TRAVERSE(&blips, current_barrier, entry) { | ||
|  | 			if (current_barrier->type != BLIP_BARRIER) | ||
|  | 				continue; | ||
|  | 			if (cur->x == current_barrier->x && cur->y == current_barrier->y) { | ||
|  | 				mark = 1; | ||
|  | 				current_barrier->health--; | ||
|  | 				if (current_barrier->health == 0) | ||
|  | 					remove_blip(current_barrier); | ||
|  | 			} | ||
|  | 		} | ||
|  | 		if (mark){ | ||
|  | 			remove_blip(cur);} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static void move_shots(void) | ||
|  | { | ||
|  | 	struct blip *cur; | ||
|  | 
 | ||
|  | 	AST_LIST_TRAVERSE(&blips, cur, entry) { | ||
|  | 		if (cur->type != BLIP_SHOT) | ||
|  | 			continue; | ||
|  | 		cur->y--; | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int ufo_action() | ||
|  | { | ||
|  | 	struct blip *cur; | ||
|  | 
 | ||
|  | 	AST_LIST_TRAVERSE(&blips, cur, entry) { | ||
|  | 		if (cur->type != BLIP_UFO) { | ||
|  | 			continue; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		cur->x--; | ||
|  | 
 | ||
|  | 		if (cur->x < 0) { | ||
|  | 			remove_blip(cur); | ||
|  | 			respawn += 1; | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (respawn == 7) { | ||
|  | 		respawn = 0; | ||
|  | 		/* make new mothership*/ | ||
|  | 		cur = calloc(1, sizeof(struct blip)); | ||
|  | 		if(!cur) | ||
|  | 			return -1; | ||
|  | 		cur->type = BLIP_UFO; | ||
|  | 		cur->x = max_x - 1; | ||
|  | 		cur->y = 1; | ||
|  | 		AST_LIST_INSERT_HEAD(&blips, cur, entry); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static void game_over(int win) | ||
|  | { | ||
|  | 	clear(); | ||
|  | 
 | ||
|  | 	wmove(stdscr, max_y / 2, max_x / 2 - 10); | ||
|  | 	wprintw(stdscr, "Game over!  You %s!", win ? "win" : "lose"); | ||
|  | 
 | ||
|  | 	wmove(stdscr, 0, max_x - 1); | ||
|  | 
 | ||
|  | 	wrefresh(stdscr); | ||
|  | 
 | ||
|  | 	sleep(1); | ||
|  | 
 | ||
|  | 	while (getch() != ' '); | ||
|  | 
 | ||
|  | 	return; | ||
|  | } | ||
|  | 
 | ||
|  | static int check_shot(struct blip *shot) | ||
|  | { | ||
|  | 	struct blip *cur; | ||
|  | 
 | ||
|  | 	AST_LIST_TRAVERSE(&blips, cur, entry) { | ||
|  | 		if ((cur->type == BLIP_ALIEN || cur->type == BLIP_UFO) && cur->x == shot->x && cur->y == shot->y){ | ||
|  | 			if (cur->type == BLIP_UFO) { | ||
|  | 				score += 80; | ||
|  | 			} | ||
|  | 			score += 20; | ||
|  | 			remove_blip(cur); | ||
|  | 			remove_blip(shot); | ||
|  | 			respawn += 1; | ||
|  | 			if (!num_aliens) { | ||
|  | 				if(alien_sleeptime < 101) { | ||
|  | 					game_over(1); | ||
|  | 					return 1; | ||
|  | 				} else { | ||
|  | 					alien_sleeptime = alien_sleeptime - 100; | ||
|  | 					return 1; | ||
|  | 				} | ||
|  | 			} | ||
|  | 			break; | ||
|  | 		} | ||
|  | 		if (cur->type == BLIP_BARRIER) { | ||
|  | 			if (shot->x == cur->x && shot->y == cur->y) { | ||
|  | 				remove_blip(cur); | ||
|  | 				remove_blip(shot); | ||
|  | 				break; | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static int check_placement(void) | ||
|  | { | ||
|  | 	struct blip *cur; | ||
|  | 
 | ||
|  | 	AST_LIST_TRAVERSE_SAFE_BEGIN(&blips, cur, entry) { | ||
|  | 		if (cur->y <= 0 || cur->y >= max_y) { | ||
|  | 			AST_LIST_REMOVE_CURRENT(&blips, entry); | ||
|  | 			remove_blip(cur); | ||
|  | 		} else if (cur->type == BLIP_SHOT && check_shot(cur)) | ||
|  | 			return 1; | ||
|  | 	} | ||
|  | 	AST_LIST_TRAVERSE_SAFE_END | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static void play_space(void) | ||
|  | { | ||
|  | 	int c; | ||
|  | 	unsigned int jiffies = 1; | ||
|  | 	int quit = 0; | ||
|  | 	struct blip *blip; | ||
|  | 	alien_sleeptime = 1000; | ||
|  | 	score = 0; | ||
|  | 
 | ||
|  | 	while(alien_sleeptime > 100) { | ||
|  | 
 | ||
|  | 		jiffies = 1; | ||
|  | 		clear(); | ||
|  | 		nodelay(stdscr, TRUE); | ||
|  | 		init_blips(); | ||
|  | 		repaint_screen(); | ||
|  | 
 | ||
|  | 		for (;;) { | ||
|  | 			c = getch(); | ||
|  | 			switch (c) { | ||
|  | 			case ' ': | ||
|  | 				tank_shoot(); | ||
|  | 				break; | ||
|  | 			case KEY_LEFT: | ||
|  | 				tank_move_left(); | ||
|  | 				break; | ||
|  | 			case KEY_RIGHT: | ||
|  | 				tank_move_right(); | ||
|  | 				break; | ||
|  | 			case 'x': | ||
|  | 			case 'X': | ||
|  | 			case 'q': | ||
|  | 			case 'Q': | ||
|  | 				quit = 1; | ||
|  | 			default: | ||
|  | 				/* ignore unknown input */ | ||
|  | 				break; | ||
|  | 			} | ||
|  | 			if (quit) { | ||
|  | 				alien_sleeptime = 1; | ||
|  | 				break; | ||
|  | 			} | ||
|  | 			if (!(jiffies % 25)) { | ||
|  | 				if (move_aliens() || move_bombs() || ufo_action()) { | ||
|  | 					alien_sleeptime = 1; | ||
|  | 					game_over(0); | ||
|  | 					break; | ||
|  | 				} | ||
|  | 				if (check_placement()) | ||
|  | 					break; | ||
|  | 			} | ||
|  | 			if (!(jiffies % 10)) { | ||
|  | 				move_shots(); | ||
|  | 				if (check_placement()) | ||
|  | 					break; | ||
|  | 			} | ||
|  | 			repaint_screen(); | ||
|  | 			jiffies++; | ||
|  | 			usleep(alien_sleeptime); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		while ((blip = AST_LIST_REMOVE_HEAD(&blips, entry))) | ||
|  | 			free(blip); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	nodelay(stdscr, FALSE); | ||
|  | } |