Example #1
0
int AgiEngine::getKeypress() {
	int k;

	while (_keyQueueStart == _keyQueueEnd)	// block
		pollTimer();

	keyDequeue(k);

	return k;
}
Example #2
0
int AgiEngine::waitKey() {
	int key = 0;

	clearKeyQueue();

	debugC(3, kDebugLevelInput, "waiting...");
	while (!(shouldQuit() || _restartGame || getflag(fRestoreJustRan))) {
		pollTimer();
		key = doPollKeyboard();
		if (key == KEY_ENTER || key == KEY_ESCAPE || key == BUTTON_LEFT)
			break;

		pollTimer();
		updateTimer();

		_gfx->doUpdate();
	}

	// Have to clear it as original did not set this variable, and we do it in doPollKeyboard()
	// Fixes bug #2823759
	_game.keypress = 0;

	return key;
}
Example #3
0
int AgiEngine::waitAnyKey() {
	int key = 0;

	clearKeyQueue();

	debugC(3, kDebugLevelInput, "waiting... (any key)");
	while (!(shouldQuit() || _restartGame)) {
		pollTimer();
		key = doPollKeyboard();
		if (key)
			break;
		_gfx->doUpdate();
	}

	// Have to clear it as original did not set this variable, and we do it in doPollKeyboard()
	_game.keypress = 0;

	return key;
}
Example #4
0
void* CMMDVMController::Entry()
{
	wxLogMessage(wxT("Starting MMDVM Controller thread"));

	// Clock every 5ms-ish
	CTimer pollTimer(200U, 0U, 100U);
	pollTimer.start();

	unsigned char  writeType   = DSMTT_NONE;
	unsigned char  writeLength = 0U;
	unsigned char* writeBuffer = new unsigned char[BUFFER_LENGTH];

	unsigned int space = 0U;

	while (!m_stopped) {
		// Poll the modem status every 100ms
		if (pollTimer.hasExpired()) {
			bool ret = readStatus();
			if (!ret) {
				ret = findModem();
				if (!ret) {
					wxLogError(wxT("Stopping MMDVM Controller thread"));
					return NULL;
				}
			}

			pollTimer.start();
		}

		unsigned int length;
		RESP_TYPE_MMDVM type = getResponse(m_buffer, length);

		switch (type) {
			case RTDVM_TIMEOUT:
				break;

			case RTDVM_ERROR: {
					bool ret = findModem();
					if (!ret) {
						wxLogError(wxT("Stopping MMDVM Controller thread"));
						return NULL;
					}
				}
				break;

			case RTDVM_DSTAR_HEADER: {
					// CUtils::dump(wxT("RT_DSTAR_HEADER"), m_buffer, length);
					wxMutexLocker locker(m_mutex);

					unsigned char data[2U];
					data[0U] = DSMTT_HEADER;
					data[1U] = RADIO_HEADER_LENGTH_BYTES;
					m_rxData.addData(data, 2U);

					m_rxData.addData(m_buffer + 3U, RADIO_HEADER_LENGTH_BYTES);

					m_rx = true;
				}
				break;

			case RTDVM_DSTAR_DATA: {
					// CUtils::dump(wxT("RT_DSTAR_DATA"), m_buffer, length);
					wxMutexLocker locker(m_mutex);

					unsigned char data[2U];
					data[0U] = DSMTT_DATA;
					data[1U] = DV_FRAME_LENGTH_BYTES;
					m_rxData.addData(data, 2U);

					m_rxData.addData(m_buffer + 3U, DV_FRAME_LENGTH_BYTES);

					m_rx = true;
				}
				break;

			case RTDVM_DSTAR_EOT: {
					// wxLogMessage(wxT("RT_DSTAR_EOT"));
					wxMutexLocker locker(m_mutex);

					unsigned char data[2U];
					data[0U] = DSMTT_EOT;
					data[1U] = 0U;
					m_rxData.addData(data, 2U);

					m_rx = false;
				}
				break;

			case RTDVM_DSTAR_LOST: {
					// wxLogMessage(wxT("RT_DSTAR_LOST"));
					wxMutexLocker locker(m_mutex);

					unsigned char data[2U];
					data[0U] = DSMTT_LOST;
					data[1U] = 0U;
					m_rxData.addData(data, 2U);

					m_rx = false;
				}
				break;

			case RTDVM_GET_STATUS: {
					bool dstar = (m_buffer[3U] & 0x01U) == 0x01U;
					if (!dstar) {
						wxLogError(wxT("D-Star not enabled in the MMDVM!!!"));
						wxLogError(wxT("Stopping MMDVM Controller thread"));
						return NULL;
					}

					m_tx  = (m_buffer[5U] & 0x01U) == 0x01U;
					space = m_buffer[6U];
					// CUtils::dump(wxT("GET_STATUS"), m_buffer, length);
					// wxLogMessage(wxT("PTT=%d space=%u"), int(m_tx), space);
				}
				break;

			// These should not be received in this loop, but don't complain if we do
			case RTDVM_GET_VERSION:
			case RTDVM_ACK:
				break;

			case RTDVM_NAK: {
					switch (m_buffer[3U]) {
						case MMDVM_DSTAR_HEADER:
							wxLogWarning(wxT("Received a header NAK from the MMDVM, reason = %u"), m_buffer[4U]);
							break;
						case MMDVM_DSTAR_DATA:
							wxLogWarning(wxT("Received a data NAK from the MMDVM, reason = %u"), m_buffer[4U]);
							break;
						case MMDVM_DSTAR_EOT:
							wxLogWarning(wxT("Received an EOT NAK from the MMDVM, reason = %u"), m_buffer[4U]);
							break;
						default:
							wxLogWarning(wxT("Received a NAK from the MMDVM, command = 0x%02X, reason = %u"), m_buffer[3U], m_buffer[4U]);
							break;
					}
				}
				break;

			default:
				wxLogMessage(wxT("Unknown message, type: %02X"), m_buffer[2U]);
				CUtils::dump(wxT("Buffer dump"), m_buffer, length);
				break;
		}

		if (writeType == DSMTT_NONE && m_txData.hasData()) {
			wxMutexLocker locker(m_mutex);

			m_txData.getData(&writeType, 1U);
			m_txData.getData(&writeLength, 1U);
			m_txData.getData(writeBuffer, writeLength);
		}

		if (space >= 4U && writeType == DSMTT_HEADER) {
			// CUtils::dump(wxT("Write Header"), writeBuffer, writeLength);

			int ret = m_serial.write(writeBuffer, writeLength);
			if (ret != int(writeLength))
				wxLogWarning(wxT("Error when writing the header to the MMDVM"));

			writeType = DSMTT_NONE;
			space -= 4U;
		}

		if (space >= 1U && (writeType == DSMTT_DATA || writeType == DSMTT_EOT)) {
			// CUtils::dump(wxT("Write Data"), writeBuffer, writeLength);

			int ret = m_serial.write(writeBuffer, writeLength);
			if (ret != int(writeLength))
				wxLogWarning(wxT("Error when writing data to the MMDVM"));

			writeType = DSMTT_NONE;
			space--;
		}

		Sleep(5UL);

		pollTimer.clock();
	}

	wxLogMessage(wxT("Stopping MMDVM Controller thread"));

	delete[] writeBuffer;

	m_serial.close();

	return NULL;
}
Example #5
0
void* CDVRPTRV3Controller::Entry()
{
	wxLogMessage(wxT("Starting DV-RPTR3 Modem Controller thread"));

	// Clock every 5ms-ish
	CTimer pollTimer(200U, 0U, 250U);
	pollTimer.start();

	unsigned int space = 0U;

	while (!m_stopped) {
		// Poll the modem status every 250ms
		if (pollTimer.hasExpired()) {
			bool ret = readSpace();
			if (!ret) {
				ret = findModem();
				if (!ret) {
					wxLogMessage(wxT("Stopping DV-RPTR3 Modem Controller thread"));
					return NULL;
				}
			}

			pollTimer.start();
		}

		unsigned int length;
		RESP_TYPE_V3 type = getResponse(m_buffer, length);

		switch (type) {
			case RT3_TIMEOUT:
				break;

			case RT3_ERROR: {
					bool ret = findModem();
					if (!ret) {
						wxLogMessage(wxT("Stopping DV-RPTR3 Modem Controller thread"));
						return NULL;
					}
				}
				break;

			case RT3_HEADER: {
					// CUtils::dump(wxT("RT3_HEADER"), m_buffer, length);
					wxMutexLocker locker(m_mutex);

					unsigned char data[2U];
					data[0U] = DSMTT_HEADER;
					data[1U] = RADIO_HEADER_LENGTH_BYTES;
					m_rxData.addData(data, 2U);

					m_rxData.addData(m_buffer + 9U, RADIO_HEADER_LENGTH_BYTES - 2U);

					// Dummy checksum
					data[0U] = 0xFFU;
					data[1U] = 0xFFU;
					m_rxData.addData(data, 2U);

					data[0U] = DSMTT_DATA;
					data[1U] = DV_FRAME_LENGTH_BYTES;
					m_rxData.addData(data, 2U);

					m_rxData.addData(m_buffer + 51U, DV_FRAME_LENGTH_BYTES);

					m_rx = true;
				}
				break;

			case RT3_DATA: {
					// CUtils::dump(wxT("RT3_DATA"), m_buffer, length);
					wxMutexLocker locker(m_mutex);

					unsigned char data[2U];
					data[0U] = DSMTT_DATA;
					data[1U] = DV_FRAME_LENGTH_BYTES;
					m_rxData.addData(data, 2U);

					m_rxData.addData(m_buffer + 5U, DV_FRAME_LENGTH_BYTES);

					m_rx = true;

					// End of transmission?
					bool end = (m_buffer[19U] & 0x40U) == 0x40U;
					if (end) {
						data[0U] = DSMTT_EOT;
						data[1U] = 0U;
						m_rxData.addData(data, 2U);

						m_rx = false;
					}
				}
				break;

			case RT3_SPACE:
				space = m_buffer[9U];
				// CUtils::dump(wxT("RT3_SPACE"), m_buffer, length);
				break;

			// These should not be received in this loop, but don't complain if we do
			case RT3_QUERY:
			case RT3_CONFIG:
				break;

			default:
				wxLogMessage(wxT("Unknown DV-RPTR3 message, type"));
				CUtils::dump(wxT("Buffer dump"), m_buffer, length);
				break;
		}

		if (space > 0U) {
			if (m_txData.hasData()) {
				unsigned char len = 0U;
				unsigned char data[200U];

				{
					wxMutexLocker locker(m_mutex);

					m_txData.getData(&len, 1U);
					m_txData.getData(data, len);
				}

				// CUtils::dump(wxT("Write"), data, len);

				bool ret = writeModem(data, len);
				if (!ret) {
					bool ret = findModem();
					if (!ret) {
						wxLogMessage(wxT("Stopping DV-RPTR3 Modem Controller thread"));
						return NULL;
					}
				} else {
					space--;
				}
			}
		}

		Sleep(5UL);

		pollTimer.clock();
	}

	wxLogMessage(wxT("Stopping DV-RPTR3 Modem Controller thread"));

	closeModem();

	return NULL;
}
void* CDVRPTRControllerV2::Entry()
{
	wxLogMessage(wxT("Starting DV-RPTR2 Modem Controller thread"));

	// Clock every 5ms-ish
	CTimer pollTimer(200U, 0U, 250U);

	pollTimer.start();

	while (!m_stopped) {
		// Poll the modem status every 250ms
		if (pollTimer.hasExpired()) {
			bool ret = getSpace();
			if (!ret) {
				ret = findModem();
				if (!ret) {
					wxLogMessage(wxT("Stopping DV-RPTR2 Modem Controller thread"));
					return NULL;
				}
			}

			pollTimer.reset();
		}

		unsigned int length;
		RESP_TYPE_V2 type = getResponse(m_buffer, length);

		switch (type) {
			case RT2_TIMEOUT:
				break;

			case RT2_ERROR: {
					bool ret = findModem();
					if (!ret) {
						wxLogMessage(wxT("Stopping DV-RPTR2 Modem Controller thread"));
						return NULL;
					}
				}
				break;

			case RT2_HEADER: {
					// CUtils::dump(wxT("RT2_HEADER"), m_buffer, length);
					m_mutex.Lock();

					unsigned int space = m_rxData.freeSpace();
					if (space < 57U) {
						wxLogMessage(wxT("Out of space in the DV-RPTR RX queue"));
					} else {
						unsigned char data[2U];
						data[0U] = DQT_HEADER;
						data[1U] = RADIO_HEADER_LENGTH_BYTES;
						m_rxData.addData(data, 2U);
						m_rxData.addData(m_buffer + 9U, RADIO_HEADER_LENGTH_BYTES - 2U);

						// Dummy checksum
						data[0U] = 0xFFU;
						data[1U] = 0xFFU;
						m_rxData.addData(data, 2U);

						data[0U] = DQT_DATA;
						data[1U] = DV_FRAME_LENGTH_BYTES;
						m_rxData.addData(data, 2U);
						m_rxData.addData(m_buffer + 51U, DV_FRAME_LENGTH_BYTES);

						m_rx = true;
					}

					m_mutex.Unlock();
				}
				break;

			case RT2_DATA: {
					// CUtils::dump(wxT("RT2_DATA"), m_buffer, length);
					m_mutex.Lock();

					unsigned int space = m_rxData.freeSpace();
					if (space < 16U) {
						wxLogMessage(wxT("Out of space in the DV-RPTR RX queue"));
					} else {
						unsigned char data[2U];
						data[0U] = DQT_DATA;
						data[1U] = DV_FRAME_LENGTH_BYTES;
						m_rxData.addData(data, 2U);
						m_rxData.addData(m_buffer + 51U, DV_FRAME_LENGTH_BYTES);

						m_rx = true;

						// End of transmission?
						if ((m_buffer[50U] & 0x40U) == 0x40) {
							data[0U] = DQT_EOT;
							data[1U] = 0U;
							m_rxData.addData(data, 2U);

							m_rx = false;
						}
					}

					m_mutex.Unlock();
				}
				break;

			case RT2_SPACE:
				m_space = m_buffer[9U];
				// CUtils::dump(wxT("RT2_SPACE"), m_buffer, length);
				break;

			// These should not be received in this loop, but don't complain if we do
			case RT2_QUERY:
			case RT2_CONFIG:
				break;

			default:
				wxLogMessage(wxT("Unknown DV-RPTR2 message, type"));
				CUtils::dump(wxT("Buffer dump"), m_buffer, length);
				break;
		}

		if (m_space > 0U) {
			if (!m_txData.isEmpty()) {
				m_mutex.Lock();

				unsigned char len = 0U;
				m_txData.getData(&len, 1U);

				unsigned char data[200U];
				m_txData.getData(data, len);

				m_mutex.Unlock();

				// CUtils::dump(wxT("Write"), data, len);

				int ret = m_serial.write(data, len);
				if (ret == -1) {
					bool ret = findModem();
					if (!ret) {
						wxLogMessage(wxT("Stopping DV-RPTR2 Modem Controller thread"));
						return NULL;
					}
				} else {
					m_space--;
				}
			}
		}

		Sleep(5UL);

		pollTimer.clock();
	}

	wxLogMessage(wxT("Stopping DV-RPTR2 Modem Controller thread"));

	m_serial.close();

	return NULL;
}
Example #7
0
/**
 * Execute a logic script
 * @param n  Number of the logic resource to execute
 */
int AgiEngine::runLogic(int n) {
	AgiGame *state = &_game;
	uint8 op = 0;
	uint8 p[CMD_BSIZE] = { 0 };
	int num = 0;
	ScriptPos sp;
	//int logic_index = 0;

	state->logic_list[0] = 0;
	state->max_logics = 0;

	debugC(2, kDebugLevelScripts, "=================");
	debugC(2, kDebugLevelScripts, "runLogic(%d)", n);

	sp.script = n;
	sp.curIP = 0;
	_game.execStack.push_back(sp);

	// If logic not loaded, load it
	if (~_game.dirLogic[n].flags & RES_LOADED) {
		debugC(4, kDebugLevelScripts, "logic %d not loaded!", n);
		agiLoadResource(rLOGIC, n);
	}

	_game.lognum = n;
	_game._curLogic = &_game.logics[_game.lognum];

	_game._curLogic->cIP = _game._curLogic->sIP;

	_timerHack = 0;
	while (ip < _game.logics[n].size && !(shouldQuit() || _restartGame)) {
		if (_debug.enabled) {
			if (_debug.steps > 0) {
				if (_debug.logic0 || n) {
					debugConsole(n, lCOMMAND_MODE, NULL);
					_debug.steps--;
				}
			} else {
				_sprites->blitBoth();
				_sprites->commitBoth();
				do {
					mainCycle();
				} while (!_debug.steps && _debug.enabled);
				_sprites->eraseBoth();
			}
		}

		_game.execStack.back().curIP = ip;

		char st[101];
		int sz = MIN(_game.execStack.size(), 100u);
		memset(st, '.', sz);
		st[sz] = 0;

		switch (op = *(code + ip++)) {
		case 0xff:	// if (open/close)
			testIfCode(n);
			break;
		case 0xfe:	// goto
			// +2 covers goto size
			ip += 2 + ((int16)READ_LE_UINT16(code + ip));

			// timer must keep running even in goto loops,
			// but AGI engine can't do that :(
			if (_timerHack > 20) {
				pollTimer();
				updateTimer();
				_timerHack = 0;
			}
			break;
		case 0x00:	// return
			debugC(2, kDebugLevelScripts, "%sreturn() // Logic %d", st, n);
			debugC(2, kDebugLevelScripts, "=================");

//			if (getVersion() < 0x2000) {
//				if (logic_index < state->max_logics) {
//					n = state->logic_list[++logic_index];
//					state->_curLogic = &state->logics[n];
//					state->lognum = n;
//					ip = 2;
//					warning("running logic %d\n", n);
//					break;
//				}
//				_v[13]=0;
//			}

			_game.execStack.pop_back();
			return 1;
		default:
			num = logicNamesCmd[op].argumentsLength();
			memmove(p, code + ip, num);
			memset(p + num, 0, CMD_BSIZE - num);

			debugC(2, kDebugLevelScripts, "%s%s(%d %d %d)", st, logicNamesCmd[op].name, p[0], p[1], p[2]);

			_agiCommands[op](&_game, p);
			ip += num;
		}

//		if ((op == 0x0B || op == 0x3F || op == 0x40) && logic_index < state->max_logics) {
//			n = state->logic_list[++logic_index];
//			state->_curLogic = &state->logics[n];
//			state->lognum = n;
//			ip = 2;
//			warning("running logic %d\n", n);
//		}

		if (_game.exitAllLogics)
			break;
	}

	_game.execStack.pop_back();

	return 0;		// after executing new.room()
}
Example #8
0
bool AgiEngine::predictiveDialog() {
	int key = 0, active = -1, lastactive = 0;
	bool rc = false;
	uint8 x;
	int y;
	int bx[17], by[17];
	Common::String prefix;
	char temp[MAXWORDLEN + 1], repeatcount[MAXWORDLEN];
	AgiBlock tmpwindow;
	bool navigationwithkeys = false;
	bool processkey;

	const char *buttonStr[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" };
	const char *buttons[] = {
		"(1)'-.&",  "(2)abc", "(3)def",
		"(4)ghi",  "(5)jkl", "(6)mno",
		"(7)pqrs", "(8)tuv", "(9)wxyz",
		"(#)next",    "add",
		"<",
		"Cancel",  "OK",
		"Pre", "(0) ", NULL
	};
	const int colors[] = {
		15, 0, 15, 0, 15, 0,
		15, 0, 15, 0, 15, 0,
		15, 0, 15, 0, 15, 0,
		15, 12, 15, 12,
		15, 0,
		15, 0, 15, 0,
		14, 0, 15, 0, 0, 0
	};
	const char *modes[] = { "(*)Pre", "(*)123", "(*)Abc" };

	// FIXME: Move this to a more appropriate place.
	if (!_predictiveDictText) {
		loadDict();
		if (!_predictiveDictText)
			return false;
	}
	_predictiveDictActLine = NULL;
	uint8 numMatchingWords = 0;

	_predictiveDialogRunning = true;
	_system->setFeatureState(OSystem::kFeatureDisableKeyFiltering, true);

	memset(repeatcount, 0, sizeof(repeatcount));

	// show the predictive dialog.
	// if another window is already in display, save its state into tmpwindow
	memset(&tmpwindow, 0, sizeof(tmpwindow));
	tmpwindow.active = false;
	if (_game.window.active)
		memcpy(&tmpwindow, &(_game.window), sizeof(AgiBlock));
	drawWindow(50, 40, 269, 159);
	_gfx->drawRectangle(62, 54, 249, 66, MSG_BOX_TEXT);
	_gfx->flushBlock(62, 54, 249, 66);

	bx[15] = 73; // Zero/space
	by[15] = 120;
	bx[9] = 110; // next
	by[9] = 120;
	bx[10] = 172; // add
	by[10] = 120;
	bx[14] = 200; // Mode
	by[14] = 120;
	bx[11] = 252; // Backspace
	by[11] = 57;
	bx[12] = 180; // Cancel
	by[12] = 140;
	bx[13] = 240; // OK
	by[13] = 140;

	x = 73;
	y = 75;
	for (int i = 0; i < 9; i++) {
		bx[i] = x;
		by[i] = y;
		x += 60;
		if (i % 3 == 2) {
			y += 15;
			x = 73;
		}
	}

	clearKeyQueue();

	prefix.clear();
	_currentCode.clear();
	_currentWord.clear();
	_wordNumber = 0;

	int mode = kModePre;

	bool needRefresh = true;

	for (;;) {
		if (needRefresh) {
			for (int i = 0; buttons[i]; i++) {
				int color1 = colors[i * 2];
				int color2 = colors[i * 2 + 1];

				if (i == 9 && !((mode != kModeAbc && _predictiveDictActLine && numMatchingWords > 1)
							|| (mode == kModeAbc && _currentWord.size() && _currentWord.lastChar() != ' '))) { // Next
					color2 = 7;
				}

				// needs fixing, or remove it!
				bool _addIsActive = false; // FIXME: word adding is not implemented
				if (i == 10 && !_addIsActive) { // Add
					color2 = 7;
				}
				if (i == 14) {
					_gfx->drawDefaultStyleButton(bx[i], by[i], modes[mode], i == active, 0, color1, color2);
				} else {
					_gfx->drawDefaultStyleButton(bx[i], by[i], buttons[i], i == active, 0, color1, color2);
				}
			}

			Common::strlcpy(temp, prefix.c_str(), sizeof(temp));
			Common::strlcat(temp, _currentWord.c_str(), sizeof(temp));

			for (int i = prefix.size() + _currentCode.size(); i < MAXWORDLEN; i++)
				temp[i] = ' ';
			temp[MAXWORDLEN] = 0;

			printText(temp, 0, 8, 7, MAXWORDLEN, 15, 0);
			_gfx->flushBlock(62, 54, 249, 66);

			if (active >= 0 && !navigationwithkeys) {
				// provide visual feedback only when not navigating with the arrows
				// so that the user can see the active button.
				active = -1;
				needRefresh = true;
			} else
				needRefresh = false;

			_gfx->doUpdate();
		}

		pollTimer();
		key = doPollKeyboard();
		processkey = false;
		switch (key) {
		case KEY_ENTER:
			if (navigationwithkeys) {
				// when the user has utilized arrow key navigation,
				// interpret enter as 'click' on the active button
				active = lastactive;
			} else {
				// else it is a shortcut for 'Ok'
				active = 13;
			}
			processkey = true;
			break;
		case KEY_ESCAPE:
			rc = false;
			goto getout;
		case BUTTON_LEFT:
			navigationwithkeys = false;
			for (int i = 0; buttons[i]; i++) {
				if (_gfx->testButton(bx[i], by[i], buttons[i])) {
					active = i;
					processkey = true;
					break;
				}
			}
			break;
		case KEY_BACKSPACE:
			active = 11;
			processkey = true;
			break;
		case '#':
			active = 9;
			processkey = true;
			break;
		case '*':
			active = 14;
			processkey = true;
			break;
		case 0x09:	// Tab
			navigationwithkeys = true;
			debugC(3, kDebugLevelText, "Focus change");
			lastactive = active = lastactive + 1;
			active %= ARRAYSIZE(buttons) - 1;
			needRefresh = true;
			break;
		case KEY_LEFT:
			navigationwithkeys = true;
			if (lastactive == 0 || lastactive == 3 || lastactive == 6)
				active = lastactive + 2;
			else if (lastactive == 9)
				active = 15;
			else if (lastactive == 11)
				active = 11;
			else if (lastactive == 12)
				active = 13;
			else if (lastactive == 14)
				active = 10;
			else
				active = lastactive - 1;
			lastactive = active;
			needRefresh = true;
			break;
		case KEY_RIGHT:
			navigationwithkeys = true;
			if (lastactive == 2 || lastactive == 5 || lastactive == 8)
				active = lastactive - 2;
			else if (lastactive == 10)
				active = 14;
			else if (lastactive == 11)
				active = 11;
			else if (lastactive == 13)
				active = 12;
			else if (lastactive == 15)
				active = 9;
			else
				active = lastactive + 1;
			lastactive = active;
			needRefresh = true;
			break;
		case KEY_UP:
			navigationwithkeys = true;
			if (lastactive <= 2)
				active = 11;
			else if (lastactive == 9 || lastactive == 10)
				active = lastactive - 2;
			else if (lastactive == 11)
				active = 13;
			else if (lastactive == 14)
				active = 8;
			else if (lastactive == 15)
				active = 6;
			else
				active = lastactive - 3;
			lastactive = active;
			needRefresh = true;
			break;
		case KEY_DOWN:
			navigationwithkeys = true;
			if (lastactive == 6)
				active = 15;
			else if (lastactive == 7 || lastactive == 8)
				active = lastactive + 2;
			else if (lastactive == 11)
				active = 0;
			else if (lastactive == 12 || lastactive == 13)
				active = 11;
			else if (lastactive == 14 || lastactive == 15)
				active = lastactive - 2;
			else
				active = lastactive + 3;
			lastactive = active;
			needRefresh = true;
			break;
		default:
			// handle numeric buttons
			if (key >= '1' && key <= '9') {
				active = key - '1';
				processkey = true;
			} else if (key == '0') {
				active = 15;
				processkey = true;
			}
			break;
		}

		if (processkey) {
			if (active >= 0) {
				needRefresh = true;
				lastactive = active;
				if (active == 15 && mode != kModeNum) { // Space
					// bring MRU word at the top of the list when changing words
					if (mode == kModePre && _predictiveDictActLine && numMatchingWords > 1 && _wordNumber != 0)
						bringWordtoTop(_predictiveDictActLine, _wordNumber);
					strncpy(temp, _currentWord.c_str(), _currentCode.size());
					temp[_currentCode.size()] = 0;
					prefix += temp;
					prefix += " ";
					_currentCode.clear();
					_currentWord.clear();
					numMatchingWords = 0;
					memset(repeatcount, 0, sizeof(repeatcount));
				} else if (active < 9 || active == 11 || active == 15) { // number or backspace
					if (active == 11) { // backspace
						if (_currentCode.size()) {
							repeatcount[_currentCode.size() - 1] = 0;
							_currentCode.deleteLastChar();
						} else {
							if (prefix.size())
								prefix.deleteLastChar();
						}
					} else if (prefix.size() + _currentCode.size() < MAXWORDLEN - 1) { // don't overflow the dialog line
						if (active == 15) { // zero
							_currentCode += buttonStr[9];
						} else {
							_currentCode += buttonStr[active];
						}
					}

					switch (mode) {
					case kModeNum:
						_currentWord = _currentCode;
						break;
					case kModePre:
						if (!matchWord() && _currentCode.size()) {
							_currentCode.deleteLastChar();
							matchWord();
						}
						numMatchingWords = countWordsInString(_predictiveDictActLine);
						break;
					case kModeAbc:
						for (x = 0; x < _currentCode.size(); x++)
							if (_currentCode[x] >= '1')
								temp[x] = buttons[_currentCode[x] - '1'][3 + repeatcount[x]];
						temp[_currentCode.size()] = 0;
						_currentWord = temp;
					}
				} else if (active == 9) { // next
					if (mode == kModePre) {
						if (_predictiveDictActLine && numMatchingWords > 1) {
							_wordNumber = (_wordNumber + 1) % numMatchingWords;
							char tmp[MAXLINELEN];
							strncpy(tmp, _predictiveDictActLine, MAXLINELEN);
							tmp[MAXLINELEN - 1] = 0;
							char *tok = strtok(tmp, " ");
							for (uint8 i = 0; i <= _wordNumber; i++)
								tok = strtok(NULL, " ");
							_currentWord = Common::String(tok, _currentCode.size());
						}
					} else if (mode == kModeAbc){
						x = _currentCode.size();
						if (x) {
							if (_currentCode.lastChar() == '1' || _currentCode.lastChar() == '7' || _currentCode.lastChar() == '9')
								repeatcount[x - 1] = (repeatcount[x - 1] + 1) % 4;
							else
								repeatcount[x - 1] = (repeatcount[x - 1] + 1) % 3;
							if (_currentCode.lastChar() >= '1')
								_currentWord.setChar(buttons[_currentCode[x - 1] - '1'][3 + repeatcount[x - 1]], x-1);
						}
					}
				} else if (active == 10) { // add
					debug(0, "add");
				} else if (active == 13) { // Ok
					// bring MRU word at the top of the list when ok'ed out of the dialog
					if (mode == kModePre && _predictiveDictActLine && numMatchingWords > 1 && _wordNumber != 0)
						bringWordtoTop(_predictiveDictActLine, _wordNumber);
					rc = true;
					goto press;
				} else if (active == 14) { // Mode
					mode++;
					if (mode > kModeAbc)
						mode = kModePre;

					// truncate current input at mode change
					strncpy(temp, _currentWord.c_str(), _currentCode.size());
					temp[_currentCode.size()] = 0;
					prefix += temp;
					_currentCode.clear();
					_currentWord.clear();
					memset(repeatcount, 0, sizeof(repeatcount));
				} else {
					goto press;
				}
			}
		}
	}

 press:
	Common::strlcpy(_predictiveResult, prefix.c_str(), sizeof(_predictiveResult));
	Common::strlcat(_predictiveResult, _currentWord.c_str(), sizeof(_predictiveResult));

 getout:
	// if another window was shown, bring it up again
	if (!tmpwindow.active)
		closeWindow();
	else {
		_gfx->restoreBlock(_game.window.x1, _game.window.y1,
				_game.window.x2, _game.window.y2, _game.window.buffer);

		free(_game.window.buffer);
		memcpy(&(_game.window), &tmpwindow, sizeof(AgiBlock));
		_gfx->doUpdate();
	}

	_system->setFeatureState(OSystem::kFeatureDisableKeyFiltering, false);
	_predictiveDialogRunning = false;

	return rc;
}
Example #9
0
/**
 * Display a message box with buttons.
 * This function displays the specified message in a text box
 * centered in the screen and waits until a button is pressed.
 * @param p The text to be displayed
 * @param b NULL-terminated list of button labels
 */
int AgiEngine::selectionBox(const char *m, const char **b) {
	int numButtons = 0;
	int x, y, i, s;
	int key, active = 0;
	int rc = -1;
	int bx[5], by[5];

	_noSaveLoadAllowed = true;

	_sprites->eraseBoth();
	blitTextbox(m, -1, -1, -1);

	x = _game.window.x1 + 5 * CHAR_COLS / 2;
	y = _game.window.y2 - 5 * CHAR_LINES / 2;
	s = _game.window.x2 - _game.window.x1 + 1 - 5 * CHAR_COLS;
	debugC(3, kDebugLevelText, "selectionBox(): s = %d", s);

	// Automatically position buttons
	for (i = 0; b[i]; i++) {
		numButtons++;
		s -= CHAR_COLS * strlen(b[i]);
	}

	if (i > 1) {
		debugC(3, kDebugLevelText, "selectionBox(): s / %d = %d", i - 1, s / (i - 1));
		s /= (i - 1);
	} else {
		x += s / 2;
	}

	for (i = 0; b[i]; i++) {
		bx[i] = x;
		by[i] = y;
		x += CHAR_COLS * strlen(b[i]) + s;
	}

	_sprites->blitBoth();

	clearKeyQueue();

	AllowSyntheticEvents on(this);

	debugC(4, kDebugLevelText, "selectionBox(): waiting...");
	while (!(shouldQuit() || _restartGame)) {
		for (i = 0; b[i]; i++)
			_gfx->drawCurrentStyleButton(bx[i], by[i], b[i], i == active, false, i == 0);

		pollTimer();
		key = doPollKeyboard();
		switch (key) {
		case KEY_ENTER:
			rc = active;
			goto press;
		case KEY_ESCAPE:
			rc = -1;
			goto getout;
		case KEY_RIGHT:
			active++;
			if (active >= numButtons)
				active = 0;
			break;
		case KEY_LEFT:
			active--;
			if (active < 0)
				active = numButtons - 1;
			break;
		case BUTTON_LEFT:
			for (i = 0; b[i]; i++) {
				if (_gfx->testButton(bx[i], by[i], b[i])) {
					rc = active = i;
					goto press;
				}
			}
			break;
		case 0x09:	// Tab
			debugC(3, kDebugLevelText, "selectionBox(): Focus change");
			active++;
			active %= i;
			break;
		}
		_gfx->doUpdate();
	}

press:
	debugC(4, kDebugLevelText, "selectionBox(): Button pressed: %d", rc);

getout:
	closeWindow();
	debugC(2, kDebugLevelText, "selectionBox(): Result = %d", rc);

	_noSaveLoadAllowed = false;

	return rc;
}
Example #10
0
// If main_cycle returns false, don't process more events!
int AgiEngine::mainCycle() {
	unsigned int key, kascii;
	VtEntry *v = &_game.viewTable[0];

	pollTimer();
	updateTimer();

	key = doPollKeyboard();

	// In AGI Mouse emulation mode we must update the mouse-related
	// vars in every interpreter cycle.
	//
	// We run AGIMOUSE always as a side effect
	if (getFeatures() & GF_AGIMOUSE || true) {
		_game.vars[28] = _mouse.x / 2;
		_game.vars[29] = _mouse.y;
	}
	if (key == KEY_PRIORITY) {
		_sprites->eraseBoth();
		_debug.priority = !_debug.priority;
		_picture->showPic();
		_sprites->blitBoth();
		_sprites->commitBoth();
		key = 0;
	}

	if (key == KEY_STATUSLN) {
		_debug.statusline = !_debug.statusline;
		writeStatus();
		key = 0;
	}

	// Click-to-walk mouse interface
	if (_game.playerControl && v->flags & ADJ_EGO_XY) {
		int toX = v->parm1;
		int toY = v->parm2;

		// AGI Mouse games use ego's sprite's bottom left corner for mouse walking target.
		// Amiga games use ego's sprite's bottom center for mouse walking target.
		// TODO: Check what Atari ST AGI and Apple IIGS AGI use for mouse walking target.
		if (getPlatform() == Common::kPlatformAmiga)
			toX -= (v->xSize / 2); // Center ego's sprite horizontally

		// Adjust ego's sprite's mouse walking target position (These parameters are
		// controlled with the adj.ego.move.to.x.y-command). Note that these values rely
		// on the horizontal centering of the ego's sprite at least on the Amiga platform.
		toX += _game.adjMouseX;
		toY += _game.adjMouseY;

		v->direction = getDirection(v->xPos, v->yPos, toX, toY, v->stepSize);

		if (v->direction == 0)
			inDestination(v);
	}

	kascii = KEY_ASCII(key);

	if (kascii)
		setvar(vKey, kascii);

process_key:

	switch (_game.inputMode) {
	case INPUT_NORMAL:
		if (!handleController(key)) {
			if (key == 0 || !_game.inputEnabled)
				break;
			handleKeys(key);

			// if ESC pressed, activate menu before
			// accept.input from the interpreter cycle
			// sets the input mode to normal again
			// (closes: #540856)
			if (key == KEY_ESCAPE) {
				key = 0;
				goto process_key;
			}

			// commented out to close Sarien bug #438872
			//if (key)
			//	_game.keypress = key;
		}
		break;
	case INPUT_GETSTRING:
		handleController(key);
		handleGetstring(key);
		setvar(vKey, 0);	// clear ENTER key
		break;
	case INPUT_MENU:
		_menu->keyhandler(key);
		_gfx->doUpdate();
		return false;
	case INPUT_NONE:
		handleController(key);
		if (key)
			_game.keypress = key;
		break;
	}
	_gfx->doUpdate();

	if (_game.msgBoxTicks > 0)
		_game.msgBoxTicks--;

	return true;
}
Example #11
0
void CYSFReflector::run()
{
	bool ret = m_conf.read();
	if (!ret) {
		::fprintf(stderr, "YSFRefector: cannot read the .ini file\n");
		return;
	}

	ret = ::LogInitialise(m_conf.getLogFilePath(), m_conf.getLogFileRoot(), m_conf.getLogFileLevel(), m_conf.getLogDisplayLevel());
	if (!ret) {
		::fprintf(stderr, "YSFReflector: unable to open the log file\n");
		return;
	}

#if !defined(_WIN32) && !defined(_WIN64)
	bool m_daemon = m_conf.getDaemon();
	if (m_daemon) {
		// Create new process
		pid_t pid = ::fork();
		if (pid == -1) {
			::LogWarning("Couldn't fork() , exiting");
			return;
		}
		else if (pid != 0)
			exit(EXIT_SUCCESS);

		// Create new session and process group
		if (::setsid() == -1) {
			::LogWarning("Couldn't setsid(), exiting");
			return;
		}

		// Set the working directory to the root directory
		if (::chdir("/") == -1) {
			::LogWarning("Couldn't cd /, exiting");
			return;
		}

		::close(STDIN_FILENO);
		::close(STDOUT_FILENO);
		::close(STDERR_FILENO);

		//If we are currently root...
		if (getuid() == 0) {
			struct passwd* user = ::getpwnam("mmdvm");
			if (user == NULL) {
				::LogError("Could not get the mmdvm user, exiting");
				return;
			}

			uid_t mmdvm_uid = user->pw_uid;
			gid_t mmdvm_gid = user->pw_gid;

			//Set user and group ID's to mmdvm:mmdvm
			if (setgid(mmdvm_gid) != 0) {
				::LogWarning("Could not set mmdvm GID, exiting");
				return;
			}

			if (setuid(mmdvm_uid) != 0) {
				::LogWarning("Could not set mmdvm UID, exiting");
				return;
			}

			//Double check it worked (AKA Paranoia) 
			if (setuid(0) != -1) {
				::LogWarning("It's possible to regain root - something is wrong!, exiting");
				return;
			}
		}
	}
#endif

	CNetwork network(m_conf.getNetworkPort(), m_conf.getName(), m_conf.getDescription(), m_conf.getNetworkDebug());

	ret = network.open();
	if (!ret)
		return;

	CStopWatch stopWatch;
	stopWatch.start();

	CTimer dumpTimer(1000U, 120U);
	dumpTimer.start();

	CTimer pollTimer(1000U, 5U);
	pollTimer.start();

	LogMessage("Starting YSFReflector-%s", VERSION);

	CTimer watchdogTimer(1000U, 0U, 1500U);

	unsigned char tag[YSF_CALLSIGN_LENGTH];
	unsigned char src[YSF_CALLSIGN_LENGTH];
	unsigned char dst[YSF_CALLSIGN_LENGTH];

	for (;;) {
		unsigned char buffer[200U];

		unsigned int len = network.readData(buffer);
		if (len > 0U) {
			if (!watchdogTimer.isRunning()) {
				::memcpy(tag, buffer + 4U, YSF_CALLSIGN_LENGTH);

				if (::memcmp(buffer + 14U, "          ", YSF_CALLSIGN_LENGTH) != 0)
					::memcpy(src, buffer + 14U, YSF_CALLSIGN_LENGTH);
				else
					::memcpy(src, "??????????", YSF_CALLSIGN_LENGTH);

				if (::memcmp(buffer + 24U, "          ", YSF_CALLSIGN_LENGTH) != 0)
					::memcpy(dst, buffer + 24U, YSF_CALLSIGN_LENGTH);
				else
					::memcpy(dst, "??????????", YSF_CALLSIGN_LENGTH);

				LogMessage("Received data from %10.10s to %10.10s at %10.10s", src, dst, buffer + 4U);
			} else {
				if (::memcmp(tag, buffer + 4U, YSF_CALLSIGN_LENGTH) == 0) {
					bool changed = false;

					if (::memcmp(buffer + 14U, "          ", YSF_CALLSIGN_LENGTH) != 0 && ::memcmp(src, "??????????", YSF_CALLSIGN_LENGTH) == 0) {
						::memcpy(src, buffer + 14U, YSF_CALLSIGN_LENGTH);
						changed = true;
					}

					if (::memcmp(buffer + 24U, "          ", YSF_CALLSIGN_LENGTH) != 0 && ::memcmp(dst, "??????????", YSF_CALLSIGN_LENGTH) == 0) {
						::memcpy(dst, buffer + 24U, YSF_CALLSIGN_LENGTH);
						changed = true;
					}

					if (changed)
						LogMessage("Received data from %10.10s to %10.10s at %10.10s", src, dst, buffer + 4U);
				}
			}

			// Only accept transmission from an already accepted repeater
			if (::memcmp(tag, buffer + 4U, YSF_CALLSIGN_LENGTH) == 0) {
				watchdogTimer.start();

				std::string callsign = std::string((char*)(buffer + 4U), YSF_CALLSIGN_LENGTH);
				CYSFRepeater* rpt = findRepeater(callsign);
				if (rpt != NULL) {
					for (std::vector<CYSFRepeater*>::const_iterator it = m_repeaters.begin(); it != m_repeaters.end(); ++it) {
						if ((*it)->m_callsign != callsign)
							network.writeData(buffer, (*it)->m_address, (*it)->m_port);
					}
				}

				if ((buffer[34U] & 0x01U) == 0x01U) {
					LogMessage("Received end of transmission");
					watchdogTimer.stop();
				}
			}
		}

		// Refresh/add repeaters based on their polls
		std::string callsign;
		in_addr address;
		unsigned int port;
		bool ret = network.readPoll(callsign, address, port);
		if (ret) {
			CYSFRepeater* rpt = findRepeater(callsign);
			if (rpt == NULL) {
				LogMessage("Adding %s", callsign.c_str());
				rpt = new CYSFRepeater;
				rpt->m_timer.start();
				rpt->m_callsign = callsign;
				rpt->m_address  = address;
				rpt->m_port     = port;
				m_repeaters.push_back(rpt);
				network.setCount(m_repeaters.size());
			} else {
				rpt->m_timer.start();
				rpt->m_address = address;
				rpt->m_port    = port;
			}
		}

		unsigned int ms = stopWatch.elapsed();
		stopWatch.start();

		network.clock(ms);

		pollTimer.clock(ms);
		if (pollTimer.hasExpired()) {
			for (std::vector<CYSFRepeater*>::const_iterator it = m_repeaters.begin(); it != m_repeaters.end(); ++it)
				network.writePoll((*it)->m_address, (*it)->m_port);
			pollTimer.start();
		}

		// Remove any repeaters that haven't reported for a while
		for (std::vector<CYSFRepeater*>::iterator it = m_repeaters.begin(); it != m_repeaters.end(); ++it)
			(*it)->m_timer.clock(ms);

		for (std::vector<CYSFRepeater*>::iterator it = m_repeaters.begin(); it != m_repeaters.end(); ++it) {
			if ((*it)->m_timer.hasExpired()) {
				LogMessage("Removing %s", (*it)->m_callsign.c_str());
				m_repeaters.erase(it);
				network.setCount(m_repeaters.size());
				break;
			}
		}

		watchdogTimer.clock(ms);
		if (watchdogTimer.isRunning() && watchdogTimer.hasExpired()) {
			LogMessage("Network watchdog has expired");
			watchdogTimer.stop();
		}

		dumpTimer.clock(ms);
		if (dumpTimer.hasExpired()) {
			dumpRepeaters();
			dumpTimer.start();
		}

		if (ms < 5U) {
#if defined(_WIN32) || defined(_WIN64)
			::Sleep(5UL);		// 5ms
#else
			::usleep(5000);		// 5ms
#endif
		}
	}

	network.close();

	::LogFinalise();
}
Example #12
0
int AgiEngine::selectSlot() {
	int i, key, active = 0;
	int rc = -1;
	int hm = 1, vm = 3;	// box margins
	int xmin, xmax, slotClicked;
	char desc[NUM_VISIBLE_SLOTS][40];
	int textCenter, buttonLength, buttonX[2], buttonY;
	const char *buttonText[] = { "  OK  ", "Cancel", NULL };

	_noSaveLoadAllowed = true;

	for (i = 0; i < NUM_VISIBLE_SLOTS; i++) {
		getSavegameDescription(_firstSlot + i, desc[i]);
	}

	textCenter = GFX_WIDTH / CHAR_LINES / 2;
	buttonLength = 6;
	buttonX[0] = (textCenter - 3 * buttonLength / 2) * CHAR_COLS;
	buttonX[1] = (textCenter + buttonLength / 2) * CHAR_COLS;
	buttonY = (vm + 17) * CHAR_LINES;

	for (i = 0; i < 2; i++)
		_gfx->drawCurrentStyleButton(buttonX[i], buttonY, buttonText[i], false, false, i == 0);

	AllowSyntheticEvents on(this);
	int oldFirstSlot = _firstSlot + 1;
	int oldActive = active + 1;

	while (!(shouldQuit() || _restartGame)) {
		int sbPos = 0;

		// Use the extreme scrollbar positions only if the extreme
		// slots are in sight. (We have to calculate this even if we
		// don't redraw the save slots, because it's also used for
		// clicking in the scrollbar.

		if (_firstSlot == 0)
			sbPos = 1;
		else if (_firstSlot == NUM_SLOTS - NUM_VISIBLE_SLOTS)
			sbPos = NUM_VISIBLE_SLOTS - 2;
		else {
			sbPos = 2 + (_firstSlot * (NUM_VISIBLE_SLOTS - 4)) / (NUM_SLOTS - NUM_VISIBLE_SLOTS - 1);
			if (sbPos >= NUM_VISIBLE_SLOTS - 3)
				sbPos = NUM_VISIBLE_SLOTS - 3;
		}

		if (oldFirstSlot != _firstSlot || oldActive != active) {
			char dstr[64];
			for (i = 0; i < NUM_VISIBLE_SLOTS; i++) {
				sprintf(dstr, "[%2d. %-28.28s]", i + _firstSlot, desc[i]);
				printText(dstr, 0, hm + 1, vm + 4 + i,
						(40 - 2 * hm) - 1, i == active ? MSG_BOX_COLOR : MSG_BOX_TEXT,
						i == active ? MSG_BOX_TEXT : MSG_BOX_COLOR);
			}

			char upArrow[] = "^";
			char downArrow[] = "v";
			char scrollBar[] = " ";

			for (i = 1; i < NUM_VISIBLE_SLOTS - 1; i++)
				printText(scrollBar, 35, hm + 1, vm + 4 + i, 1, MSG_BOX_COLOR, 7, true);

			printText(upArrow, 35, hm + 1, vm + 4, 1, 8, 7);
			printText(downArrow, 35, hm + 1, vm + 4 + NUM_VISIBLE_SLOTS - 1, 1, 8, 7);
			printText(scrollBar, 35, hm + 1, vm + 4 + sbPos, 1, MSG_BOX_COLOR, MSG_BOX_TEXT);

			oldActive = active;
			oldFirstSlot = _firstSlot;
		}

		pollTimer();
		key = doPollKeyboard();

		// It may happen that somebody will open GMM while
		// this dialog is open, and load a game
		// We are processing it here, effectively jumping
		// out of the dead loop
		if (getflag(fRestoreJustRan)) {
			rc = -2;
			goto getout;
		}

		switch (key) {
		case KEY_ENTER:
			rc = active;
			strncpy(_game.strings[MAX_STRINGS], desc[i], MAX_STRINGLEN);
			goto press;
		case KEY_ESCAPE:
			rc = -1;
			goto getout;
		case BUTTON_LEFT:
			if (_gfx->testButton(buttonX[0], buttonY, buttonText[0])) {
				rc = active;
				strncpy(_game.strings[MAX_STRINGS], desc[i], MAX_STRINGLEN);
				goto press;
			}
			if (_gfx->testButton(buttonX[1], buttonY, buttonText[1])) {
				rc = -1;
				goto getout;
			}
			slotClicked = ((int)_mouse.y - 1) / CHAR_COLS - (vm + 4);
			xmin = (hm + 1) * CHAR_COLS;
			xmax = xmin + CHAR_COLS * 34;
			if ((int)_mouse.x >= xmin && (int)_mouse.x <= xmax) {
				if (slotClicked >= 0 && slotClicked < NUM_VISIBLE_SLOTS)
					active = slotClicked;
			}
			xmin = (hm + 36) * CHAR_COLS;
			xmax = xmin + CHAR_COLS;
			if ((int)_mouse.x >= xmin && (int)_mouse.x <= xmax) {
				if (slotClicked >= 0 && slotClicked < NUM_VISIBLE_SLOTS) {
					if (slotClicked == 0)
						keyEnqueue(KEY_UP);
					else if (slotClicked == NUM_VISIBLE_SLOTS - 1)
						keyEnqueue(KEY_DOWN);
					else if (slotClicked < sbPos)
						keyEnqueue(KEY_UP_RIGHT);
					else if (slotClicked > sbPos)
						keyEnqueue(KEY_DOWN_RIGHT);
				}
			}
			break;
		case KEY_DOWN:
			active++;
			if (active >= NUM_VISIBLE_SLOTS) {
				if (_firstSlot + NUM_VISIBLE_SLOTS < NUM_SLOTS) {
					_firstSlot++;
					for (i = 1; i < NUM_VISIBLE_SLOTS; i++)
						memcpy(desc[i - 1], desc[i], sizeof(desc[0]));
					getSavegameDescription(_firstSlot + NUM_VISIBLE_SLOTS - 1, desc[NUM_VISIBLE_SLOTS - 1]);
				}
				active = NUM_VISIBLE_SLOTS - 1;
			}
			break;
		case KEY_UP:
			active--;
			if (active < 0) {
				active = 0;
				if (_firstSlot > 0) {
					_firstSlot--;
					for (i = NUM_VISIBLE_SLOTS - 1; i > 0; i--)
						memcpy(desc[i], desc[i - 1], sizeof(desc[0]));
					getSavegameDescription(_firstSlot, desc[0]);
				}
			}
			break;

		// Page Up/Down and mouse wheel scrolling all leave 'active'
		// unchanged so that a visible slot will remain selected.

		case WHEEL_DOWN:
			if (_firstSlot < NUM_SLOTS - NUM_VISIBLE_SLOTS) {
				_firstSlot++;
				for (i = 1; i < NUM_VISIBLE_SLOTS; i++)
					memcpy(desc[i - 1], desc[i], sizeof(desc[0]));
				getSavegameDescription(_firstSlot + NUM_VISIBLE_SLOTS - 1, desc[NUM_VISIBLE_SLOTS - 1]);
			}
			break;
		case WHEEL_UP:
			if (_firstSlot > 0) {
				_firstSlot--;
				for (i = NUM_VISIBLE_SLOTS - 1; i > 0; i--)
					memcpy(desc[i], desc[i - 1], sizeof(desc[0]));
				getSavegameDescription(_firstSlot, desc[0]);
			}
			break;
		case KEY_DOWN_RIGHT:
			// This is probably triggered by Page Down.
			_firstSlot += NUM_VISIBLE_SLOTS;
			if (_firstSlot > NUM_SLOTS - NUM_VISIBLE_SLOTS) {
				_firstSlot = NUM_SLOTS - NUM_VISIBLE_SLOTS;
			}
			for (i = 0; i < NUM_VISIBLE_SLOTS; i++)
				getSavegameDescription(_firstSlot + i, desc[i]);
			break;
		case KEY_UP_RIGHT:
			// This is probably triggered by Page Up.
			_firstSlot -= NUM_VISIBLE_SLOTS;
			if (_firstSlot < 0) {
				_firstSlot = 0;
			}
			for (i = 0; i < NUM_VISIBLE_SLOTS; i++)
				getSavegameDescription(_firstSlot + i, desc[i]);
			break;
		}
		_gfx->doUpdate();
	}

press:
	debugC(8, kDebugLevelMain | kDebugLevelInput, "Button pressed: %d", rc);

getout:
	closeWindow();

	_noSaveLoadAllowed = false;

	return rc;
}