ft2-clone

Fasttracker 2 clone
Log | Files | Refs | README | LICENSE

ft2_events.c (12381B)


      1 // for finding memory leaks in debug mode with Visual Studio
      2 #if defined _DEBUG && defined _MSC_VER
      3 #include <crtdbg.h>
      4 #endif
      5 
      6 #ifdef _WIN32
      7 #define WIN32_MEAN_AND_LEAN
      8 #include <windows.h>
      9 #include <SDL2/SDL_syswm.h>
     10 #else
     11 #include <limits.h>
     12 #include <signal.h>
     13 #include <unistd.h> // chdir()
     14 #endif
     15 #include <stdio.h>
     16 #include <sys/stat.h>
     17 #include "ft2_header.h"
     18 #include "ft2_config.h"
     19 #include "ft2_diskop.h"
     20 #include "ft2_module_loader.h"
     21 #include "ft2_module_saver.h"
     22 #include "ft2_sample_loader.h"
     23 #include "ft2_mouse.h"
     24 #include "ft2_midi.h"
     25 #include "ft2_video.h"
     26 #include "ft2_trim.h"
     27 #include "ft2_inst_ed.h"
     28 #include "ft2_sampling.h"
     29 #include "ft2_textboxes.h"
     30 #include "ft2_sysreqs.h"
     31 #include "ft2_keyboard.h"
     32 #include "ft2_sample_ed.h"
     33 #include "ft2_sample_ed_features.h"
     34 #include "ft2_structs.h"
     35 
     36 #define CRASH_TEXT "Oh no! The Fasttracker II clone has crashed...\nA backup of the song was hopefully " \
     37                    "saved to the current module directory.\n\nPlease report this bug if you can.\n" \
     38                    "Try to mention what you did before the crash happened.\n" \
     39                    "My email is on the bottom of https://16-bits.org"
     40 
     41 static bool backupMadeAfterCrash;
     42 
     43 #ifdef _WIN32
     44 #define SYSMSG_FILE_ARG (WM_USER+1)
     45 #define ARGV_SHARED_MEM_MAX_LEN ((PATH_MAX+1) * sizeof (WCHAR))
     46 #define SHARED_HWND_NAME TEXT("Local\\FT2CloneHwnd")
     47 #define SHARED_FILENAME TEXT("Local\\FT2CloneFilename")
     48 static HWND hWnd;
     49 static HANDLE oneInstHandle, hMapFile;
     50 static LPCTSTR sharedMemBuf;
     51 #endif
     52 
     53 static void handleSDLEvents(void);
     54 
     55 void readInput(void)
     56 {
     57 	readMouseXY();
     58 	readKeyModifiers();
     59 	setSyncedReplayerVars();
     60 	handleSDLEvents();
     61 }
     62 
     63 void handleThreadEvents(void)
     64 {
     65 	if (okBoxData.active)
     66 	{
     67 		okBoxData.returnData = okBox(okBoxData.type, okBoxData.headline, okBoxData.text, okBoxData.checkBoxCallback);
     68 		okBoxData.active = false;
     69 	}
     70 }
     71 
     72 void handleEvents(void)
     73 {
     74 #ifdef HAS_MIDI
     75 	// called after MIDI has been initialized
     76 	if (midi.rescanDevicesFlag)
     77 	{
     78 		midi.rescanDevicesFlag = false;
     79 
     80 		rescanMidiInputDevices();
     81 		if (ui.configScreenShown && editor.currConfigScreen == CONFIG_SCREEN_MIDI_INPUT)
     82 			drawMidiInputList();
     83 	}
     84 #endif
     85 
     86 	if (editor.trimThreadWasDone)
     87 	{
     88 		editor.trimThreadWasDone = false;
     89 		trimThreadDone();
     90 	}
     91 
     92 	if (editor.updateCurSmp)
     93 	{
     94 		editor.updateCurSmp = false;
     95 
     96 		updateNewInstrument();
     97 		updateNewSample();
     98 
     99 		diskOpSetFilename(DISKOP_ITEM_SAMPLE, editor.tmpFilenameU);
    100 
    101 		removeSampleIsLoadingFlag();
    102 		setMouseBusy(false);
    103 	}
    104 
    105 	if (editor.updateCurInstr)
    106 	{
    107 		editor.updateCurInstr = false;
    108 
    109 		updateNewInstrument();
    110 		updateNewSample();
    111 
    112 		diskOpSetFilename(DISKOP_ITEM_INSTR, editor.tmpInstrFilenameU);
    113 		setMouseBusy(false);
    114 	}
    115 
    116 	// some Disk Op. stuff
    117 
    118 	if (editor.diskOpReadDir)
    119 	{
    120 		editor.diskOpReadDir = false;
    121 		diskOp_StartDirReadThread();
    122 	}
    123 
    124 	if (editor.diskOpReadDone)
    125 	{
    126 		editor.diskOpReadDone = false;
    127 		if (ui.diskOpShown)
    128 			diskOp_DrawDirectory();
    129 	}
    130 
    131 	handleLoadMusicEvents();
    132 
    133 	if (editor.samplingAudioFlag) handleSamplingUpdates();
    134 	if (ui.setMouseBusy) mouseAnimOn();
    135 	if (ui.setMouseIdle) mouseAnimOff();
    136 
    137 	if (editor.updateWindowTitle)
    138 	{
    139 		editor.updateWindowTitle = false;
    140 		updateWindowTitle(false);
    141 	}
    142 }
    143 
    144 // Windows specific routines
    145 #ifdef _WIN32
    146 static bool instanceAlreadyOpen(void)
    147 {
    148 	hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, SHARED_HWND_NAME);
    149 	if (hMapFile != NULL)
    150 		return true; // another instance is already open
    151 
    152 	// no instance is open, let's created a shared memory file with hWnd in it
    153 	oneInstHandle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof (HWND), SHARED_HWND_NAME);
    154 	if (oneInstHandle != NULL)
    155 	{
    156 		sharedMemBuf = (LPTSTR)MapViewOfFile(oneInstHandle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof (HWND));
    157 		if (sharedMemBuf != NULL)
    158 		{
    159 			CopyMemory((PVOID)sharedMemBuf, &video.hWnd, sizeof (HWND));
    160 			UnmapViewOfFile(sharedMemBuf);
    161 			sharedMemBuf = NULL;
    162 		}
    163 	}
    164 
    165 	return false;
    166 }
    167 
    168 bool handleSingleInstancing(int32_t argc, char **argv)
    169 {
    170 	SDL_SysWMinfo wmInfo;
    171 
    172 	SDL_VERSION(&wmInfo.version);
    173 	if (!SDL_GetWindowWMInfo(video.window, &wmInfo))
    174 		return false;
    175 
    176 	video.hWnd = wmInfo.info.win.window;
    177 	if (instanceAlreadyOpen() && argc >= 2 && argv[1][0] != '\0')
    178 	{
    179 		sharedMemBuf = (LPTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof (HWND));
    180 		if (sharedMemBuf != NULL)
    181 		{
    182 			memcpy(&hWnd, sharedMemBuf, sizeof (HWND));
    183 
    184 			UnmapViewOfFile(sharedMemBuf);
    185 			sharedMemBuf = NULL;
    186 			CloseHandle(hMapFile);
    187 			hMapFile = NULL;
    188 
    189 			hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, ARGV_SHARED_MEM_MAX_LEN, SHARED_FILENAME);
    190 			if (hMapFile != NULL)
    191 			{
    192 				sharedMemBuf = (LPTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, ARGV_SHARED_MEM_MAX_LEN);
    193 				if (sharedMemBuf != NULL)
    194 				{
    195 					strcpy((char *)sharedMemBuf, argv[1]);
    196 
    197 					UnmapViewOfFile(sharedMemBuf);
    198 					sharedMemBuf = NULL;
    199 
    200 					SendMessage(hWnd, SYSMSG_FILE_ARG, 0, 0);
    201 					Sleep(80); // wait a bit to make sure first instance received msg
    202 
    203 					CloseHandle(hMapFile);
    204 					hMapFile = NULL;
    205 
    206 					return true; // quit instance now
    207 				}
    208 			}
    209 
    210 			return true;
    211 		}
    212 
    213 		CloseHandle(hMapFile);
    214 		hMapFile = NULL;
    215 	}
    216 
    217 	SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
    218 	return false;
    219 }
    220 
    221 static void handleSysMsg(SDL_Event inputEvent)
    222 {
    223 	SDL_SysWMmsg *wmMsg = inputEvent.syswm.msg;
    224 	if (wmMsg->subsystem == SDL_SYSWM_WINDOWS && wmMsg->msg.win.msg == SYSMSG_FILE_ARG)
    225 	{
    226 		hMapFile = OpenFileMapping(FILE_MAP_READ, FALSE, SHARED_FILENAME);
    227 		if (hMapFile != NULL)
    228 		{
    229 			sharedMemBuf = (LPTSTR)MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, ARGV_SHARED_MEM_MAX_LEN);
    230 			if (sharedMemBuf != NULL)
    231 			{
    232 				editor.autoPlayOnDrop = true;
    233 
    234 				if (video.window != NULL && !video.fullscreen)
    235 				{
    236 					if (SDL_GetWindowFlags(video.window) & SDL_WINDOW_MINIMIZED)
    237 						SDL_RestoreWindow(video.window);
    238 
    239 					SDL_RaiseWindow(video.window);
    240 				}
    241 
    242 				loadDroppedFile((char *)sharedMemBuf, true);
    243 
    244 				UnmapViewOfFile(sharedMemBuf);
    245 				sharedMemBuf = NULL;
    246 			}
    247 
    248 			CloseHandle(hMapFile);
    249 			hMapFile = NULL;
    250 		}
    251 	}
    252 }
    253 
    254 void closeSingleInstancing(void)
    255 {
    256 	if (oneInstHandle != NULL)
    257 	{
    258 		CloseHandle(oneInstHandle);
    259 		oneInstHandle = NULL;
    260 	}
    261 }
    262 
    263 static LONG WINAPI exceptionHandler(EXCEPTION_POINTERS *ptr)
    264 {
    265 #define BACKUP_FILES_TO_TRY 1000
    266 	char fileName[32];
    267 	struct stat statBuffer;
    268 
    269 	if (oneInstHandle != NULL)
    270 		CloseHandle(oneInstHandle);
    271 
    272 	if (!backupMadeAfterCrash)
    273 	{
    274 		if (getDiskOpModPath() != NULL && UNICHAR_CHDIR(getDiskOpModPath()) == 0)
    275 		{
    276 			// find a free filename
    277 			int32_t i;
    278 			for (i = 1; i < BACKUP_FILES_TO_TRY; i++)
    279 			{
    280 				sprintf(fileName, "backup%03d.xm", (int32_t)i);
    281 				if (stat(fileName, &statBuffer) != 0)
    282 					break; // filename OK
    283 			}
    284 
    285 			if (i != BACKUP_FILES_TO_TRY)
    286 			{
    287 				UNICHAR *fileNameU = cp850ToUnichar(fileName);
    288 				if (fileNameU != NULL)
    289 				{
    290 					saveXM(fileNameU);
    291 					free(fileNameU);
    292 				}
    293 			}
    294 		}
    295 
    296 		backupMadeAfterCrash = true; // set this flag to prevent multiple backups from being saved at once
    297 		showErrorMsgBox(CRASH_TEXT);
    298 	}
    299 
    300 	return EXCEPTION_CONTINUE_SEARCH;
    301 
    302 	(void)ptr;
    303 }
    304 #else
    305 static void exceptionHandler(int32_t signal)
    306 {
    307 #define BACKUP_FILES_TO_TRY 1000
    308 	char fileName[32];
    309 	struct stat statBuffer;
    310 
    311 	if (signal == 15)
    312 		return;
    313 
    314 	if (!backupMadeAfterCrash)
    315 	{
    316 		if (getDiskOpModPath() != NULL && UNICHAR_CHDIR(getDiskOpModPath()) == 0)
    317 		{
    318 			// find a free filename
    319 			int32_t i;
    320 			for (i = 1; i < BACKUP_FILES_TO_TRY; i++)
    321 			{
    322 				sprintf(fileName, "backup%03d.xm", i);
    323 				if (stat(fileName, &statBuffer) != 0)
    324 					break; // filename OK
    325 			}
    326 
    327 			if (i != BACKUP_FILES_TO_TRY)
    328 			{
    329 				UNICHAR *fileNameU = cp850ToUnichar(fileName);
    330 				if (fileNameU != NULL)
    331 				{
    332 					saveXM(fileNameU);
    333 					free(fileNameU);
    334 				}
    335 			}
    336 		}
    337 
    338 		backupMadeAfterCrash = true; // set this flag to prevent multiple backups from being saved at once
    339 		showErrorMsgBox(CRASH_TEXT);
    340 	}
    341 }
    342 #endif
    343 
    344 void setupCrashHandler(void)
    345 {
    346 #ifndef _DEBUG
    347 #ifdef _WIN32
    348 	SetUnhandledExceptionFilter(exceptionHandler);
    349 #else
    350 	struct sigaction act;
    351 	struct sigaction oldAct;
    352 
    353 	memset(&act, 0, sizeof (act));
    354 	act.sa_handler = exceptionHandler;
    355 	act.sa_flags = SA_RESETHAND;
    356 
    357 	sigaction(SIGILL | SIGABRT | SIGFPE | SIGSEGV, &act, &oldAct);
    358 	sigaction(SIGILL, &act, &oldAct);
    359 	sigaction(SIGABRT, &act, &oldAct);
    360 	sigaction(SIGFPE, &act, &oldAct);
    361 	sigaction(SIGSEGV, &act, &oldAct);
    362 #endif
    363 #endif
    364 }
    365 
    366 void handleWaitVblQuirk(SDL_Event *event)
    367 {
    368 	if (event->type == SDL_WINDOWEVENT)
    369 	{
    370 		if (event->window.event == SDL_WINDOWEVENT_HIDDEN)
    371 			video.windowHidden = true;
    372 		else if (event->window.event == SDL_WINDOWEVENT_SHOWN)
    373 			video.windowHidden = false;
    374 
    375 		// reset vblank end time if we minimize window
    376 		if (event->window.event == SDL_WINDOWEVENT_MINIMIZED || event->window.event == SDL_WINDOWEVENT_FOCUS_LOST)
    377 			hpc_ResetCounters(&video.vblankHpc);
    378 	}
    379 }
    380 
    381 static void handleSDLEvents(void)
    382 {
    383 	SDL_Event event;
    384 
    385 	if (!editor.busy)
    386 		handleLastGUIObjectDown(); // this should be handled before main input poll (on next frame)
    387 
    388 	while (SDL_PollEvent(&event))
    389 	{
    390 		handleWaitVblQuirk(&event);
    391 
    392 		if (editor.busy)
    393 		{
    394 			const uint32_t eventType = event.type;
    395 			const SDL_Scancode key = event.key.keysym.scancode;
    396 
    397 			/* The Echo tool in Smp. Ed. can take forever if abused, let
    398 			** mouse buttons/ESC/SIGTERM force-stop it.
    399 			*/
    400 			if (eventType == SDL_MOUSEBUTTONDOWN || eventType == SDL_QUIT ||
    401 				(eventType == SDL_KEYUP && key == SDL_SCANCODE_ESCAPE))
    402 			{
    403 				handleEchoToolPanic();
    404 			}
    405 
    406 			// let certain mouse buttons or keyboard keys stop certain events
    407 			if (eventType == SDL_MOUSEBUTTONDOWN ||
    408 				(eventType == SDL_KEYDOWN && key != SDL_SCANCODE_MUTE &&
    409 				 key != SDL_SCANCODE_AUDIOMUTE && key != SDL_SCANCODE_VOLUMEDOWN &&
    410 				 key != SDL_SCANCODE_VOLUMEUP))
    411 			{
    412 				if (editor.samplingAudioFlag)
    413 					stopSampling();
    414 
    415 				editor.wavIsRendering = false;
    416 			}
    417 
    418 			continue; // another thread is busy with something, drop input
    419 		}
    420 
    421 #ifdef _WIN32
    422 		if (event.type == SDL_SYSWMEVENT)
    423 			handleSysMsg(event);
    424 #endif
    425 		// text input when editing texts
    426 		if (event.type == SDL_TEXTINPUT)
    427 		{
    428 			if (editor.editTextFlag)
    429 			{
    430 				if (keyb.ignoreTextEditKey)
    431 				{
    432 					keyb.ignoreTextEditKey = false;
    433 					continue;
    434 				}
    435 
    436 				char *inputText = utf8ToCp850(event.text.text, false);
    437 				if (inputText != NULL)
    438 				{
    439 					if (inputText[0] != '\0')
    440 						handleTextEditInputChar(inputText[0]);
    441 
    442 					free(inputText);
    443 				}
    444 			}
    445 		}
    446 		else if (event.type == SDL_MOUSEWHEEL)
    447 		{
    448 			if (event.wheel.y > 0)
    449 				mouseWheelHandler(MOUSE_WHEEL_UP);
    450 			else if (event.wheel.y < 0)
    451 				mouseWheelHandler(MOUSE_WHEEL_DOWN);
    452 		}
    453 		else if (event.type == SDL_DROPFILE)
    454 		{
    455 			editor.autoPlayOnDrop = false;
    456 
    457 			if (!video.fullscreen)
    458 			{
    459 				if (SDL_GetWindowFlags(video.window) & SDL_WINDOW_MINIMIZED)
    460 					SDL_RestoreWindow(video.window);
    461 
    462 				SDL_RaiseWindow(video.window);
    463 			}
    464 
    465 			loadDroppedFile(event.drop.file, true);
    466 			SDL_free(event.drop.file);
    467 		}
    468 		else if (event.type == SDL_QUIT)
    469 		{
    470 			if (ui.sysReqShown)
    471 				continue;
    472 
    473 			if (editor.editTextFlag)
    474 				exitTextEditing();
    475 
    476 			if (!song.isModified)
    477 			{
    478 				editor.throwExit = true;
    479 			}
    480 			else
    481 			{
    482 				if (!video.fullscreen)
    483 				{
    484 					// de-minimize window and set focus so that the user sees the message box
    485 					if (SDL_GetWindowFlags(video.window) & SDL_WINDOW_MINIMIZED)
    486 						SDL_RestoreWindow(video.window);
    487 
    488 					SDL_RaiseWindow(video.window);
    489 				}
    490 
    491 				if (quitBox(true) == 1)
    492 					editor.throwExit = true;
    493 			}
    494 		}
    495 		else if (event.type == SDL_KEYUP)
    496 		{
    497 			keyUpHandler(event.key.keysym.scancode, event.key.keysym.sym);
    498 		}
    499 		else if (event.type == SDL_KEYDOWN)
    500 		{
    501 			keyDownHandler(event.key.keysym.scancode, event.key.keysym.sym, event.key.repeat);
    502 		}
    503 		else if (event.type == SDL_MOUSEBUTTONUP)
    504 		{
    505 			mouseButtonUpHandler(event.button.button);
    506 #if defined __APPLE__ && defined __aarch64__
    507 			armMacGhostMouseCursorFix();
    508 #endif
    509 		}
    510 		else if (event.type == SDL_MOUSEBUTTONDOWN)
    511 		{
    512 			mouseButtonDownHandler(event.button.button);
    513 		}
    514 #if defined __APPLE__ && defined __aarch64__
    515 		else if (event.type == SDL_MOUSEMOTION)
    516 		{
    517 			armMacGhostMouseCursorFix();
    518 		}
    519 #endif
    520 
    521 		if (editor.throwExit)
    522 			editor.programRunning = false;
    523 	}
    524 
    525 #ifdef HAS_MIDI
    526 	// MIDI vibrato
    527 	const uint8_t vibDepth = (midi.currMIDIVibDepth >> 9) & 0x0F;
    528 	if (vibDepth > 0)
    529 		recordMIDIEffect(0x04, 0xA0 | vibDepth);
    530 #endif
    531 }