1 #include <wx/dcbuffer.h>
2 
3 #include "wxvbam.h"
4 #include "drawing.h"
5 #include "../common/ConfigManager.h"
6 #include "../common/Patch.h"
7 #include "../gb/gbPrinter.h"
8 #include "../gba/RTC.h"
9 #include "../gba/agbprint.h"
10 #include "../sdl/text.h"
11 #include "filters.h"
12 #include "../../version.h"
13 
14 int emulating;
15 
IMPLEMENT_DYNAMIC_CLASS(GameArea,wxPanel)16 IMPLEMENT_DYNAMIC_CLASS(GameArea, wxPanel)
17 
18 GameArea::GameArea()
19 	: wxPanel(), loaded(IMAGE_UNKNOWN), panel(NULL), emusys(NULL),
20 	  basic_width(GBAWidth), basic_height(GBAHeight), fullscreen(false),
21 	  paused(false), was_paused(false), rewind_time(0), do_rewind(false),
22 	  rewind_mem(0), pointer_blanked(false), mouse_active_time(0)
23 {
24 	SetSizer(new wxBoxSizer(wxVERTICAL));
25 	// all renderers prefer 32-bit
26 	// well, "simple" prefers 24-bit, but that's not available for filters
27 	systemColorDepth = 32;
28 	hq2x_init(32);
29 	Init_2xSaI(32);
30 }
31 
LoadGame(const wxString & name)32 void GameArea::LoadGame(const wxString &name)
33 {
34 	rom_scene_rls = wxT("-");
35 	rom_scene_rls_name = wxT("-");
36 	rom_name = wxT("");
37 	// fex just crashes if file does not exist and it's compressed,
38 	// so check first
39 	wxFileName fnfn(name);
40 	bool badfile = !fnfn.IsFileReadable();
41 
42 	// if path was relative, look for it before giving up
43 	if (badfile && !fnfn.IsAbsolute())
44 	{
45 		wxString rp = fnfn.GetPath();
46 
47 		// can't really decide which dir to use, so try GBA first, then GB
48 		if (!wxGetApp().GetAbsolutePath(gopts.gba_rom_dir).empty())
49 		{
50 			fnfn.SetPath(wxGetApp().GetAbsolutePath(gopts.gba_rom_dir) + wxT('/') + rp);
51 			badfile = !fnfn.IsFileReadable();
52 		}
53 
54 		if (badfile && !wxGetApp().GetAbsolutePath(gopts.gb_rom_dir).empty())
55 		{
56 			fnfn.SetPath(wxGetApp().GetAbsolutePath(gopts.gb_rom_dir) + wxT('/') + rp);
57 			badfile = !fnfn.IsFileReadable();
58 		}
59 
60 		if (badfile && !wxGetApp().GetAbsolutePath(gopts.gbc_rom_dir).empty())
61 		{
62 			fnfn.SetPath(wxGetApp().GetAbsolutePath(gopts.gbc_rom_dir) + wxT('/') + rp);
63 			badfile = !fnfn.IsFileReadable();
64 		}
65 	}
66 
67 	// auto-conversion of wxCharBuffer to const char * seems broken
68 	// so save underlying wxCharBuffer (or create one of none is used)
69 	wxCharBuffer fnb(fnfn.GetFullPath().mb_fn_str());
70 	const char* fn = fnb.data();
71 	IMAGE_TYPE t = badfile ? IMAGE_UNKNOWN : utilFindType(fn);
72 
73 	if (t == IMAGE_UNKNOWN)
74 	{
75 		wxString s;
76 		s.Printf(_("%s is not a valid ROM file"), name.c_str());
77 		wxMessageDialog dlg(GetParent(), s, _("Problem loading file"), wxOK | wxICON_ERROR);
78 		dlg.ShowModal();
79 		return;
80 	}
81 
82 	{
83 		wxFileConfig* cfg = wxGetApp().cfg;
84 
85 		if (!gopts.recent_freeze)
86 		{
87 			gopts.recent->AddFileToHistory(name);
88 			wxGetApp().frame->SetRecentAccels();
89 			cfg->SetPath(wxT("/Recent"));
90 			gopts.recent->Save(*cfg);
91 			cfg->SetPath(wxT("/"));
92 			cfg->Flush();
93 		}
94 	}
95 
96 	UnloadGame();
97 	// strip extension from actual game file name
98 	// FIXME: save actual file name of archive so patches & cheats can
99 	// be loaded from archive
100 	// FIXME: if archive name does not match game name, prepend archive
101 	// name to uniquify game name
102 	loaded_game = fnfn;
103 	loaded_game.ClearExt();
104 	loaded_game.MakeAbsolute();
105 	// load patch, if enabled
106 	// note that it is difficult to load from archive due to
107 	// ../common/Patch.cpp depending on opening the file itself and doing
108 	// lots of seeking & such.  The only way would be to copy the patch
109 	// out to a temporary file and load it (and can't just use
110 	// AssignTempFileName because it needs correct extension)
111 	// too much trouble for now, though
112 	bool loadpatch = autoPatch;
113 	wxFileName pfn = loaded_game;
114 	int ovSaveType = 0;
115 
116 	if (loadpatch)
117 	{
118 		// SetExt may strip something off by accident, so append to text instead
119 		// pfn.SetExt(wxT("ips")
120 		pfn.SetFullName(pfn.GetFullName() + wxT(".ips"));
121 
122 		if (!pfn.IsFileReadable())
123 		{
124 			pfn.SetExt(wxT("ups"));
125 
126 			if (!pfn.IsFileReadable())
127 			{
128 				pfn.SetExt(wxT("ppf"));
129 				loadpatch = pfn.IsFileReadable();
130 			}
131 		}
132 	}
133 
134 	if (t == IMAGE_GB)
135 	{
136 		if (!gbLoadRom(fn))
137 		{
138 			wxString s;
139 			s.Printf(_("Unable to load Game Boy ROM %s"), name.c_str());
140 			wxMessageDialog dlg(GetParent(), s, _("Problem loading file"), wxOK | wxICON_ERROR);
141 			dlg.ShowModal();
142 			return;
143 		}
144 
145 		rom_size = gbRomSize;
146 
147 		if (loadpatch)
148 		{
149 			int size = rom_size;
150 			// auto-conversion of wxCharBuffer to const char * seems broken
151 			// so save underlying wxCharBuffer (or create one of none is used)
152 			wxCharBuffer pfnb(pfn.GetFullPath().mb_fn_str());
153 			applyPatch(pfnb.data(), &gbRom, &size);
154 
155 			if (size != rom_size)
156 				gbUpdateSizes();
157 
158 			rom_size = size;
159 		}
160 
161 		// start sound; this must happen before CPU stuff
162 		gb_effects_config.echo = (float)gopts.gb_echo / 100.0;
163 		gb_effects_config.stereo = (float)gopts.gb_stereo / 100.0;
164 		gbSoundSetDeclicking(gopts.gb_declick);
165 		soundInit();
166 		soundSetThrottle(throttle);
167 		soundSetEnable(gopts.sound_en);
168 		gbSoundSetSampleRate(!gopts.sound_qual ? 48000 :
169 		                     44100 / (1 << (gopts.sound_qual - 1)));
170 		soundSetVolume((float)gopts.sound_vol / 100.0);
171 		gbGetHardwareType();
172 		bool use_bios = false;
173 		// auto-conversion of wxCharBuffer to const char * seems broken
174 		// so save underlying wxCharBuffer (or create one of none is used)
175 		const char* fn = NULL;
176 		wxCharBuffer fnb;
177 
178 		if (gbCgbMode)
179 		{
180 			use_bios = useBiosFileGBC;
181 			fnb = gopts.gbc_bios.mb_fn_str();
182 		}
183 		else
184 		{
185 			use_bios = useBiosFileGB;
186 			fnb = gopts.gb_bios.mb_fn_str();
187 		}
188 
189 		fn = fnb.data();
190 		gbCPUInit(fn, use_bios);
191 
192 		if (use_bios && !useBios)
193 		{
194 			wxLogError(_("Could not load BIOS %s"), (gbCgbMode ? gopts.gbc_bios : gopts.gb_bios).c_str());
195 			// could clear use flag & file name now, but better to force
196 			// user to do it
197 		}
198 
199 		gbReset();
200 
201 		if (gbBorderOn)
202 		{
203 			basic_width = gbBorderLineSkip = SGBWidth;
204 			basic_height = SGBHeight;
205 			gbBorderColumnSkip = (SGBWidth - GBWidth) / 2;
206 			gbBorderRowSkip = (SGBHeight - GBHeight) / 2;
207 		}
208 		else
209 		{
210 			basic_width = gbBorderLineSkip = GBWidth;
211 			basic_height = GBHeight;
212 			gbBorderColumnSkip = gbBorderRowSkip = 0;
213 		}
214 
215 		emusys = &GBSystem;
216 	}
217 	else /* if(t == IMAGE_GBA) */
218 	{
219 		if (!(rom_size = CPULoadRom(fn)))
220 		{
221 			wxString s;
222 			s.Printf(_("Unable to load Game Boy Advance ROM %s"), name.c_str());
223 			wxMessageDialog dlg(GetParent(), s, _("Problem loading file"), wxOK | wxICON_ERROR);
224 			dlg.ShowModal();
225 			return;
226 		}
227 
228 		rom_crc32 = crc32(0L, rom, rom_size);
229 
230 		if (loadpatch)
231 		{
232 			// don't use real rom size or it might try to resize rom[]
233 			// instead, use known size of rom[]
234 			int size = 0x2000000;
235 			// auto-conversion of wxCharBuffer to const char * seems broken
236 			// so save underlying wxCharBuffer (or create one of none is used)
237 			wxCharBuffer pfnb(pfn.GetFullPath().mb_fn_str());
238 			applyPatch(pfnb.data(), &rom, &size);
239 			// that means we no longer really know rom_size either <sigh>
240 		}
241 
242 		wxFileConfig* cfg = wxGetApp().overrides;
243 		wxString id = wxString((const char*)&rom[0xac], wxConvLibc, 4);
244 
245 		if (cfg->HasGroup(id))
246 		{
247 			cfg->SetPath(id);
248 			rtcEnable(cfg->Read(wxT("rtcEnabled"), rtcEnabled));
249 			int fsz = cfg->Read(wxT("flashSize"), (long)0);
250 
251 			if (fsz != 0x10000 && fsz != 0x20000)
252 				fsz = 0x10000 << winFlashSize;
253 
254 			flashSetSize(fsz);
255 			ovSaveType = cfg->Read(wxT("saveType"), cpuSaveType);
256 
257 			if (ovSaveType < 0 || ovSaveType > 5)
258 				ovSaveType = 0;
259 
260 			if (ovSaveType == 0)
261 				utilGBAFindSave(rom_size);
262 			else
263 				saveType = ovSaveType;
264 
265 			mirroringEnable = cfg->Read(wxT("mirroringEnabled"), (long)1);
266 			cfg->SetPath(wxT("/"));
267 		}
268 		else
269 		{
270 			rtcEnable(rtcEnabled);
271 			flashSetSize(0x10000 << winFlashSize);
272 
273 			if (cpuSaveType < 0 || cpuSaveType > 5)
274 				cpuSaveType = 0;
275 
276 			if (cpuSaveType == 0)
277 				utilGBAFindSave(rom_size);
278 			else
279 				saveType = cpuSaveType;
280 
281 			mirroringEnable = true;
282 		}
283 
284 		doMirroring(mirroringEnable);
285 		// start sound; this must happen before CPU stuff
286 		gb_effects_config.echo = (float)gopts.gb_echo / 100.0;
287 		gb_effects_config.stereo = (float)gopts.gb_stereo / 100.0;
288 		gbSoundSetDeclicking(gopts.gb_declick);
289 		soundInit();
290 		soundSetThrottle(throttle);
291 		soundSetEnable(gopts.sound_en);
292 		gbSoundSetSampleRate(!gopts.sound_qual ? 48000 :
293 							 44100 / (1 << (gopts.sound_qual - 1)));
294 		soundSetSampleRate(!gopts.sound_qual ? 48000 :
295 		                   44100 / (1 << (gopts.sound_qual - 1)));
296 		soundSetVolume((float)gopts.sound_vol / 100.0);
297 		soundFiltering = (float)gopts.gba_sound_filter / 100.0f;
298 		CPUInit(gopts.gba_bios.mb_fn_str(), useBiosFileGBA);
299 
300 		if (useBiosFileGBA && !useBios)
301 		{
302 			wxLogError(_("Could not load BIOS %s"), gopts.gba_bios.c_str());
303 			// could clear use flag & file name now, but better to force
304 			// user to do it
305 		}
306 
307 		CPUReset();
308 		basic_width = GBAWidth;
309 		basic_height = GBAHeight;
310 		emusys = &GBASystem;
311 	}
312 
313 	if (fullScreen)
314 		GameArea::ShowFullScreen(true);
315 
316 	loaded = t;
317 	SetFrameTitle();
318 	SetFocus();
319 	AdjustSize(true);
320 	emulating = true;
321 	was_paused = true;
322 	MainFrame* mf = wxGetApp().frame;
323 	mf->SetJoystick();
324 	mf->cmd_enable &= ~(CMDEN_GB | CMDEN_GBA);
325 	mf->cmd_enable |= ONLOAD_CMDEN;
326 	mf->cmd_enable |= loaded == IMAGE_GB ? CMDEN_GB : (CMDEN_GBA | CMDEN_NGDB_GBA);
327 	mf->enable_menus();
328 #if (defined __WIN32__ || defined _WIN32)
329 	gbSerialFunction = gbStartLink;
330 #else
331 	gbSerialFunction = NULL;
332 #endif
333 
334 	// probably only need to do this for GB carts
335 	if (winGbPrinterEnabled)
336 		gbSerialFunction = gbPrinterSend;
337 
338 	// probably only need to do this for GBA carts
339 	agbPrintEnable(agbPrint);
340 	// set frame skip based on ROM type
341 	systemFrameSkip = frameSkip;
342 
343 	if (systemFrameSkip < 0)
344 		systemFrameSkip = 0;
345 
346 	// load battery and/or saved state
347 	recompute_dirs();
348 	mf->update_state_ts(true);
349 	bool did_autoload = gopts.autoload_state ? LoadState() : false;
350 
351 	if (!did_autoload || skipSaveGameBattery)
352 	{
353 		wxString bname = loaded_game.GetFullName();
354 #ifndef NO_LINK
355 		// MakeInstanceFilename doesn't do wxString, so just add slave ID here
356 		int playerId = GetLinkPlayerId();
357 
358 		if (playerId >= 0)
359 		{
360 			bname.append(wxT('-'));
361 			bname.append(wxChar(wxT('1') + playerId));
362 		}
363 
364 #endif
365 		bname.append(wxT(".sav"));
366 		wxFileName bat(batdir, bname);
367 		fnb = bat.GetFullPath().mb_fn_str();
368 
369 		if (emusys->emuReadBattery(fnb.data()))
370 		{
371 			wxString msg;
372 			msg.Printf(_("Loaded battery %s"), bat.GetFullPath().c_str());
373 			systemScreenMessage(msg);
374 
375 			if (cpuSaveType == 0 && ovSaveType == 0 && t == IMAGE_GBA)
376 			{
377 				switch (bat.GetSize().GetValue())
378 				{
379 				case 0x200:
380 				case 0x2000:
381 					saveType = 1;
382 					break;
383 
384 				case 0x8000:
385 					saveType = 2;
386 					break;
387 
388 				case 0x10000:
389 					if (saveType == 1 || saveType == 2)
390 						break;
391 
392 				case 0x20000:
393 					saveType = 3;
394 					flashSetSize(bat.GetSize().GetValue());
395 					break;
396 
397 				default:
398 					break;
399 				}
400 
401 				SetSaveType(saveType);
402 			}
403 		}
404 
405 		// forget old save writes
406 		systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;
407 	}
408 
409 	// do an immediate rewind save
410 	// even if loaded from state file: not smart enough yet to just
411 	// do a reset or load from state file when # rewinds == 0
412 	do_rewind = gopts.rewind_interval > 0;
413 	// FIXME: backup battery file (useful if game name conflict)
414 	cheats_dirty = (did_autoload && !skipSaveGameCheats) ||
415 	               (loaded == IMAGE_GB ? gbCheatNumber > 0 : cheatsNumber > 0);
416 
417 	if (gopts.autoload_cheats && (!did_autoload || skipSaveGameCheats))
418 	{
419 		wxFileName cfn = loaded_game;
420 		// SetExt may strip something off by accident, so append to text instead
421 		cfn.SetFullName(cfn.GetFullName() + wxT(".clt"));
422 
423 		if (cfn.IsFileReadable())
424 		{
425 			bool cld;
426 
427 			if (loaded == IMAGE_GB)
428 				cld = gbCheatsLoadCheatList(cfn.GetFullPath().mb_fn_str());
429 			else
430 				cld = cheatsLoadCheatList(cfn.GetFullPath().mb_fn_str());
431 
432 			if (cld)
433 			{
434 				systemScreenMessage(_("Loaded cheats"));
435 				cheats_dirty = false;
436 			}
437 		}
438 	}
439 
440 #ifndef NO_LINK
441 
442 	if (gopts.link_auto)
443 	{
444 		linkMode = mf->GetConfiguredLinkMode();
445 		BootLink(linkMode, gopts.link_host.mb_str(wxConvUTF8), linkTimeout, linkHacks, linkNumPlayers);
446 	}
447 
448 #endif
449 }
450 
SetFrameTitle()451 void GameArea::SetFrameTitle()
452 {
453 	wxString tit = wxT("");
454 
455 	if (loaded != IMAGE_UNKNOWN)
456 	{
457 		tit.append(loaded_game.GetFullName());
458 		tit.append(wxT(" - "));
459 	}
460 
461 	tit.append(wxT("VisualBoyAdvance-M "));
462 #ifndef FINAL_BUILD
463 	tit.append(_("Git:"));
464 #endif
465 #ifndef NO_LINK
466 	int playerId = GetLinkPlayerId();
467 
468 	if (playerId >= 0)
469 	{
470 		tit.append(_(" player "));
471 		tit.append(wxChar(wxT('1') + playerId));
472 	}
473 
474 #endif
475 	wxGetApp().frame->SetTitle(tit);
476 }
477 
recompute_dirs()478 void GameArea::recompute_dirs()
479 {
480 	batdir = gopts.battery_dir;
481 
482 	if (!batdir.size())
483 	{
484 		batdir = loaded_game.GetPathWithSep();
485 	}
486 	else
487 	{
488 		batdir = wxGetApp().GetAbsolutePath(gopts.battery_dir);
489 	}
490 
491 	statedir = gopts.state_dir;
492 
493 	if (!statedir.size())
494 	{
495 		statedir = loaded_game.GetPathWithSep();
496 	}
497 	else
498 	{
499 		statedir = wxGetApp().GetAbsolutePath(gopts.state_dir);
500 	}
501 
502 	if (!wxIsWritable(batdir))
503 		batdir = wxGetApp().GetConfigurationPath();
504 
505 	if (!wxIsWritable(statedir))
506 		statedir = wxGetApp().GetConfigurationPath();
507 }
508 
UnloadGame(bool destruct)509 void GameArea::UnloadGame(bool destruct)
510 {
511 	if (!emulating)
512 		return;
513 
514 	// last opportunity to autosave cheats
515 	if (gopts.autoload_cheats && cheats_dirty)
516 	{
517 		wxFileName cfn = loaded_game;
518 		// SetExt may strip something off by accident, so append to text instead
519 		cfn.SetFullName(cfn.GetFullName() + wxT(".clt"));
520 
521 		if (loaded == IMAGE_GB)
522 		{
523 			if (!gbCheatNumber)
524 				wxRemoveFile(cfn.GetFullPath());
525 			else
526 				gbCheatsSaveCheatList(cfn.GetFullPath().mb_fn_str());
527 		}
528 		else
529 		{
530 			if (!cheatsNumber)
531 				wxRemoveFile(cfn.GetFullPath());
532 			else
533 				cheatsSaveCheatList(cfn.GetFullPath().mb_fn_str());
534 		}
535 	}
536 
537 	// if timer was counting down for save, go ahead and save
538 	// this might not be safe, though..
539 	if (systemSaveUpdateCounter > SYSTEM_SAVE_NOT_UPDATED)
540 	{
541 		SaveBattery(destruct);
542 	}
543 
544 	MainFrame* mf = wxGetApp().frame;
545 #ifndef NO_FFMPEG
546 	snd_rec.Stop();
547 	vid_rec.Stop();
548 #endif
549 	systemStopGameRecording();
550 	systemStopGamePlayback();
551 	debugger = false;
552 	remoteCleanUp();
553 	mf->cmd_enable |= CMDEN_NGDB_ANY;
554 
555 	if (loaded == IMAGE_GB)
556 	{
557 		gbCleanUp();
558 		gbCheatRemoveAll();
559 	}
560 	else if (loaded == IMAGE_GBA)
561 	{
562 		CPUCleanUp();
563 		cheatsDeleteAll(false);
564 	}
565 
566 	emulating = false;
567 	loaded = IMAGE_UNKNOWN;
568 	emusys = NULL;
569 	soundShutdown();
570 
571 	if (destruct)
572 		return;
573 
574 	// in destructor, panel should be auto-deleted by wx since all panels
575 	// are derived from a window attached as child to GameArea
576 	if (panel)
577 		panel->Delete();
578 
579 	panel = NULL;
580 
581 	// close any game-related viewer windows
582 	// in destructor, viewer windows are in process of being deleted anyway
583 	while (!mf->popups.empty())
584 		mf->popups.front()->Close(true);
585 
586 	// remaining items are GUI updates that should not be needed in destructor
587 	SetFrameTitle();
588 	mf->cmd_enable &= UNLOAD_CMDEN_KEEP;
589 	mf->update_state_ts(true);
590 	mf->enable_menus();
591 	mf->SetJoystick();
592 	mf->ResetCheatSearch();
593 
594 	if (rewind_mem)
595 		num_rewind_states = 0;
596 }
597 
LoadState()598 bool GameArea::LoadState()
599 {
600 	int slot = wxGetApp().frame->newest_state_slot();
601 
602 	if (slot < 1)
603 		return false;
604 
605 	return LoadState(slot);
606 }
607 
LoadState(int slot)608 bool GameArea::LoadState(int slot)
609 {
610 	wxString fname;
611 	fname.Printf(SAVESLOT_FMT, game_name().c_str(), slot);
612 	return LoadState(wxFileName(statedir, fname));
613 }
614 
LoadState(const wxFileName & fname)615 bool GameArea::LoadState(const wxFileName &fname)
616 {
617 	// FIXME: first save to backup state if not backup state
618 	bool ret = emusys->emuReadState(fname.GetFullPath().mb_fn_str());
619 
620 	if (ret && num_rewind_states)
621 	{
622 		MainFrame* mf = wxGetApp().frame;
623 		mf->cmd_enable &= ~CMDEN_REWIND;
624 		mf->enable_menus();
625 		num_rewind_states = 0;
626 		// do an immediate rewind save
627 		// even if loaded from state file: not smart enough yet to just
628 		// do a reset or load from state file when # rewinds == 0
629 		do_rewind = true;
630 		rewind_time = gopts.rewind_interval * 6;
631 	}
632 
633 	if (ret)
634 	{
635 		// forget old save writes
636 		systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;
637 		// no point in blending after abrupt change
638 		InterframeCleanup();
639 		// frame rate calc should probably reset as well
640 		was_paused = true;
641 		// save state had a screen frame, so draw it
642 		systemDrawScreen();
643 	}
644 
645 	wxString msg;
646 	msg.Printf(ret ? _("Loaded state %s") : _("Error loading state %s"),
647 	           fname.GetFullPath().c_str());
648 	systemScreenMessage(msg);
649 	return ret;
650 }
651 
SaveState()652 bool GameArea::SaveState()
653 {
654 	return SaveState(wxGetApp().frame->oldest_state_slot());
655 }
656 
SaveState(int slot)657 bool GameArea::SaveState(int slot)
658 {
659 	wxString fname;
660 	fname.Printf(SAVESLOT_FMT, game_name().c_str(), slot);
661 	return SaveState(wxFileName(statedir, fname));
662 }
663 
SaveState(const wxFileName & fname)664 bool GameArea::SaveState(const wxFileName &fname)
665 {
666 	// FIXME: first copy to backup state if not backup state
667 	bool ret = emusys->emuWriteState(fname.GetFullPath().mb_fn_str());
668 	wxGetApp().frame->update_state_ts(true);
669 	wxString msg;
670 	msg.Printf(ret ? _("Saved state %s") : _("Error saving state %s"),
671 	           fname.GetFullPath().c_str());
672 	systemScreenMessage(msg);
673 	return ret;
674 }
675 
SaveBattery(bool quiet)676 void GameArea::SaveBattery(bool quiet)
677 {
678 	// MakeInstanceFilename doesn't do wxString, so just add slave ID here
679 	wxString bname = game_name();
680 #ifndef NO_LINK
681 	int playerId = GetLinkPlayerId();
682 
683 	if (playerId >= 0)
684 	{
685 		bname.append(wxT('-'));
686 		bname.append(wxChar(wxT('1') + playerId));
687 	}
688 
689 #endif
690 	bname.append(wxT(".sav"));
691 	wxFileName bat(batdir, bname);
692 	bat.Mkdir(0777, wxPATH_MKDIR_FULL);
693 	wxString fn = bat.GetFullPath();
694 	// auto-conversion of wxCharBuffer to const char * seems broken
695 	// so save underlying wxCharBuffer (or create one of none is used)
696 	wxCharBuffer fnb = fn.mb_fn_str();
697 	wxString msg;
698 
699 	// FIXME: add option to support ring of backups
700 	// of course some games just write battery way too often for such
701 	// a thing to be useful
702 	if (emusys->emuWriteBattery(fnb.data()))
703 		msg.Printf(_("Wrote battery %s"), fn.c_str());
704 	else
705 		msg.Printf(_("Error writing battery %s"), fn.c_str());
706 
707 	systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;
708 
709 	if (!quiet)
710 		systemScreenMessage(msg);
711 }
712 
AddBorder()713 void GameArea::AddBorder()
714 {
715 	if (basic_width != GBWidth)
716 		return;
717 
718 	basic_width = SGBWidth;
719 	basic_height = SGBHeight;
720 	gbBorderLineSkip = SGBWidth;
721 	gbBorderColumnSkip = (SGBWidth - GBWidth) / 2;
722 	gbBorderRowSkip = (SGBHeight - GBHeight) / 2;
723 	AdjustSize(false);
724 	wxGetApp().frame->Fit();
725 	GetSizer()->Detach(panel->GetWindow());
726 
727 	if (panel)
728 		panel->Delete();
729 
730 	panel = NULL;
731 }
732 
DelBorder()733 void GameArea::DelBorder()
734 {
735 	if (basic_width != SGBWidth)
736 		return;
737 
738 	basic_width = GBWidth;
739 	basic_height = GBHeight;
740 	gbBorderLineSkip = GBWidth;
741 	gbBorderColumnSkip = gbBorderRowSkip = 0;
742 	AdjustSize(false);
743 	wxGetApp().frame->Fit();
744 	GetSizer()->Detach(panel->GetWindow());
745 
746 	if (panel)
747 		panel->Delete();
748 
749 	panel = NULL;
750 }
751 
AdjustMinSize()752 void GameArea::AdjustMinSize()
753 {
754 	wxWindow* frame = wxGetApp().frame;
755 	// note: could safely set min size to 1x or less regardless of video_scale
756 	// but setting it to scaled size makes resizing to default easier
757 	wxSize sz(basic_width * gopts.video_scale, basic_height * gopts.video_scale);
758 	SetMinSize(sz);
759 #if wxCHECK_VERSION(2,8,8)
760 	sz = frame->ClientToWindowSize(sz);
761 #else
762 	sz += frame->GetSize() - frame->GetClientSize();
763 #endif
764 	frame->SetMinSize(sz);
765 }
766 
LowerMinSize()767 void GameArea::LowerMinSize()
768 {
769 	wxWindow* frame = wxGetApp().frame;
770 	wxSize sz(basic_width , basic_height);
771 	SetMinSize(sz);
772 	// do not take decorations into account
773 	frame->SetMinSize(sz);
774 }
775 
AdjustSize(bool force)776 void GameArea::AdjustSize(bool force)
777 {
778 	AdjustMinSize();
779 
780 	if (fullscreen)
781 		return;
782 
783 	const wxSize newsz(basic_width * gopts.video_scale, basic_height * gopts.video_scale);
784 
785 	if (!force)
786 	{
787 		wxSize sz = GetSize();
788 
789 		if (sz.GetWidth() >= newsz.GetWidth() && sz.GetHeight() >= newsz.GetHeight())
790 			return;
791 	}
792 
793 	SetSize(newsz);
794 	GetParent()->SetClientSize(newsz);
795 	wxGetApp().frame->Fit();
796 }
797 
ShowFullScreen(bool full)798 void GameArea::ShowFullScreen(bool full)
799 {
800 	if (full == fullscreen)
801 	{
802 		// in case the tlw somehow lost its mind, force it to proper mode
803 		if (wxGetApp().frame->IsFullScreen() != fullscreen)
804 			wxGetApp().frame->ShowFullScreen(full);
805 
806 		return;
807 	}
808 
809 	fullscreen = full;
810 
811 	// just in case screen mode is going to change, go ahead and preemptively
812 	// delete panel to be recreated immediately after resize
813 	if (panel)
814 	{
815 		panel->Delete();
816 		panel = NULL;
817 	}
818 
819 	// Windows does not restore old window size/pos
820 	// at least under Wine
821 	// so store them before entering fullscreen
822 	static bool cursz_valid = false;
823 	static wxSize cursz;
824 	static wxPoint curpos;
825 	MainFrame* tlw = wxGetApp().frame;
826 	int dno = wxDisplay::GetFromWindow(tlw);
827 
828 	if (!full)
829 	{
830 		if (gopts.fs_mode.w && gopts.fs_mode.h)
831 		{
832 			wxDisplay d(dno);
833 			d.ChangeMode(wxDefaultVideoMode);
834 		}
835 
836 		tlw->ShowFullScreen(false);
837 
838 		if (!cursz_valid)
839 		{
840 			curpos = wxDefaultPosition;
841 			cursz = tlw->GetMinSize();
842 		}
843 
844 		tlw->SetSize(cursz);
845 		tlw->SetPosition(curpos);
846 		AdjustMinSize();
847 	}
848 	else
849 	{
850 		// close all non-modal dialogs
851 		while (!tlw->popups.empty())
852 			tlw->popups.front()->Close();
853 
854 		// mouse stays blank whenever full-screen
855 		HidePointer();
856 		cursz_valid = true;
857 		cursz = tlw->GetSize();
858 		curpos = tlw->GetPosition();
859 		LowerMinSize();
860 
861 		if (gopts.fs_mode.w && gopts.fs_mode.h)
862 		{
863 			// grglflargm
864 			// stupid wx does not do fullscreen properly when video mode is
865 			// changed under X11.   Since it does not use xrandr to switch, it
866 			// just changes the video mode without resizing the desktop.  It
867 			// still uses the original desktop size for fullscreen, though.
868 			// It also seems to stick the top-left corner wherever it pleases,
869 			// so I can't just resize the panel and expect it to show up.
870 			// ^(*&^^&!!!!  maybe just disable this code altogether on UNIX
871 			// most 3d cards are fine with full screen size, anyway.
872 			wxDisplay d(dno);
873 
874 			if (!d.ChangeMode(gopts.fs_mode))
875 			{
876 				wxLogInfo(_("Fullscreen mode %dx%d-%d@%d not supported; looking for another"),
877 				          gopts.fs_mode.w, gopts.fs_mode.h, gopts.fs_mode.bpp, gopts.fs_mode.refresh);
878 				// specifying a mode may not work with bpp/rate of 0
879 				// in particular, unix does Matches() in wrong direction
880 				wxArrayVideoModes vm = d.GetModes();
881 				int best_mode = -1;
882 				int i;
883 
884 				for (i = 0; i < vm.size(); i++)
885 				{
886 					if (vm[i].w != gopts.fs_mode.w || vm[i].h != gopts.fs_mode.h)
887 						continue;
888 
889 					int bpp = vm[i].bpp;
890 
891 					if (gopts.fs_mode.bpp && bpp == gopts.fs_mode.bpp)
892 						break;
893 
894 					if (!gopts.fs_mode.bpp && bpp == 32)
895 					{
896 						gopts.fs_mode.bpp = 32;
897 						break;
898 					}
899 
900 					int bm_bpp = best_mode < 0 ? 0 : vm[i].bpp;
901 
902 					if (bpp == 32 && bm_bpp != 32)
903 						best_mode = i;
904 					else if (bpp == 24 && bm_bpp < 24)
905 						best_mode = i;
906 					else if (bpp == 16 && bm_bpp < 24 && bm_bpp != 16)
907 						best_mode = i;
908 					else if (!best_mode)
909 						best_mode = i;
910 				}
911 
912 				if (i == vm.size() && best_mode >= 0)
913 					i = best_mode;
914 
915 				if (i == vm.size())
916 				{
917 					wxLogWarning(_("Fullscreen mode %dx%d-%d@%d not supported"),
918 					             gopts.fs_mode.w, gopts.fs_mode.h, gopts.fs_mode.bpp, gopts.fs_mode.refresh);
919 					gopts.fs_mode = wxVideoMode();
920 
921 					for (i = 0; i < vm.size(); i++)
922 						wxLogInfo(_("Valid mode: %dx%d-%d@%d"), vm[i].w,
923 						          vm[i].h, vm[i].bpp,
924 						          vm[i].refresh);
925 				}
926 				else
927 				{
928 					gopts.fs_mode.bpp = vm[i].bpp;
929 					gopts.fs_mode.refresh = vm[i].refresh;
930 				}
931 
932 				wxLogInfo(_("Chose mode %dx%d-%d@%d"),
933 				          gopts.fs_mode.w, gopts.fs_mode.h, gopts.fs_mode.bpp, gopts.fs_mode.refresh);
934 
935 				if (!d.ChangeMode(gopts.fs_mode))
936 				{
937 					wxLogWarning(_("Failed to change mode to %dx%d-%d@%d"),
938 					             gopts.fs_mode.w, gopts.fs_mode.h, gopts.fs_mode.bpp, gopts.fs_mode.refresh);
939 					gopts.fs_mode.w = 0;
940 				}
941 			}
942 		}
943 
944 		tlw->ShowFullScreen(true);
945 	}
946 }
947 
~GameArea()948 GameArea::~GameArea()
949 {
950 	UnloadGame(true);
951 
952 	if (rewind_mem)
953 		free(rewind_mem);
954 
955 	if (gopts.fs_mode.w && gopts.fs_mode.h && fullscreen)
956 	{
957 		MainFrame* tlw = wxGetApp().frame;
958 		int dno = wxDisplay::GetFromWindow(tlw);
959 		wxDisplay d(dno);
960 		d.ChangeMode(wxDefaultVideoMode);
961 	}
962 }
963 
Pause()964 void GameArea::Pause()
965 {
966 	if (paused)
967 		return;
968 
969 	paused = was_paused = true;
970 
971 	if (loaded != IMAGE_UNKNOWN)
972 		soundPause();
973 }
974 
Resume()975 void GameArea::Resume()
976 {
977 	if (!paused)
978 		return;
979 
980 	paused = false;
981 	SetExtraStyle(GetExtraStyle() | wxWS_EX_PROCESS_IDLE);
982 
983 	if (loaded != IMAGE_UNKNOWN)
984 		soundResume();
985 
986 	SetFocus();
987 }
988 
OnIdle(wxIdleEvent & event)989 void GameArea::OnIdle(wxIdleEvent &event)
990 {
991 	wxString pl = wxGetApp().pending_load;
992 
993 	if (pl.size())
994 	{
995 		// sometimes this gets into a loop if LoadGame() called before
996 		// clearing pending_load.  weird.
997 		wxGetApp().pending_load = wxEmptyString;
998 		LoadGame(pl);
999 		MainFrame* mf = wxGetApp().frame;
1000 
1001 		if (gdbBreakOnLoad)
1002 			mf->GDBBreak();
1003 
1004 		if (debugger && loaded != IMAGE_GBA)
1005 		{
1006 			wxLogError(_("Not a valid GBA cartridge"));
1007 			UnloadGame();
1008 		}
1009 	}
1010 
1011 	// stupid wx doesn't resize to screen size
1012 	// forcing it this way just puts it in an infinite loop, though
1013 	// with wx trying to resize/reposition to what it thinks is full screen
1014 	// every time it detects a manual resize like this
1015 	if (gopts.fs_mode.w && gopts.fs_mode.h && fullscreen)
1016 	{
1017 		wxSize sz = GetSize();
1018 
1019 		if (sz.GetWidth() != gopts.fs_mode.w || sz.GetHeight() != gopts.fs_mode.h)
1020 		{
1021 			wxGetApp().frame->Move(0, 84);
1022 			SetSize(gopts.fs_mode.w, gopts.fs_mode.h);
1023 		}
1024 	}
1025 
1026 #ifdef __WXMSW__
1027 
1028 	// Check for online updates
1029 	if (gopts.onlineupdates > 0 && !emusys)
1030 	{
1031 		if ((wxDateTime::Now().GetTicks() - gopts.last_update) > gopts.onlineupdates * 24 * 60 * 60)
1032 		{
1033 			wxGetApp().frame->CheckForUpdates();
1034 		}
1035 	}
1036 
1037 #endif
1038 
1039 	if (!emusys)
1040 		return;
1041 
1042 	if (!panel)
1043 	{
1044 		switch (gopts.render_method)
1045 		{
1046 		case RND_SIMPLE:
1047 			panel = new BasicDrawingPanel(this, basic_width, basic_height);
1048 			break;
1049 #ifndef NO_OGL
1050 
1051 		case RND_OPENGL:
1052 			panel = new GLDrawingPanel(this, basic_width, basic_height);
1053 			break;
1054 #endif
1055 #ifndef NO_CAIRO
1056 
1057 		case RND_CAIRO:
1058 			panel = new CairoDrawingPanel(this, basic_width, basic_height);
1059 			break;
1060 #endif
1061 #ifdef __WXMSW__
1062 
1063 		case RND_DIRECT3D:
1064 			panel = new DXDrawingPanel(this, basic_width, basic_height);
1065 			break;
1066 #endif
1067 		}
1068 
1069 		wxWindow* w = panel->GetWindow();
1070 		w->SetBackgroundStyle(wxBG_STYLE_CUSTOM);
1071 		w->Enable(false); // never give it the keyboard focus
1072 		w->SetSize(wxSize(basic_width, basic_height));
1073 
1074 		if (maxScale)
1075 			w->SetMaxSize(wxSize(basic_width * maxScale,
1076 			                     basic_height * maxScale));
1077 
1078 		GetSizer()->Add(w, 1, gopts.retain_aspect ?
1079 		                (wxSHAPED | wxALIGN_CENTER) : wxEXPAND);
1080 		Layout();
1081 
1082 		if (pointer_blanked)
1083 			w->SetCursor(wxCursor(wxCURSOR_BLANK));
1084 	}
1085 
1086 	if (!paused && (!pauseWhenInactive || wxGetApp().frame->HasFocus()))
1087 	{
1088 		HidePointer();
1089 		event.RequestMore();
1090 
1091 		if (debugger)
1092 		{
1093 			was_paused = true;
1094 			dbgMain();
1095 
1096 			if (!emulating)
1097 			{
1098 				emulating = true;
1099 				UnloadGame();
1100 			}
1101 
1102 			return;
1103 		}
1104 
1105 		emusys->emuMain(emusys->emuCount);
1106 #ifndef NO_LINK
1107 
1108 		if (loaded == IMAGE_GBA && GetLinkMode() != LINK_DISCONNECTED)
1109 			CheckLinkConnection();
1110 
1111 #endif
1112 	}
1113 	else
1114 	{
1115 		was_paused = true;
1116 
1117 		if (paused)
1118 			SetExtraStyle(GetExtraStyle() & ~wxWS_EX_PROCESS_IDLE);
1119 	}
1120 
1121 	if (do_rewind && emusys->emuWriteMemState)
1122 	{
1123 		if (!rewind_mem)
1124 		{
1125 			rewind_mem = (char*)malloc(NUM_REWINDS * REWIND_SIZE);
1126 			num_rewind_states = next_rewind_state = 0;
1127 		}
1128 
1129 		if (!rewind_mem)
1130 		{
1131 			wxLogError(_("No memory for rewinding"));
1132 			wxGetApp().frame->Close(true);
1133 			return;
1134 		}
1135 
1136 		long resize;
1137 
1138 		if (!emusys->emuWriteMemState(&rewind_mem[REWIND_SIZE * next_rewind_state],
1139 		                              REWIND_SIZE, resize /* actual size */))
1140 			// if you see a lot of these, maybe increase REWIND_SIZE
1141 			wxLogInfo(_("Error writing rewind state"));
1142 		else
1143 		{
1144 			if (!num_rewind_states)
1145 			{
1146 				MainFrame* mf = wxGetApp().frame;
1147 				mf->cmd_enable |= CMDEN_REWIND;
1148 				mf->enable_menus();
1149 			}
1150 
1151 			if (num_rewind_states < NUM_REWINDS)
1152 				++num_rewind_states;
1153 
1154 			next_rewind_state = (next_rewind_state + 1) % NUM_REWINDS;
1155 		}
1156 
1157 		do_rewind = false;
1158 	}
1159 }
1160 
1161 // Note: keys will get stuck if they are released while window has no focus
1162 // can't really do anything about it, except scan for pressed keys on
1163 // activate events.  Maybe later.
1164 
1165 static uint32_t bmask[NUM_KEYS] =
1166 {
1167 	KEYM_UP, KEYM_DOWN, KEYM_LEFT, KEYM_RIGHT, KEYM_A, KEYM_B, KEYM_L, KEYM_R,
1168 	KEYM_SELECT, KEYM_START, KEYM_MOTION_UP, KEYM_MOTION_DOWN, KEYM_MOTION_LEFT,
1169 	KEYM_MOTION_RIGHT, KEYM_MOTION_IN, KEYM_MOTION_OUT, KEYM_AUTO_A, KEYM_AUTO_B, KEYM_SPEED, KEYM_CAPTURE,
1170 	KEYM_GS
1171 };
1172 
1173 static wxJoyKeyBinding_v keys_pressed;
1174 
process_key_press(bool down,int key,int mod,int joy=0)1175 static void process_key_press(bool down, int key, int mod, int joy = 0)
1176 {
1177 	// check if key is already pressed
1178 	int kpno;
1179 
1180 	for (kpno = 0; kpno < keys_pressed.size(); kpno++)
1181 		if (keys_pressed[kpno].key == key && keys_pressed[kpno].mod == mod &&
1182 		        keys_pressed[kpno].joy == joy)
1183 			break;
1184 
1185 	if (kpno < keys_pressed.size())
1186 	{
1187 		// double press is noop
1188 		if (down)
1189 			return;
1190 
1191 		// otherwise forget it
1192 		keys_pressed.erase(keys_pressed.begin() + kpno);
1193 	}
1194 	else
1195 	{
1196 		// double release is noop
1197 		if (!down)
1198 			return;
1199 
1200 		// otherwise remember it
1201 		// c++0x
1202 		// keys_pressed.push_back({ key, mod, joy });
1203 		wxJoyKeyBinding jb = { key, mod, joy };
1204 		keys_pressed.push_back(jb);
1205 	}
1206 
1207 	// find all game keys this is bound to
1208 	for (int i = 0; i < 4; i++)
1209 		for (int j = 0; j < NUM_KEYS; j++)
1210 		{
1211 			wxJoyKeyBinding_v &b = gopts.joykey_bindings[i][j];
1212 
1213 			for (int k = 0; k < b.size(); k++)
1214 				if (b[k].key == key && b[k].mod == mod && b[k].joy == joy)
1215 				{
1216 					if (down)
1217 						joypress[i] |= bmask[j];
1218 					else
1219 					{
1220 						// only release if no others pressed
1221 						int k2;
1222 
1223 						for (k2 = 0; k2 < b.size(); k2++)
1224 						{
1225 							if (k == k2 || (b[k2].key == key && b[k2].mod == mod &&
1226 							                b[k2].joy == joy))
1227 								continue;
1228 
1229 							for (kpno = 0; kpno < keys_pressed.size(); kpno++)
1230 								if (keys_pressed[kpno].key == b[k2].key &&
1231 								        keys_pressed[kpno].mod == b[k2].mod &&
1232 								        keys_pressed[kpno].joy == b[k2].joy)
1233 									break;
1234 
1235 							if (kpno < keys_pressed.size())
1236 								break;
1237 						}
1238 
1239 						if (k2 == b.size())
1240 							joypress[i] &= ~bmask[j];
1241 					}
1242 
1243 					break;
1244 				}
1245 		}
1246 }
1247 
OnKeyDown(wxKeyEvent & ev)1248 void GameArea::OnKeyDown(wxKeyEvent &ev)
1249 {
1250 	process_key_press(true, ev.GetKeyCode(), 0 /* ev.GetModifiers() */);
1251 	ev.Skip();  // process accelerators
1252 }
1253 
OnKeyUp(wxKeyEvent & ev)1254 void GameArea::OnKeyUp(wxKeyEvent &ev)
1255 {
1256 	process_key_press(false, ev.GetKeyCode(), 0 /* ev.GetModifiers() */);
1257 	ev.Skip();  // process accelerators
1258 }
1259 
OnSDLJoy(wxSDLJoyEvent & ev)1260 void GameArea::OnSDLJoy(wxSDLJoyEvent &ev)
1261 {
1262 	int key = ev.GetControlIndex();
1263 	int mod = wxJoyKeyTextCtrl::DigitalButton(ev);
1264 	int joy = ev.GetJoy() + 1;
1265 
1266 	// mutually exclusive key types unpress their opposite
1267 	if (mod == WXJB_AXIS_PLUS)
1268 	{
1269 		process_key_press(false, key, WXJB_AXIS_MINUS, joy);
1270 		process_key_press(ev.GetControlValue() != 0, key, mod, joy);
1271 	}
1272 	else if (mod == WXJB_AXIS_MINUS)
1273 	{
1274 		process_key_press(false, key, WXJB_AXIS_PLUS, joy);
1275 		process_key_press(ev.GetControlValue() != 0, key, mod, joy);
1276 	}
1277 	else if (mod >= WXJB_HAT_FIRST && mod <= WXJB_HAT_LAST)
1278 	{
1279 		if (ev.GetControlValue() == 0)
1280 		{
1281 			process_key_press(false, key, WXJB_HAT_N, joy);
1282 			process_key_press(false, key, WXJB_HAT_S, joy);
1283 			process_key_press(false, key, WXJB_HAT_E, joy);
1284 			process_key_press(false, key, WXJB_HAT_W, joy);
1285 		}
1286 		else
1287 		{
1288 			switch (mod)
1289 			{
1290 			case WXJB_HAT_N:
1291 				process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_N, joy);
1292 				process_key_press(false, key, WXJB_HAT_S, joy);
1293 				break;
1294 
1295 			case WXJB_HAT_S:
1296 				process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_S, joy);
1297 				process_key_press(false, key, WXJB_HAT_N, joy);
1298 				break;
1299 
1300 			case WXJB_HAT_E:
1301 				process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_E, joy);
1302 				process_key_press(false, key, WXJB_HAT_W, joy);
1303 				break;
1304 
1305 			case WXJB_HAT_W:
1306 				process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_W, joy);
1307 				process_key_press(false, key, WXJB_HAT_E, joy);
1308 				break;
1309 
1310 			case WXJB_HAT_NE:
1311 				process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_N, joy);
1312 				process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_E, joy);
1313 				process_key_press(false, key, WXJB_HAT_S, joy);
1314 				process_key_press(false, key, WXJB_HAT_W, joy);
1315 				break;
1316 
1317 			case WXJB_HAT_NW:
1318 				process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_N, joy);
1319 				process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_W, joy);
1320 				process_key_press(false, key, WXJB_HAT_S, joy);
1321 				process_key_press(false, key, WXJB_HAT_E, joy);
1322 				break;
1323 
1324 			case WXJB_HAT_SE:
1325 				process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_S, joy);
1326 				process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_E, joy);
1327 				process_key_press(false, key, WXJB_HAT_N, joy);
1328 				process_key_press(false, key, WXJB_HAT_W, joy);
1329 				break;
1330 
1331 			case WXJB_HAT_SW:
1332 				process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_S, joy);
1333 				process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_W, joy);
1334 				process_key_press(false, key, WXJB_HAT_N, joy);
1335 				process_key_press(false, key, WXJB_HAT_E, joy);
1336 				break;
1337 			}
1338 		}
1339 	}
1340 	else
1341 		process_key_press(ev.GetControlValue() != 0, key, mod, joy);
1342 }
1343 
BEGIN_EVENT_TABLE(GameArea,wxPanel)1344 BEGIN_EVENT_TABLE(GameArea, wxPanel)
1345 	EVT_IDLE(GameArea::OnIdle)
1346 	EVT_SDLJOY(GameArea::OnSDLJoy)
1347 	EVT_KEY_DOWN(GameArea::OnKeyDown)
1348 	EVT_KEY_UP(GameArea::OnKeyUp)
1349 	// FIXME: wxGTK does not generate motion events in MainFrame (not sure
1350 	// what to do about it)
1351 	EVT_MOUSE_EVENTS(GameArea::MouseEvent)
1352 END_EVENT_TABLE()
1353 
1354 IMPLEMENT_ABSTRACT_CLASS(DrawingPanel, wxEvtHandler)
1355 
1356 DrawingPanel::DrawingPanel(int _width, int _height) :
1357 	wxObject(), width(_width), height(_height), scale(1), todraw(0),
1358 	pixbuf1(0), pixbuf2(0), rpi(0), nthreads(0)
1359 {
1360 	memset(delta, 0xff, sizeof(delta));
1361 
1362 	if (gopts.filter == FF_PLUGIN)
1363 	{
1364 		do   // do { } while(0) so break; exits entire block
1365 		{
1366 			// could've also just used goto & a label, I guess
1367 			gopts.filter = FF_NONE; // preemptive in case of errors
1368 			systemColorDepth = 32;
1369 
1370 			if (gopts.filter_plugin.empty())
1371 				break;
1372 
1373 			wxFileName fpn(gopts.filter_plugin);
1374 			fpn.MakeAbsolute(wxStandardPaths::Get().GetPluginsDir());
1375 
1376 			if (!filt_plugin.Load(fpn.GetFullPath(), wxDL_VERBATIM | wxDL_NOW))
1377 				break;
1378 
1379 			RENDPLUG_GetInfo gi = (RENDPLUG_GetInfo)filt_plugin.GetSymbol(wxT("RenderPluginGetInfo"));
1380 
1381 			if (!gi)
1382 				break;
1383 
1384 			// need to be able to write to _rpi to set Output() and Flags
1385 			RENDER_PLUGIN_INFO* _rpi = gi();
1386 
1387 			// FIXME: maybe < RPI_VERSION, assuming future vers. back compat?
1388 			if (!_rpi || (_rpi->Flags & 0xff) != RPI_VERSION ||
1389 			        !(_rpi->Flags & (RPI_555_SUPP | RPI_888_SUPP)))
1390 				break;
1391 
1392 			_rpi->Flags &= ~RPI_565_SUPP;
1393 
1394 			if (_rpi->Flags & RPI_888_SUPP)
1395 			{
1396 				_rpi->Flags &= ~RPI_555_SUPP;
1397 				// FIXME: should this be 32 or 24?  No docs or sample source
1398 				systemColorDepth = 32;
1399 			}
1400 			else
1401 				systemColorDepth = 16;
1402 
1403 			if (!_rpi->Output)
1404 				// note that in actual kega fusion plugins, rpi->Output is
1405 				// unused (as is rpi->Handle)
1406 				_rpi->Output = (RENDPLUG_Output)filt_plugin.GetSymbol(wxT("RenderPluginOutput"));
1407 
1408 			scale = (_rpi->Flags & RPI_OUT_SCLMSK) >> RPI_OUT_SCLSH;
1409 			rpi = _rpi;
1410 			gopts.filter = FF_PLUGIN; // now that there is a valid plugin
1411 		}
1412 		while (0);
1413 	}
1414 	else
1415 	{
1416 		scale = builtin_ff_scale(gopts.filter);
1417 #define out_16 (systemColorDepth == 16)
1418 		systemColorDepth = 32;
1419 	}
1420 
1421 	// Intialize color tables
1422 	if (systemColorDepth == 32)
1423 	{
1424 #if wxBYTE_ORDER == wxLITTLE_ENDIAN
1425 		systemRedShift    = 3;
1426 		systemGreenShift  = 11;
1427 		systemBlueShift   = 19;
1428 		RGB_LOW_BITS_MASK = 0x00010101;
1429 #else
1430 		systemRedShift    = 27;
1431 		systemGreenShift  = 19;
1432 		systemBlueShift   = 11;
1433 		RGB_LOW_BITS_MASK = 0x01010100;
1434 #endif
1435 	}
1436 	else
1437 	{
1438 		// plugins expect RGB in native byte order
1439 		systemRedShift    = 10;
1440 		systemGreenShift  = 5;
1441 		systemBlueShift   = 0;
1442 		RGB_LOW_BITS_MASK = 0x0421;
1443 	}
1444 
1445 	// FIXME: should be "true" for GBA carts if lcd mode selected
1446 	// which means this needs to be re-run at pref change time
1447 	utilUpdateSystemColorMaps(false);
1448 }
1449 
PaintEv(wxPaintEvent & ev)1450 void DrawingPanel::PaintEv(wxPaintEvent &ev)
1451 {
1452 	wxPaintDC dc(GetWindow());
1453 
1454 	if (!todraw)
1455 	{
1456 		// since this is set for custom background, not drawing anything
1457 		// will cause garbage to be displayed, so draw a black area
1458 		wxCoord w, h;
1459 		dc.GetSize(&w, &h);
1460 		dc.SetPen(*wxBLACK_PEN);
1461 		dc.SetBrush(*wxBLACK_BRUSH);
1462 		dc.DrawRectangle(0, 0, w, h);
1463 		return;
1464 	}
1465 
1466 	DrawArea(dc);
1467 	DrawOSD(dc);
1468 }
1469 
1470 // In order to run filters in parallel, they have to run from the method of
1471 // a wxThread-derived class
1472 //   threads are only created once, if possible, to avoid thread creation
1473 //   overhead every frame; mutex+cond used to signal start and sem for end
1474 
1475 //   when threading, there _will_ be bands for any nontrivial filter
1476 //   usually the top and bottom line(s) of each region will look a little
1477 //   different.  To ease this a tiny bit, 2 extra lines are generated at
1478 //   the top of each region, so hopefully only the bottom of the previous
1479 //   region will look screwed up.  The only correct way to handle this
1480 //   is to draw an increasingly larger band around the seam until the
1481 //   seam cover matches a line in both top & bottom regions, and then apply
1482 //   cover to seam.   Way too much trouble for this, though.
1483 
1484 //   another issue to consider is whether or not these filters are thread-
1485 //   safe to begin with.  The built-in ones are verifyable (I didn't verify
1486 //   them, though).  The plugins cannot be verified.  However, unlike the MFC
1487 //   interface, I will allow them to be threaded at user's discretion.
1488 class FilterThread : public wxThread
1489 {
1490 public:
FilterThread()1491 	FilterThread() : wxThread(wxTHREAD_JOINABLE), lock(), sig(lock) {}
1492 
1493 	wxMutex lock;
1494 	wxCondition sig;
1495 	wxSemaphore* done;
1496 
1497 	// Set these params before running
1498 	int nthreads, threadno;
1499 	int width, height, scale;
1500 	const RENDER_PLUGIN_INFO* rpi;
1501 	u8* dst, *delta;
1502 
1503 	// set this param every round
1504 	// if NULL, end thread
1505 	u8* src;
1506 
Entry()1507 	ExitCode Entry()
1508 	{
1509 		// This is the band this thread will process
1510 		// threadno == -1 means just do a dummy round on the border line
1511 		int procy = height * threadno / nthreads;
1512 		height = height * (threadno + 1) / nthreads - procy;
1513 		int inbpp = systemColorDepth >> 3;
1514 		int inrb = systemColorDepth == 16 ? 2 : systemColorDepth == 24 ? 0 : 1;
1515 		int instride = (width + inrb) * inbpp;
1516 		int outbpp = out_16 ? 2 : systemColorDepth == 24 ? 3 : 4;
1517 		int outrb = systemColorDepth == 24 ? 0 : 4;
1518 		int outstride = width * outbpp * scale + outrb;
1519 		delta += instride * procy;
1520 		// + 1 for stupid top border
1521 		dst += outstride * (procy + 1) * scale;
1522 
1523 		while (nthreads == 1 || sig.Wait() == wxCOND_NO_ERROR)
1524 		{
1525 			if (!src /* && nthreads > 1 */)
1526 			{
1527 				lock.Unlock();
1528 				return 0;
1529 			}
1530 
1531 			// + 1 for stupid top border
1532 			src += instride;
1533 
1534 			// interframe blending filter
1535 			// definitely not thread safe by default
1536 			// added procy param to provide offset into accum buffers
1537 			if (gopts.ifb != IFB_NONE)
1538 			{
1539 				switch (gopts.ifb)
1540 				{
1541 				case IFB_SMART:
1542 					if (systemColorDepth == 16)
1543 						SmartIB(src, instride, width, procy, height);
1544 					else
1545 						SmartIB32(src, instride, width, procy, height);
1546 
1547 					break;
1548 
1549 				case IFB_MOTION_BLUR:
1550 
1551 					// FIXME: if(renderer == d3d/gl && filter == NONE) break;
1552 					if (systemColorDepth == 16)
1553 						MotionBlurIB(src, instride, width, procy, height);
1554 					else
1555 						MotionBlurIB32(src, instride, width, procy, height);
1556 
1557 					break;
1558 				}
1559 			}
1560 
1561 			if (gopts.filter == FF_NONE)
1562 			{
1563 				if (nthreads == 1)
1564 					return 0;
1565 
1566 				done->Post();
1567 				continue;
1568 			}
1569 
1570 			src += instride * procy;
1571 
1572 			// naturally, any of these with accumulation buffers like those of
1573 			// the IFB filters will screw up royally as well
1574 			switch (gopts.filter)
1575 			{
1576 			case FF_2XSAI:
1577 				_2xSaI32(src, instride, delta, dst, outstride, width, height);
1578 				break;
1579 
1580 			case FF_SUPER2XSAI:
1581 				Super2xSaI32(src, instride, delta, dst, outstride, width, height);
1582 				break;
1583 
1584 			case FF_SUPEREAGLE:
1585 				SuperEagle32(src, instride, delta, dst, outstride, width, height);
1586 				break;
1587 
1588 			case FF_PIXELATE:
1589 				Pixelate32(src, instride, delta, dst, outstride, width, height);
1590 				break;
1591 
1592 			case FF_ADVMAME:
1593 				AdMame2x32(src, instride, delta, dst, outstride, width, height);
1594 				break;
1595 
1596 			case FF_BILINEAR:
1597 				Bilinear32(src, instride, delta, dst, outstride, width, height);
1598 				break;
1599 
1600 			case FF_BILINEARPLUS:
1601 				BilinearPlus32(src, instride, delta, dst, outstride, width, height);
1602 				break;
1603 
1604 			case FF_SCANLINES:
1605 				Scanlines32(src, instride, delta, dst, outstride, width, height);
1606 				break;
1607 
1608 			case FF_TV:
1609 				ScanlinesTV32(src, instride, delta, dst, outstride, width, height);
1610 				break;
1611 
1612 			case FF_LQ2X:
1613 				lq2x32(src, instride, delta, dst, outstride, width, height);
1614 				break;
1615 
1616 			case FF_SIMPLE2X:
1617 				Simple2x32(src, instride, delta, dst, outstride, width, height);
1618 				break;
1619 
1620 			case FF_SIMPLE3X:
1621 				Simple3x32(src, instride, delta, dst, outstride, width, height);
1622 				break;
1623 
1624 			case FF_SIMPLE4X:
1625 				Simple4x32(src, instride, delta, dst, outstride, width, height);
1626 				break;
1627 
1628 			case FF_HQ2X:
1629 				hq2x32(src, instride, delta, dst, outstride, width, height);
1630 				break;
1631 
1632 			case FF_HQ3X:
1633 				hq3x32_32(src, instride, delta, dst, outstride, width, height);
1634 				break;
1635 
1636 			case FF_HQ4X:
1637 				hq4x32_32(src, instride, delta, dst, outstride, width, height);
1638 				break;
1639 
1640 			case FF_XBRZ2X:
1641 				xbrz2x32(src, instride, delta, dst, outstride, width, height);
1642 				break;
1643 
1644 			case FF_XBRZ3X:
1645 				xbrz3x32(src, instride, delta, dst, outstride, width, height);
1646 				break;
1647 
1648 			case FF_XBRZ4X:
1649 				xbrz4x32(src, instride, delta, dst, outstride, width, height);
1650 				break;
1651 
1652 			case FF_XBRZ5X:
1653 				xbrz5x32(src, instride, delta, dst, outstride, width, height);
1654 				break;
1655 
1656 			case FF_XBRZ6X:
1657 				xbrz6x32(src, instride, delta, dst, outstride, width, height);
1658 				break;
1659 
1660 			case FF_PLUGIN:
1661 				// MFC interface did not do plugins in parallel
1662 				// Probably because it's almost certain they carry state or do
1663 				// other non-thread-safe things
1664 				// But the user can always turn mt off of it's not working..
1665 				RENDER_PLUGIN_OUTP outdesc;
1666 				outdesc.Size = sizeof(outdesc);
1667 				outdesc.Flags = rpi->Flags;
1668 				outdesc.SrcPtr = src;
1669 				outdesc.SrcPitch = instride;
1670 				outdesc.SrcW = width;
1671 				// FIXME: win32 code adds to H, saying that frame isn't fully
1672 				// rendered otherwise
1673 				// I need to verify that statement before I go adding stuff that
1674 				// may make it crash.
1675 				outdesc.SrcH = height; // + scale / 2
1676 				outdesc.DstPtr = dst;
1677 				outdesc.DstPitch = outstride;
1678 				outdesc.DstW = width * scale;
1679 				// on the other hand, there is at least 1 line below, so I'll add
1680 				// that to dest in case safety checks in plugin use < instead of <=
1681 				outdesc.DstH = height * scale + 1; // + scale * (scale / 2)
1682 				rpi->Output(&outdesc);
1683 				break;
1684 
1685 			default:
1686 				break;
1687 			}
1688 
1689 			if (nthreads == 1)
1690 				return 0;
1691 
1692 			done->Post();
1693 			continue;
1694 		}
1695 	}
1696 };
1697 
DrawArea(u8 ** data)1698 void DrawingPanel::DrawArea(u8** data)
1699 {
1700 	// double-buffer buffer:
1701 	//   if filtering, this is filter output, retained for redraws
1702 	//   if not filtering, we still retain current image for redraws
1703 	int outbpp = out_16 ? 2 : systemColorDepth == 24 ? 3 : 4;
1704 	int outrb = systemColorDepth == 24 ? 0 : 4;
1705 	int outstride = width * outbpp * scale + outrb;
1706 
1707 	if (!pixbuf2)
1708 	{
1709 		int allocstride = outstride, alloch = height;
1710 
1711 		// gb may write borders, so allocate enough for them
1712 		if (width == GameArea::GBWidth && height == GameArea::GBHeight)
1713 		{
1714 			allocstride = GameArea::SGBWidth * outbpp * scale + outrb;
1715 			alloch = GameArea::SGBHeight;
1716 		}
1717 
1718 		pixbuf2 = (u8*)calloc(allocstride, (alloch + 2) * scale);
1719 	}
1720 
1721 	if (gopts.filter == FF_NONE)
1722 	{
1723 		todraw = *data;
1724 		// *data is assigned below, after old buf has been processed
1725 		pixbuf1 = pixbuf2;
1726 		pixbuf2 = todraw;
1727 	}
1728 	else
1729 		todraw = pixbuf2;
1730 
1731 	// FIXME: filters race condition?
1732 	gopts.max_threads = 1;
1733 
1734 	// First, apply filters, if applicable, in parallel, if enabled
1735 	if (gopts.filter != FF_NONE ||
1736 	        gopts.ifb != FF_NONE /* FIXME: && (gopts.ifb != FF_MOTION_BLUR || !renderer_can_motion_blur) */)
1737 	{
1738 		if (nthreads != gopts.max_threads)
1739 		{
1740 			if (nthreads)
1741 			{
1742 				if (nthreads > 1)
1743 					for (int i = 0; i < nthreads; i++)
1744 					{
1745 						threads[i].lock.Lock();
1746 						threads[i].src = NULL;
1747 						threads[i].sig.Signal();
1748 						threads[i].lock.Unlock();
1749 						threads[i].Wait();
1750 					}
1751 
1752 				delete[] threads;
1753 			}
1754 
1755 			nthreads = gopts.max_threads;
1756 			threads = new FilterThread[nthreads];
1757 			// first time around, no threading in order to avoid
1758 			// static initializer conflicts
1759 			threads[0].threadno = 0;
1760 			threads[0].nthreads = 1;
1761 			threads[0].width = width;
1762 			threads[0].height = height;
1763 			threads[0].scale = scale;
1764 			threads[0].src = *data;
1765 			threads[0].dst = todraw;
1766 			threads[0].delta = delta;
1767 			threads[0].rpi = rpi;
1768 			threads[0].Entry();
1769 
1770 			// go ahead and start the threads up, though
1771 			if (nthreads > 1)
1772 			{
1773 				for (int i = 0; i < nthreads; i++)
1774 				{
1775 					threads[i].threadno = i;
1776 					threads[i].nthreads = nthreads;
1777 					threads[i].width = width;
1778 					threads[i].height = height;
1779 					threads[i].scale = scale;
1780 					threads[i].dst = todraw;
1781 					threads[i].delta = delta;
1782 					threads[i].rpi = rpi;
1783 					threads[i].done = &filt_done;
1784 					threads[i].lock.Lock();
1785 					threads[i].Create();
1786 					threads[i].Run();
1787 				}
1788 			}
1789 		}
1790 		else if (nthreads == 1)
1791 		{
1792 			threads[0].threadno = 0;
1793 			threads[0].nthreads = 1;
1794 			threads[0].width = width;
1795 			threads[0].height = height;
1796 			threads[0].scale = scale;
1797 			threads[0].src = *data;
1798 			threads[0].dst = todraw;
1799 			threads[0].delta = delta;
1800 			threads[0].rpi = rpi;
1801 			threads[0].Entry();
1802 		}
1803 		else
1804 		{
1805 			for (int i = 0; i < nthreads; i++)
1806 			{
1807 				threads[i].lock.Lock();
1808 				threads[i].src = *data;
1809 				threads[i].sig.Signal();
1810 				threads[i].lock.Unlock();
1811 			}
1812 
1813 			for (int i = 0; i < nthreads; i++)
1814 				filt_done.Wait();
1815 		}
1816 	}
1817 
1818 	// swap buffers now that src has been processed
1819 	if (gopts.filter == FF_NONE)
1820 		*data = pixbuf1;
1821 
1822 	// draw OSD text old-style (directly into output buffer), if needed
1823 	// new style flickers too much, so we'll stick to this for now
1824 	if (wxGetApp().frame->IsFullScreen() || !gopts.statusbar)
1825 	{
1826 		GameArea* panel = wxGetApp().frame->GetPanel();
1827 
1828 		if (panel->osdstat.size())
1829 			drawText(todraw + outstride * (systemColorDepth != 24), outstride,
1830 			         10, 20, panel->osdstat.utf8_str(), showSpeedTransparent);
1831 
1832 		if (!disableStatusMessages && !panel->osdtext.empty())
1833 		{
1834 			if (systemGetClock() - panel->osdtime < OSD_TIME)
1835 			{
1836 				std::string message = ToString(panel->osdtext);
1837 				int linelen = (width * scale - 20) / 8;
1838 				int nlines = (message.length() + linelen - 1) / linelen;
1839 				int cury = height - 14 - nlines * 10;
1840 				char* ptr = const_cast<char*>(message.c_str());
1841 
1842 				while (nlines > 1)
1843 				{
1844 					char lchar = ptr[linelen];
1845 					ptr[linelen] = 0;
1846 					drawText(todraw + outstride * (systemColorDepth != 24),
1847 					         outstride, 10, cury, ptr,
1848 					         showSpeedTransparent);
1849 					cury += 10;
1850 					nlines--;
1851 					ptr += linelen;
1852 					*ptr = lchar;
1853 				}
1854 
1855 				drawText(todraw + outstride * (systemColorDepth != 24),
1856 				         outstride, 10, cury, ptr,
1857 				         showSpeedTransparent);
1858 			}
1859 			else
1860 				panel->osdtext.clear();
1861 		}
1862 	}
1863 
1864 	// next, draw the game
1865 	wxClientDC dc(GetWindow());
1866 	DrawArea(dc);
1867 
1868 	// finally, draw on-screen text using wx method, if possible
1869 	// this method flickers too much right now
1870 	if (0)
1871 		DrawOSD(dc);
1872 }
1873 
DrawOSD(wxWindowDC & dc)1874 void DrawingPanel::DrawOSD(wxWindowDC &dc)
1875 {
1876 	// draw OSD message, if available
1877 	// doing this here rather than using drawText() directly into the screen
1878 	// image gives us 2 advantages:
1879 	//   - non-ASCII text in messages
1880 	//   - message is always default font size, regardless of screen stretch
1881 	// and one known disadvantage:
1882 	//   - 3d renderers may overwrite text asynchronously
1883 	// Until I go through the trouble of making this render to a bitmap, and
1884 	// then using the bitmap as a texture for the 3d renderers or rendering
1885 	// directly into the output like DrawText, this is only enabled for
1886 	// non-3d renderers.
1887 	GameArea* panel = wxGetApp().frame->GetPanel();
1888 	dc.SetTextForeground(wxColour(255, 0, 0, showSpeedTransparent ? 128 : 255));
1889 	dc.SetTextBackground(wxColour(0, 0, 0, 0));
1890 	dc.SetUserScale(1.0, 1.0);
1891 
1892 	if (panel->osdstat.size())
1893 		dc.DrawText(panel->osdstat, 10, 20);
1894 
1895 	if (!panel->osdtext.empty())
1896 	{
1897 		if (systemGetClock() - panel->osdtime >= OSD_TIME)
1898 		{
1899 			panel->osdtext.clear();
1900 			wxGetApp().frame->PopStatusText();
1901 		}
1902 	}
1903 
1904 	if (!disableStatusMessages && !panel->osdtext.empty())
1905 	{
1906 		wxSize asz = dc.GetSize();
1907 		wxString msg = panel->osdtext;
1908 		int lw, lh;
1909 		int mlw = asz.GetWidth() - 20;
1910 		dc.GetTextExtent(msg, &lw, &lh);
1911 
1912 		if (lw <= mlw)
1913 			dc.DrawText(msg, 10, asz.GetHeight() - 16 - lh);
1914 		else
1915 		{
1916 			// Since font is likely proportional, the only way to
1917 			// find amt of text that will fit on a line is to search
1918 			wxArrayInt llen; // length of each line, in chars
1919 
1920 			for (int off = 0; off < msg.size();)
1921 			{
1922 				// One way would be to bsearch on GetTextExtent() looking for
1923 				// best fit.
1924 				// Another would be to use GetPartialTextExtents and search
1925 				// the resulting array.
1926 				// GetPartialTextExtents may be inaccurate, but calling it
1927 				// once per line may be more efficient than calling
1928 				// GetTextExtent log2(remaining_len) times per line.
1929 #if 1
1930 				wxArrayInt clen;
1931 				dc.GetPartialTextExtents(msg.substr(off), clen);
1932 #endif
1933 				int lenl = 0, lenh = msg.size() - off - 1, len;
1934 
1935 				do
1936 				{
1937 					len = (lenl + lenh) / 2;
1938 #if 1
1939 					lw = clen[len];
1940 #else
1941 					dc.GetTextExtent(msg.substr(off, len), &lw, NULL);
1942 #endif
1943 
1944 					if (lw > mlw)
1945 						lenh = len - 1;
1946 					else if (lw < mlw)
1947 						lenl = len + 1;
1948 					else
1949 						break;
1950 				}
1951 				while (lenh >= lenl);
1952 
1953 				if (lw <= mlw)
1954 					len++;
1955 
1956 				off += len;
1957 				llen.push_back(len);
1958 			}
1959 
1960 			int nlines = llen.size();
1961 			int cury = asz.GetHeight() - 14 - nlines * (lh + 2);
1962 
1963 			for (int off = 0, line = 0; line < nlines; off += llen[line], line++, cury += lh + 2)
1964 				dc.DrawText(msg.substr(off, llen[line]), 10, cury);
1965 		}
1966 	}
1967 }
1968 
~DrawingPanel()1969 DrawingPanel::~DrawingPanel()
1970 {
1971 	// pixbuf1 freed by emulator
1972 	if (pixbuf2)
1973 		free(pixbuf2);
1974 
1975 	InterframeCleanup();
1976 
1977 	if (nthreads)
1978 	{
1979 		if (nthreads > 1)
1980 			for (int i = 0; i < nthreads; i++)
1981 			{
1982 				threads[i].lock.Lock();
1983 				threads[i].src = NULL;
1984 				threads[i].sig.Signal();
1985 				threads[i].lock.Unlock();
1986 				threads[i].Wait();
1987 			}
1988 
1989 		delete[] threads;
1990 	}
1991 }
1992 
IMPLEMENT_CLASS2(BasicDrawingPanel,DrawingPanel,wxPanel)1993 IMPLEMENT_CLASS2(BasicDrawingPanel, DrawingPanel, wxPanel)
1994 
1995 BEGIN_EVENT_TABLE(BasicDrawingPanel, wxPanel)
1996 	EVT_PAINT(BasicDrawingPanel::PaintEv2)
1997 END_EVENT_TABLE()
1998 
1999 BasicDrawingPanel::BasicDrawingPanel(wxWindow* parent, int _width, int _height)
2000 	: wxPanel(parent, wxID_ANY, wxPoint(0, 0), parent->GetSize(),
2001 	          wxFULL_REPAINT_ON_RESIZE), DrawingPanel(_width, _height)
2002 {
2003 	// wxImage is 24-bit RGB, so 24-bit is preferred.  Filters require
2004 	// 16 or 32, though
2005 	if (gopts.filter == FF_NONE && gopts.ifb == IFB_NONE)
2006 		// changing from 32 to 24 does not require regenerating color tables
2007 		systemColorDepth = 24;
2008 }
2009 
DrawArea(wxWindowDC & dc)2010 void BasicDrawingPanel::DrawArea(wxWindowDC &dc)
2011 {
2012 	wxBitmap* bm;
2013 
2014 	if (systemColorDepth == 24)
2015 	{
2016 		// never scaled, no borders, no transformations needed
2017 		wxImage im(width, height, todraw, true);
2018 		bm = new wxBitmap(im);
2019 	}
2020 	else if (out_16)
2021 	{
2022 		// scaled by filters, top/right borders, transform to 24-bit
2023 		wxImage im(width * scale, height * scale, false);
2024 		u16* src = (u16*)todraw + (width + 2) * scale;  // skip top border
2025 		u8* dst = im.GetData();
2026 
2027 		for (int y = 0; y < height * scale; y++)
2028 		{
2029 			for (int x = 0; x < width * scale; x++, src++)
2030 			{
2031 				*dst++ = ((*src >> systemRedShift) & 0x1f) << 3;
2032 				*dst++ = ((*src >> systemGreenShift) & 0x1f) << 3;
2033 				*dst++ = ((*src >> systemBlueShift) & 0x1f) << 3;
2034 			}
2035 
2036 			src += 2; // skip rhs border
2037 		}
2038 
2039 		bm = new wxBitmap(im);
2040 	}
2041 	else     // 32-bit
2042 	{
2043 		// scaled by filters, top/right borders, transform to 24-bit
2044 		wxImage im(width * scale, height * scale, false);
2045 		u32* src = (u32*)todraw + (width + 1) * scale;  // skip top border
2046 		u8* dst = im.GetData();
2047 
2048 		for (int y = 0; y < height * scale; y++)
2049 		{
2050 			for (int x = 0; x < width * scale; x++, src++)
2051 			{
2052 				*dst++ = *src >> (systemRedShift - 3);
2053 				*dst++ = *src >> (systemGreenShift - 3);
2054 				*dst++ = *src >> (systemBlueShift - 3);
2055 			}
2056 
2057 			++src; // skip rhs border
2058 		}
2059 
2060 		bm = new wxBitmap(im);
2061 	}
2062 
2063 	double sx, sy;
2064 	int w, h;
2065 	GetClientSize(&w, &h);
2066 	sx = (double)w / (double)(width * scale);
2067 	sy = (double)h / (double)(height * scale);
2068 	dc.SetUserScale(sx, sy);
2069 	dc.DrawBitmap(*bm, 0, 0);
2070 	delete bm;
2071 }
2072 
2073 #ifndef NO_OGL
2074 // following 3 for vsync
2075 #ifdef __WXMAC__
2076 #include <OpenGL/OpenGL.h>
2077 #endif
2078 #ifdef __WXGTK__  // should actually check for X11, but GTK implies X11
2079 #include <GL/glx.h>
2080 #endif
2081 #ifdef __WXMSW__
2082 #include <GL/glext.h>
2083 #endif
2084 
2085 IMPLEMENT_CLASS2(GLDrawingPanel, DrawingPanel, wxGLCanvas)
2086 
2087 // this would be easier in 2.9
2088 BEGIN_EVENT_TABLE(GLDrawingPanel, wxGLCanvas)
2089 	EVT_PAINT(GLDrawingPanel::PaintEv2)
2090 	EVT_SIZE(GLDrawingPanel::OnSize)
2091 END_EVENT_TABLE()
2092 
2093 // This is supposed to be the default, but DOUBLEBUFFER doesn't seem to be
2094 // turned on by default for wxGTK.
2095 static int glopts[] =
2096 {
2097 	WX_GL_RGBA, WX_GL_DOUBLEBUFFER, 0
2098 };
2099 
2100 #if wxCHECK_VERSION(2,9,0) || !defined(__WXMAC__)
2101 #define glc wxGLCanvas
2102 #else
2103 // shuffled parms for 2.9 indicates non-auto glcontext
2104 // before 2.9, wxMAC does not have this (but wxGTK & wxMSW do)
2105 #define glc(a,b,c,d,e,f) wxGLCanvas(a, b, d, e, f, wxEmptyString, c)
2106 #endif
2107 
GLDrawingPanel(wxWindow * parent,int _width,int _height)2108 GLDrawingPanel::GLDrawingPanel(wxWindow* parent, int _width, int _height) :
2109 	glc(parent, wxID_ANY, glopts, wxPoint(0, 0), parent->GetSize(),
2110 	    wxFULL_REPAINT_ON_RESIZE), DrawingPanel(_width, _height),
2111 	did_init(false)
2112 #if wxCHECK_VERSION(2,9,0) || !defined(__WXMAC__)
2113 	, ctx(this)
2114 #endif
2115 {
2116 }
2117 
~GLDrawingPanel()2118 GLDrawingPanel::~GLDrawingPanel()
2119 {
2120 #if 0
2121 
2122 	// this should be automatically deleted w/ context
2123 	// it's also unsafe if panel no longer displayed
2124 	if (did_init)
2125 	{
2126 #if wxCHECK_VERSION(2,9,0) || !defined(__WXMAC__)
2127 		SetContext(ctx);
2128 #else
2129 		SetContext();
2130 #endif
2131 		glDeleteLists(vlist, 1);
2132 		glDeleteTextures(1, &texid);
2133 	}
2134 
2135 #endif
2136 }
2137 
Init()2138 void GLDrawingPanel::Init()
2139 {
2140 	// taken from GTK front end almost verbatim
2141 	glDisable(GL_CULL_FACE);
2142 	glEnable(GL_TEXTURE_2D);
2143 	glMatrixMode(GL_PROJECTION);
2144 	glLoadIdentity();
2145 	glOrtho(0.0, 1.0, 1.0, 0.0, 0.0, 1.0);
2146 	glMatrixMode(GL_MODELVIEW);
2147 	glLoadIdentity();
2148 	vlist = glGenLists(1);
2149 	glNewList(vlist, GL_COMPILE);
2150 	glBegin(GL_TRIANGLE_STRIP);
2151 	glTexCoord2f(0.0, 0.0);
2152 	glVertex3i(0, 0, 0);
2153 	glTexCoord2f(1.0, 0.0);
2154 	glVertex3i(1, 0, 0);
2155 	glTexCoord2f(0.0, 1.0);
2156 	glVertex3i(0, 1, 0);
2157 	glTexCoord2f(1.0, 1.0);
2158 	glVertex3i(1, 1, 0);
2159 	glEnd();
2160 	glEndList();
2161 	glGenTextures(1, &texid);
2162 	glBindTexture(GL_TEXTURE_2D, texid);
2163 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
2164 	                gopts.bilinear ? GL_LINEAR : GL_NEAREST);
2165 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
2166 	                gopts.bilinear ? GL_LINEAR : GL_NEAREST);
2167 #define int_fmt out_16 ? GL_RGB5 : GL_RGB
2168 #define tex_fmt out_16 ? GL_BGRA : GL_RGBA, \
2169                 out_16 ? GL_UNSIGNED_SHORT_1_5_5_5_REV : GL_UNSIGNED_BYTE
2170 #if 0
2171 	texsize = width > height ? width : height;
2172 	texsize *= scale;
2173 	// texsize = 1 << ffs(texsize);
2174 	texsize = texsize | (texsize >> 1);
2175 	texsize = texsize | (texsize >> 2);
2176 	texsize = texsize | (texsize >> 4);
2177 	texsize = texsize | (texsize >> 8);
2178 	texsize = (texsize >> 1) + 1;
2179 	glTexImage2D(GL_TEXTURE_2D, 0, int_fmt, texsize, texsize, 0, tex_fmt, NULL);
2180 #else
2181 	// but really, most cards support non-p2 and rect
2182 	// if not, use cairo or wx renderer
2183 	glTexImage2D(GL_TEXTURE_2D, 0, int_fmt, width * scale, height * scale, 0, tex_fmt, NULL);
2184 #endif
2185 	glClearColor(0.0, 0.0, 0.0, 1.0);
2186 	// non-portable vsync code
2187 #if defined(__WXGTK__) && defined(GLX_SGI_swap_control)
2188 	static PFNGLXSWAPINTERVALSGIPROC si = NULL;
2189 
2190 	if (!si)
2191 		si = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddress((const GLubyte*)"glxSwapIntervalSGI");
2192 
2193 	if (si)
2194 		si(vsync);
2195 
2196 #else
2197 #if defined(__WXMSW__) && defined(WGL_EXT_swap_control)
2198 	static PFNWGLSWAPINTERVALEXTPROC si = NULL;
2199 
2200 	if (!si)
2201 		si = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
2202 
2203 	if (si)
2204 		si(vsync);
2205 
2206 #else
2207 #ifdef __WXMAC__
2208 	int swap_interval = vsync ? 1 : 0;
2209 	CGLContextObj cgl_context = CGLGetCurrentContext();
2210 	CGLSetParameter(cgl_context, kCGLCPSwapInterval, &swap_interval);
2211 #else
2212 //#warning no vsync support on this platform
2213 #endif
2214 #endif
2215 #endif
2216 	did_init = true;
2217 }
2218 
DrawArea(wxWindowDC & dc)2219 void GLDrawingPanel::DrawArea(wxWindowDC &dc)
2220 {
2221 #if wxCHECK_VERSION(2,9,0) || !defined(__WXMAC__)
2222 	SetCurrent(ctx);
2223 #else
2224 	SetCurrent();
2225 #endif
2226 
2227 	if (!did_init)
2228 		Init();
2229 
2230 	if (todraw)
2231 	{
2232 		int rowlen = width * scale + (out_16 ? 2 : 1);
2233 		glPixelStorei(GL_UNPACK_ROW_LENGTH, rowlen);
2234 #if wxBYTE_ORDER == wxBIG_ENDIAN
2235 
2236 		// FIXME: is this necessary?
2237 		if (out_16)
2238 			glPixelStorei(GL_UNPACK_SWAP_BYTES, GL_TRUE);
2239 
2240 #endif
2241 		glTexImage2D(GL_TEXTURE_2D, 0, int_fmt, width * scale, height * scale,
2242 		             0, tex_fmt, todraw + rowlen * (out_16 ? 2 : 4) * scale);
2243 		glCallList(vlist);
2244 	}
2245 	else
2246 		glClear(GL_COLOR_BUFFER_BIT);
2247 
2248 	SwapBuffers();
2249 }
2250 
OnSize(wxSizeEvent & ev)2251 void GLDrawingPanel::OnSize(wxSizeEvent &ev)
2252 {
2253 	if (!did_init)
2254 		return;
2255 
2256 	int w, h;
2257 	GetClientSize(&w, &h);
2258 #if wxCHECK_VERSION(2,9,0) || !defined(__WXMAC__)
2259 	SetCurrent(ctx);
2260 #else
2261 	SetCurrent();
2262 #endif
2263 	glViewport(0, 0, w, h);
2264 	ev.Skip(); // propagate to parent
2265 }
2266 #endif
2267 
2268 #ifndef NO_CAIRO
2269 
IMPLEMENT_CLASS(CairoDrawingPanel,DrawingPanel)2270 IMPLEMENT_CLASS(CairoDrawingPanel, DrawingPanel)
2271 
2272 BEGIN_EVENT_TABLE(CairoDrawingPanel, wxPanel)
2273 	EVT_PAINT(CairoDrawingPanel::PaintEv2)
2274 END_EVENT_TABLE()
2275 
2276 CairoDrawingPanel::CairoDrawingPanel(wxWindow* parent, int _width, int _height)
2277 	: wxPanel(parent, wxID_ANY, wxPoint(0, 0), parent->GetSize(),
2278 	          wxFULL_REPAINT_ON_RESIZE), DrawingPanel(_width, _height)
2279 {
2280 	conv_surf = NULL;
2281 
2282 	// Intialize color tables in reverse order from default
2283 	// probably doesn't help mmx hq3x/hq4x
2284 	if (systemColorDepth == 32)
2285 	{
2286 #if wxBYTE_ORDER == wxLITTLE_ENDIAN
2287 		systemBlueShift    = 3;
2288 		systemRedShift   = 19;
2289 #else
2290 		systemBlueShift    = 27;
2291 		systemRedShift   = 11;
2292 #endif
2293 	}
2294 
2295 	// FIXME: should be "true" for GBA carts if lcd mode selected
2296 	utilUpdateSystemColorMaps(false);
2297 }
2298 
~CairoDrawingPanel()2299 CairoDrawingPanel::~CairoDrawingPanel()
2300 {
2301 	if (conv_surf)
2302 		cairo_surface_destroy(conv_surf);
2303 }
2304 
2305 #include <wx/graphics.h>
2306 #ifdef __WXMSW__
2307 #include <cairo-win32.h>
2308 #include <gdiplus.h>
2309 #endif
2310 #if defined(__WXMAC__) && wxMAC_USE_CORE_GRAPHICS
2311 #include <cairo-quartz.h>
2312 #endif
2313 
DrawArea(wxWindowDC & dc)2314 void CairoDrawingPanel::DrawArea(wxWindowDC &dc)
2315 {
2316 	cairo_t* cr;
2317 	wxGraphicsContext* gc = wxGraphicsContext::Create(dc);
2318 #ifdef __WXMSW__
2319 	// not sure why this is so slow
2320 	// doing this only once in constructor and resize handler doesn't seem
2321 	// to help, and may be unsafe
2322 	Gdiplus::Graphics* gr = (Gdiplus::Graphics*)gc->GetNativeContext();
2323 	cairo_surface_t* s = cairo_win32_surface_create(gr->GetHDC());
2324 	cr = cairo_create(s);
2325 	cairo_surface_destroy(s);
2326 #else
2327 #ifdef __WXGTK__
2328 	cr = cairo_reference((cairo_t*)gc->GetNativeContext());
2329 #else
2330 #if defined(__WXMAC__) && wxMAC_USE_CORE_GRAPHICS
2331 	CGContextRef c = static_cast<CGContextRef>(gc->GetNativeContext());
2332 	cairo_surface_t* s = cairo_quartz_surface_create_for_cg_context(c, width, height);
2333 	cr = cairo_create(s);
2334 	cairo_surface_destroy(s);
2335 #else
2336 #error Cairo rendering is not supported on this platform
2337 #endif
2338 #endif
2339 #endif
2340 	cairo_surface_t* surf;
2341 
2342 	if (!out_16)
2343 		surf = cairo_image_surface_create_for_data(todraw + 4 * width,
2344 		        CAIRO_FORMAT_RGB24,
2345 		        width, height,
2346 		        4 * (width + 1));
2347 	else
2348 	{
2349 		if (!conv_surf)
2350 			conv_surf = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
2351 			                                       width * scale,
2352 			                                       height * scale);
2353 
2354 		if (!conv_surf)
2355 		{
2356 			wxLogError(_("Cannot create conversion buffer"));
2357 			wxGetApp().frame->Close(true);
2358 		}
2359 
2360 		surf = cairo_surface_reference(conv_surf);
2361 		u16* src = (u16*)todraw + (width + 2) * scale;  // skip top border
2362 		u32* dst = (u32*)cairo_image_surface_get_data(surf);
2363 
2364 		for (int y = 0; y < height * scale; y++)
2365 		{
2366 			for (int x = 0; x < width * scale; x++, src++)
2367 			{
2368 				*dst++ = (((*src >> systemRedShift) & 0x1f) << 19) |
2369 				         (((*src >> systemGreenShift) & 0x1f) << 11) |
2370 				         (((*src >> systemBlueShift) & 0x1f) << 3);
2371 			}
2372 
2373 			src += 2; // skip rhs border
2374 		}
2375 	}
2376 
2377 	cairo_pattern_t* pat = cairo_pattern_create_for_surface(surf);
2378 	// GOOD is "similar to" bilinear, and FAST is "similar to" nearest
2379 	// could also just use BILINEAR and NEAREST directly, I suppose
2380 	cairo_pattern_set_filter(pat, gopts.bilinear ? CAIRO_FILTER_GOOD : CAIRO_FILTER_FAST);
2381 	double sx, sy;
2382 	int w, h;
2383 	GetClientSize(&w, &h);
2384 	sx = (double)width / (double)w;
2385 	sy = (double)height / (double)h;
2386 	cairo_matrix_t mat;
2387 	cairo_matrix_init_scale(&mat, sx, sy);
2388 	cairo_pattern_set_matrix(pat, &mat);
2389 	cairo_set_source(cr, pat);
2390 	cairo_paint(cr);
2391 	cairo_pattern_destroy(pat);
2392 	cairo_surface_destroy(surf);
2393 	cairo_destroy(cr);
2394 	delete gc;
2395 }
2396 #endif
2397 
2398 #if defined(__WXMSW__) && !defined(NO_D3D)
2399 #define DIRECT3D_VERSION 0x0900
2400 #include <d3d9.h>
2401 //#include <Dxerr.h>
2402 
IMPLEMENT_CLASS(DXDrawingPanel,DrawingPanel)2403 IMPLEMENT_CLASS(DXDrawingPanel, DrawingPanel)
2404 
2405 BEGIN_EVENT_TABLE(DXDrawingPanel, wxPanel)
2406 	EVT_PAINT(DXDrawingPanel::PaintEv2)
2407 END_EVENT_TABLE()
2408 
2409 DXDrawingPanel::DXDrawingPanel(wxWindow* parent, int _width, int _height)
2410 	: wxPanel(parent, wxID_ANY, wxPoint(0, 0), parent->GetSize(),
2411 	          wxFULL_REPAINT_ON_RESIZE), DrawingPanel(_width, _height)
2412 {
2413 	// FIXME: implement
2414 }
2415 
DrawArea(wxWindowDC & dc)2416 void DXDrawingPanel::DrawArea(wxWindowDC &dc)
2417 {
2418 	// FIXME: implement
2419 }
2420 #endif
2421 
2422 #ifndef NO_FFMPEG
media_err(MediaRet ret)2423 static const wxChar* media_err(MediaRet ret)
2424 {
2425 	switch (ret)
2426 	{
2427 	case MRET_OK:
2428 		return wxT("");
2429 
2430 	case MRET_ERR_NOMEM:
2431 		return _("memory allocation error");
2432 
2433 	case MRET_ERR_NOCODEC:
2434 		return _("error initializing codec");
2435 
2436 	case MRET_ERR_FERR:
2437 		return _("error writing to output file");
2438 
2439 	case MRET_ERR_FMTGUESS:
2440 		return _("can't guess output format from file name");
2441 
2442 	default:
2443 //    case MRET_ERR_RECORDING:
2444 //    case MRET_ERR_BUFSIZE:
2445 		return _("programming error; aborting!");
2446 	}
2447 }
2448 
StartVidRecording(const wxString & fname)2449 void GameArea::StartVidRecording(const wxString &fname)
2450 {
2451 	// auto-conversion of wxCharBuffer to const char * seems broken
2452 	// so save underlying wxCharBuffer (or create one of none is used)
2453 	wxCharBuffer fnb(fname.mb_fn_str());
2454 	MediaRet ret;
2455 
2456 	if ((ret = vid_rec.Record(fnb.data(), basic_width, basic_height,
2457 	                          systemColorDepth)) != MRET_OK)
2458 		wxLogError(_("Unable to begin recording to %s (%s)"), fname.c_str(),
2459 		           media_err(ret));
2460 	else
2461 	{
2462 		MainFrame* mf = wxGetApp().frame;
2463 		mf->cmd_enable &= ~(CMDEN_NVREC | CMDEN_NREC_ANY);
2464 		mf->cmd_enable |= CMDEN_VREC;
2465 		mf->enable_menus();
2466 	}
2467 }
2468 
StopVidRecording()2469 void GameArea::StopVidRecording()
2470 {
2471 	vid_rec.Stop();
2472 	MainFrame* mf = wxGetApp().frame;
2473 	mf->cmd_enable &= ~CMDEN_VREC;
2474 	mf->cmd_enable |= CMDEN_NVREC;
2475 
2476 	if (!(mf->cmd_enable & (CMDEN_VREC | CMDEN_SREC)))
2477 		mf->cmd_enable |= CMDEN_NREC_ANY;
2478 
2479 	mf->enable_menus();
2480 }
2481 
StartSoundRecording(const wxString & fname)2482 void GameArea::StartSoundRecording(const wxString &fname)
2483 {
2484 	// auto-conversion of wxCharBuffer to const char * seems broken
2485 	// so save underlying wxCharBuffer (or create one of none is used)
2486 	wxCharBuffer fnb(fname.mb_fn_str());
2487 	MediaRet ret;
2488 
2489 	if ((ret = snd_rec.Record(fnb.data())) != MRET_OK)
2490 		wxLogError(_("Unable to begin recording to %s (%s)"), fname.c_str(),
2491 		           media_err(ret));
2492 	else
2493 	{
2494 		MainFrame* mf = wxGetApp().frame;
2495 		mf->cmd_enable &= ~(CMDEN_NSREC | CMDEN_NREC_ANY);
2496 		mf->cmd_enable |= CMDEN_SREC;
2497 		mf->enable_menus();
2498 	}
2499 }
2500 
StopSoundRecording()2501 void GameArea::StopSoundRecording()
2502 {
2503 	snd_rec.Stop();
2504 	MainFrame* mf = wxGetApp().frame;
2505 	mf->cmd_enable &= ~CMDEN_SREC;
2506 	mf->cmd_enable |= CMDEN_NSREC;
2507 
2508 	if (!(mf->cmd_enable & (CMDEN_VREC | CMDEN_SREC)))
2509 		mf->cmd_enable |= CMDEN_NREC_ANY;
2510 
2511 	mf->enable_menus();
2512 }
2513 
AddFrame(const u16 * data,int length)2514 void GameArea::AddFrame(const u16* data, int length)
2515 {
2516 	MediaRet ret;
2517 
2518 	if ((ret = vid_rec.AddFrame(data)) != MRET_OK)
2519 	{
2520 		wxLogError(_("Error in audio/video recording (%s); aborting"),
2521 		           media_err(ret));
2522 		vid_rec.Stop();
2523 	}
2524 
2525 	if ((ret = snd_rec.AddFrame(data)) != MRET_OK)
2526 	{
2527 		wxLogError(_("Error in audio recording (%s); aborting"), media_err(ret));
2528 		snd_rec.Stop();
2529 	}
2530 }
2531 
AddFrame(const u8 * data)2532 void GameArea::AddFrame(const u8* data)
2533 {
2534 	MediaRet ret;
2535 
2536 	if ((ret = vid_rec.AddFrame(data)) != MRET_OK)
2537 	{
2538 		wxLogError(_("Error in video recording (%s); aborting"), media_err(ret));
2539 		vid_rec.Stop();
2540 	}
2541 }
2542 #endif
2543 
ShowPointer()2544 void GameArea::ShowPointer()
2545 {
2546 	if (fullscreen)
2547 		return;
2548 
2549 	mouse_active_time = systemGetClock();
2550 
2551 	if (!pointer_blanked)
2552 		return;
2553 
2554 	pointer_blanked = false;
2555 	SetCursor(wxNullCursor);
2556 
2557 	if (panel)
2558 		panel->GetWindow()->SetCursor(wxNullCursor);
2559 }
2560 
HidePointer()2561 void GameArea::HidePointer()
2562 {
2563 	if (pointer_blanked)
2564 		return;
2565 
2566 	// FIXME: make time configurable
2567 	if (fullscreen || (systemGetClock() - mouse_active_time) > 3000)
2568 	{
2569 		pointer_blanked = true;
2570 		SetCursor(wxCursor(wxCURSOR_BLANK));
2571 
2572 		// wxGTK requires that subwindows get the cursor as well
2573 		if (panel)
2574 			panel->GetWindow()->SetCursor(wxCursor(wxCURSOR_BLANK));
2575 	}
2576 }
2577