1 /*
2  * TilEm II
3  *
4  * Copyright (c) 2011-2012 Benjamin Moody
5  *
6  * This program is free software: you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation, either version 3 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23 
24 #include <stdio.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <gtk/gtk.h>
28 #include <glib/gstdio.h>
29 #include <ticalcs.h>
30 #include <tilem.h>
31 
32 #include "gui.h"
33 #include "emucore.h"
34 #include "msgbox.h"
35 #include "filedlg.h"
36 
37 #define MILLISEC_PER_FRAME 30
38 #define MICROSEC_PER_FRAME (MILLISEC_PER_FRAME * 1000)
39 
40 #define GRAY_WINDOW_SIZE 4
41 #define GRAY_SAMPLE_INT 200
42 
43 
44 /* Lock emulator.  Notify the core loop that we wish to do so - note
45    that if the core is running at full speed, it keeps the mutex
46    locked almost all the time, so we need to explicitly ask it to
47    yield.  (This may not be necessary with all mutex implementations,
48    but definitely is necessary with some.) */
tilem_calc_emulator_lock(TilemCalcEmulator * emu)49 void tilem_calc_emulator_lock(TilemCalcEmulator *emu)
50 {
51 	g_atomic_int_inc(&emu->calc_lock_waiting);
52 	g_mutex_lock(emu->calc_mutex);
53 }
54 
55 /* Unlock emulator and (if no other threads are waiting to lock it)
56    wake up the core thread.  This also serves to resume emulation if
57    the calculator has been powered off. */
tilem_calc_emulator_unlock(TilemCalcEmulator * emu)58 void tilem_calc_emulator_unlock(TilemCalcEmulator *emu)
59 {
60 	if (g_atomic_int_dec_and_test(&emu->calc_lock_waiting))
61 		g_cond_signal(emu->calc_wakeup_cond);
62 	g_mutex_unlock(emu->calc_mutex);
63 }
64 
refresh_lcd(gpointer data)65 static gboolean refresh_lcd(gpointer data)
66 {
67 	TilemCalcEmulator* emu = data;
68 
69 	if (emu->ewin)
70 		tilem_emulator_window_refresh_lcd(emu->ewin);
71 
72 	return FALSE;
73 }
74 
tmr_screen_update(TilemCalc * calc,void * data)75 static void tmr_screen_update(TilemCalc *calc, void *data)
76 {
77 	TilemCalcEmulator *emu = data;
78 
79 	g_mutex_lock(emu->lcd_mutex);
80 
81 	if (emu->glcd)
82 		tilem_gray_lcd_get_frame(emu->glcd, emu->lcd_buffer);
83 	else
84 		tilem_lcd_get_frame(calc, emu->lcd_buffer);
85 
86 	if (emu->anim) {
87 		if (emu->anim_grayscale) {
88 			tilem_animation_append_frame(emu->anim,
89 			                             emu->lcd_buffer,
90 			                             MILLISEC_PER_FRAME);
91 		}
92 		else {
93 			tilem_lcd_get_frame(calc, emu->tmp_lcd_buffer);
94 			tilem_animation_append_frame(emu->anim,
95 			                             emu->tmp_lcd_buffer,
96 			                             MILLISEC_PER_FRAME);
97 		}
98 	}
99 
100 	if (!emu->lcd_update_pending) {
101 		emu->lcd_update_pending = TRUE;
102 		g_idle_add_full(G_PRIORITY_DEFAULT, &refresh_lcd, emu, NULL);
103 	}
104 
105 	g_mutex_unlock(emu->lcd_mutex);
106 }
107 
cancel_animation(TilemCalcEmulator * emu)108 static void cancel_animation(TilemCalcEmulator *emu)
109 {
110 	if (emu->anim)
111 		g_object_unref(emu->anim);
112 	emu->anim = NULL;
113 }
114 
get_toplevel(TilemCalcEmulator * emu)115 static GtkWidget *get_toplevel(TilemCalcEmulator *emu)
116 {
117 	if (emu->ewin)
118 		return emu->ewin->window;
119 	else
120 		return NULL;
121 }
122 
link_update_nop()123 static void link_update_nop()
124 {
125 }
126 
tilem_calc_emulator_new()127 TilemCalcEmulator *tilem_calc_emulator_new()
128 {
129 	TilemCalcEmulator *emu = g_new0(TilemCalcEmulator, 1);
130 	CalcUpdate *update;
131 
132 	emu->calc_mutex = g_mutex_new();
133 	emu->calc_wakeup_cond = g_cond_new();
134 	emu->lcd_mutex = g_mutex_new();
135 
136 	tilem_config_get("emulation",
137 	                 "grayscale/b=1", &emu->grayscale,
138 	                 "limit_speed/b=1", &emu->limit_speed,
139 	                 NULL);
140 
141 	emu->task_queue = g_queue_new();
142 	emu->task_finished_cond = g_cond_new();
143 
144 	emu->timer = g_timer_new();
145 
146 	emu->pbar_mutex = g_mutex_new();
147 
148 	update = g_new0(CalcUpdate, 1);
149 	update->start = &link_update_nop;
150 	update->stop = &link_update_nop;
151 	update->refresh = &link_update_nop;
152 	update->pbar = &link_update_nop;
153 	update->label = &link_update_nop;
154 	emu->link_update = update;
155 
156 	return emu;
157 }
158 
tilem_calc_emulator_free(TilemCalcEmulator * emu)159 void tilem_calc_emulator_free(TilemCalcEmulator *emu)
160 {
161 	g_return_if_fail(emu != NULL);
162 
163 	tilem_calc_emulator_cancel_tasks(emu);
164 
165 	tilem_calc_emulator_lock(emu);
166 	cancel_animation(emu);
167 	emu->exiting = TRUE;
168 	tilem_calc_emulator_unlock(emu);
169 
170 	if (emu->z80_thread)
171 		g_thread_join(emu->z80_thread);
172 
173 	g_free(emu->key_queue);
174 
175 	g_free(emu->rom_file_name);
176 	g_free(emu->state_file_name);
177 
178 	g_mutex_free(emu->calc_mutex);
179 	g_mutex_free(emu->lcd_mutex);
180 	g_cond_free(emu->calc_wakeup_cond);
181 
182 	g_cond_free(emu->task_finished_cond);
183 	g_queue_free(emu->task_queue);
184 
185 	g_timer_destroy(emu->timer);
186 
187 	g_mutex_free(emu->pbar_mutex);
188 	g_free(emu->link_update);
189 
190 	if (emu->lcd_buffer)
191 		tilem_lcd_buffer_free(emu->lcd_buffer);
192 	if (emu->tmp_lcd_buffer)
193 		tilem_lcd_buffer_free(emu->tmp_lcd_buffer);
194 	if (emu->glcd)
195 		tilem_gray_lcd_free(emu->glcd);
196 	if (emu->calc)
197 		tilem_calc_free(emu->calc);
198 
199 	g_free(emu);
200 }
201 
get_sav_name(const char * romname)202 static char *get_sav_name(const char *romname)
203 {
204 	char *dname, *bname, *sname, *suff;
205 
206 	dname = g_path_get_dirname(romname);
207 	bname = g_path_get_basename(romname);
208 
209 	if ((suff = strrchr(bname, '.')))
210 		*suff = 0;
211 	sname = g_strconcat(dname, G_DIR_SEPARATOR_S, bname, ".sav", NULL);
212 
213 	g_free(dname);
214 	g_free(bname);
215 	return sname;
216 }
217 
tilem_calc_emulator_load_state(TilemCalcEmulator * emu,const char * romfname,const char * statefname,int model,GError ** err)218 gboolean tilem_calc_emulator_load_state(TilemCalcEmulator *emu,
219                                         const char *romfname,
220                                         const char *statefname,
221                                         int model, GError **err)
222 {
223 	const char *modelname;
224 	FILE *romfile, *savfile;
225 	char *rname = NULL, *sname = NULL;
226 	TilemCalc *calc;
227 	char *dname;
228 	int errnum;
229 
230 	g_return_val_if_fail(emu != NULL, FALSE);
231 	g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
232 
233 	tilem_calc_emulator_cancel_tasks(emu);
234 
235 	if (romfname)
236 		rname = g_strdup(romfname);
237 	if (!sname && statefname)
238 		sname = g_strdup(statefname);
239 
240 	/* Choose ROM file */
241 
242 	if (!rname && model) {
243 		modelname = model_to_name(model);
244 		g_return_val_if_fail(modelname != NULL, FALSE);
245 		if (sname) g_free(sname);
246 		tilem_config_get(modelname,
247 		                 "rom_file/f", &rname,
248 		                 "state_file/f", &sname,
249 		                 NULL);
250 	}
251 
252 	if (!rname) {
253 		g_set_error(err, TILEM_EMULATOR_ERROR,
254 		            TILEM_EMULATOR_ERROR_NO_ROM,
255 		            "No ROM file specified");
256 		g_free(rname);
257 		g_free(sname);
258 		return FALSE;
259 	}
260 
261 	/* Open ROM file */
262 
263 	romfile = g_fopen(rname, "rb");
264 	if (!romfile) {
265 		errnum = errno;
266 		dname = g_filename_display_basename(rname);
267 		g_set_error(err, G_FILE_ERROR,
268 		            g_file_error_from_errno(errnum),
269 		            "Unable to open %s for reading: %s",
270 		            dname, g_strerror(errnum));
271 		g_free(dname);
272 		g_free(rname);
273 		g_free(sname);
274 		return FALSE;
275 	}
276 
277 	/* Open state file */
278 
279 	if (!sname)
280 		sname = get_sav_name(rname);
281 
282 	savfile = g_fopen(sname, "rb");
283 
284 	if (!savfile && errno != ENOENT) {
285 		errnum = errno;
286 		dname = g_filename_display_basename(sname);
287 		g_set_error(err, G_FILE_ERROR,
288 		            g_file_error_from_errno(errnum),
289 		            "Unable to open %s for reading: %s",
290 		            dname, g_strerror(errnum));
291 		g_free(dname);
292 		g_free(rname);
293 		g_free(sname);
294 		fclose(romfile);
295 		return FALSE;
296 	}
297 
298 	/* Determine model from state file, if possible */
299 
300 	if (!model && savfile)
301 		model = tilem_get_sav_type(savfile);
302 
303 	/* Otherwise, guess from ROM file; ask user if ambiguous */
304 
305 	if (!model) {
306 		model = tilem_guess_rom_type(romfile);
307 		if (model) {
308 			model = choose_rom_popup(get_toplevel(emu),
309 						 rname, model);
310 		}
311 		else {
312 			dname = g_filename_display_basename(rname);
313 			g_set_error(err, TILEM_EMULATOR_ERROR,
314 			            TILEM_EMULATOR_ERROR_INVALID_ROM,
315 			            "The file %s is not a recognized"
316 			            " calculator ROM file.",
317 			            dname);
318 			g_free(dname);
319 		}
320 	}
321 
322 	if (!model) {
323 		fclose(romfile);
324 		if (savfile) fclose(savfile);
325 		g_free(rname);
326 		g_free(sname);
327 		return FALSE;
328 	}
329 
330 	/* Create new calc, and load state */
331 
332 	calc = tilem_calc_new(model);
333 	if (tilem_calc_load_state(calc, romfile, savfile)) {
334 		g_set_error(err, TILEM_EMULATOR_ERROR,
335 		            TILEM_EMULATOR_ERROR_INVALID_STATE,
336 		            "The specified ROM or state file is invalid.");
337 		fclose(romfile);
338 		if (savfile) fclose(savfile);
339 		g_free(rname);
340 		g_free(sname);
341 		return FALSE;
342 	}
343 
344 	if (!savfile) {
345 		/* save model as default for the future */
346 		savfile = g_fopen(sname, "wb");
347 		if (savfile)
348 			fprintf(savfile, "MODEL = %s\n", calc->hw.name);
349 	}
350 
351 	fclose(romfile);
352 	if (savfile) fclose(savfile);
353 
354 	/* Switch to new calc */
355 
356 	tilem_calc_emulator_lock(emu);
357 
358 	cancel_animation(emu);
359 
360 	if (emu->glcd)
361 		tilem_gray_lcd_free(emu->glcd);
362 	if (emu->calc)
363 		tilem_calc_free(emu->calc);
364 
365 	emu->calc = calc;
366 	emu->lcd_buffer = tilem_lcd_buffer_new();
367 	emu->tmp_lcd_buffer = tilem_lcd_buffer_new();
368 
369 	if (emu->grayscale)
370 		emu->glcd = tilem_gray_lcd_new(calc, GRAY_WINDOW_SIZE,
371 		                               GRAY_SAMPLE_INT);
372 	else
373 		emu->glcd = NULL;
374 
375 	tilem_z80_add_timer(calc, MICROSEC_PER_FRAME,
376 	                    MICROSEC_PER_FRAME, 1,
377 	                    &tmr_screen_update, emu);
378 
379 	tilem_calc_emulator_unlock(emu);
380 
381 	if (emu->rom_file_name)
382 		g_free(emu->rom_file_name);
383 	emu->rom_file_name = rname;
384 
385 	if (emu->state_file_name)
386 		g_free(emu->state_file_name);
387 	emu->state_file_name = sname;
388 
389 	tilem_keybindings_init(emu, calc->hw.name);
390 
391 	if (emu->ewin)
392 		tilem_emulator_window_calc_changed(emu->ewin);
393 	if (emu->dbg)
394 		tilem_debugger_calc_changed(emu->dbg);
395 
396 	if (emu->rcvdlg)
397 		tilem_receive_dialog_free(emu->rcvdlg);
398 	emu->rcvdlg = NULL;
399 
400 	return TRUE;
401 }
402 
tilem_calc_emulator_revert_state(TilemCalcEmulator * emu,GError ** err)403 gboolean tilem_calc_emulator_revert_state(TilemCalcEmulator *emu, GError **err)
404 {
405 	FILE *romfile, *savfile;
406 	char *dname;
407 	int errnum = 0;
408 	gboolean status = TRUE;
409 
410 	g_return_val_if_fail(emu != NULL, FALSE);
411 	g_return_val_if_fail(emu->calc != NULL, FALSE);
412 	g_return_val_if_fail(emu->rom_file_name != NULL, FALSE);
413 	g_return_val_if_fail(emu->state_file_name != NULL, FALSE);
414 	g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
415 
416 	/* Open ROM file */
417 
418 	if (emu->calc->hw.flags & TILEM_CALC_HAS_FLASH) {
419 		romfile = g_fopen(emu->rom_file_name, "rb");
420 		if (!romfile) {
421 			errnum = errno;
422 			dname = g_filename_display_basename(emu->rom_file_name);
423 			g_set_error(err, G_FILE_ERROR,
424 			            g_file_error_from_errno(errnum),
425 			            "Unable to open %s for reading: %s",
426 			            dname, g_strerror(errnum));
427 			g_free(dname);
428 			return FALSE;
429 		}
430 	}
431 	else {
432 		romfile = NULL;
433 	}
434 
435 	/* Open state file */
436 
437 	savfile = g_fopen(emu->state_file_name, "rb");
438 	if (!savfile) {
439 		errnum = errno;
440 		dname = g_filename_display_basename(emu->state_file_name);
441 		g_set_error(err, G_FILE_ERROR,
442 		            g_file_error_from_errno(errnum),
443 		            "Unable to open %s for reading: %s",
444 		            dname, g_strerror(errnum));
445 		g_free(dname);
446 		if (romfile) fclose(romfile);
447 		return FALSE;
448 	}
449 
450 	/* Read state */
451 
452 	tilem_calc_emulator_lock(emu);
453 
454 	if (tilem_calc_load_state(emu->calc, romfile, savfile)) {
455 		g_set_error(err, TILEM_EMULATOR_ERROR,
456 		            TILEM_EMULATOR_ERROR_INVALID_STATE,
457 		            "The specified ROM or state file is invalid.");
458 		status = FALSE;
459 	}
460 
461 	tilem_calc_emulator_unlock(emu);
462 
463 	if (emu->dbg)
464 		tilem_debugger_refresh(emu->dbg, TRUE);
465 
466 	if (romfile) fclose(romfile);
467 	fclose(savfile);
468 	return status;
469 }
470 
tilem_calc_emulator_save_state(TilemCalcEmulator * emu,GError ** err)471 gboolean tilem_calc_emulator_save_state(TilemCalcEmulator *emu, GError **err)
472 {
473 	FILE *romfile, *savfile;
474 	char *dname;
475 	int errnum = 0;
476 
477 	g_return_val_if_fail(emu != NULL, FALSE);
478 	g_return_val_if_fail(emu->calc != NULL, FALSE);
479 	g_return_val_if_fail(emu->rom_file_name != NULL, FALSE);
480 	g_return_val_if_fail(emu->state_file_name != NULL, FALSE);
481 	g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
482 
483 	/* Open ROM file */
484 
485 	if (emu->calc->hw.flags & TILEM_CALC_HAS_FLASH) {
486 		romfile = g_fopen(emu->rom_file_name, "r+b");
487 		if (!romfile) {
488 			errnum = errno;
489 			dname = g_filename_display_basename(emu->rom_file_name);
490 			g_set_error(err, G_FILE_ERROR,
491 			            g_file_error_from_errno(errnum),
492 			            "Unable to open %s for writing: %s",
493 			            dname, g_strerror(errnum));
494 			g_free(dname);
495 			return FALSE;
496 		}
497 	}
498 	else {
499 		romfile = NULL;
500 	}
501 
502 	/* Open state file */
503 
504 	savfile = g_fopen(emu->state_file_name, "wb");
505 	if (!savfile) {
506 		errnum = errno;
507 		dname = g_filename_display_basename(emu->state_file_name);
508 		g_set_error(err, G_FILE_ERROR,
509 		            g_file_error_from_errno(errnum),
510 		            "Unable to open %s for writing: %s",
511 		            dname, g_strerror(errnum));
512 		g_free(dname);
513 		if (romfile) fclose(romfile);
514 		return FALSE;
515 	}
516 
517 	/* Write state */
518 
519 	tilem_calc_emulator_lock(emu);
520 
521 	if (romfile && tilem_calc_save_state(emu->calc, romfile, NULL))
522 		errnum = errno;
523 	if (romfile && fclose(romfile))
524 		errnum = errno;
525 
526 	if (errnum) {
527 		dname = g_filename_display_basename(emu->rom_file_name);
528 		g_set_error(err, G_FILE_ERROR,
529 		            g_file_error_from_errno(errnum),
530 		            "Error writing %s: %s",
531 		            dname, g_strerror(errnum));
532 		g_free(dname);
533 		fclose(savfile);
534 		tilem_calc_emulator_unlock(emu);
535 		return FALSE;
536 	}
537 
538 	if (tilem_calc_save_state(emu->calc, NULL, savfile))
539 		errnum = errno;
540 	if (fclose(savfile))
541 		errnum = errno;
542 
543 	tilem_calc_emulator_unlock(emu);
544 
545 	if (errnum) {
546 		dname = g_filename_display_basename(emu->state_file_name);
547 		g_set_error(err, G_FILE_ERROR,
548 		            g_file_error_from_errno(errnum),
549 		            "Error writing %s: %s",
550 		            dname, g_strerror(errnum));
551 		g_free(dname);
552 		return FALSE;
553 	}
554 
555 	return TRUE;
556 }
557 
tilem_calc_emulator_reset(TilemCalcEmulator * emu)558 void tilem_calc_emulator_reset(TilemCalcEmulator *emu)
559 {
560 	g_return_if_fail(emu != NULL);
561 	g_return_if_fail(emu->calc != NULL);
562 
563 	tilem_calc_emulator_lock(emu);
564 	tilem_calc_reset(emu->calc);
565 	tilem_calc_emulator_unlock(emu);
566 
567 	if (emu->dbg)
568 		tilem_debugger_refresh(emu->dbg, TRUE);
569 }
570 
tilem_calc_emulator_pause(TilemCalcEmulator * emu)571 void tilem_calc_emulator_pause(TilemCalcEmulator *emu)
572 {
573 	g_return_if_fail(emu != NULL);
574 
575 	tilem_calc_emulator_lock(emu);
576 	emu->paused = TRUE;
577 	tilem_calc_emulator_unlock(emu);
578 }
579 
tilem_calc_emulator_run(TilemCalcEmulator * emu)580 void tilem_calc_emulator_run(TilemCalcEmulator *emu)
581 {
582 	g_return_if_fail(emu != NULL);
583 	g_return_if_fail(emu->calc != NULL);
584 
585 	tilem_calc_emulator_lock(emu);
586 	emu->paused = FALSE;
587 	tilem_calc_emulator_unlock(emu);
588 
589 	if (!emu->z80_thread)
590 		emu->z80_thread = g_thread_create(&tilem_em_main, emu, TRUE, NULL);
591 }
592 
tilem_calc_emulator_set_limit_speed(TilemCalcEmulator * emu,gboolean limit)593 void tilem_calc_emulator_set_limit_speed(TilemCalcEmulator *emu,
594                                          gboolean limit)
595 {
596 	emu->limit_speed = limit;
597 }
598 
tilem_calc_emulator_set_grayscale(TilemCalcEmulator * emu,gboolean grayscale)599 void tilem_calc_emulator_set_grayscale(TilemCalcEmulator *emu,
600                                        gboolean grayscale)
601 {
602 	emu->grayscale = grayscale;
603 
604 	if (grayscale && emu->calc && !emu->glcd) {
605 		tilem_calc_emulator_lock(emu);
606 		emu->glcd = tilem_gray_lcd_new(emu->calc, GRAY_WINDOW_SIZE,
607 		                               GRAY_SAMPLE_INT);
608 		tilem_calc_emulator_unlock(emu);
609 	}
610 	else if (!grayscale && emu->glcd) {
611 		tilem_calc_emulator_lock(emu);
612 		tilem_gray_lcd_free(emu->glcd);
613 		emu->glcd = NULL;
614 		tilem_calc_emulator_unlock(emu);
615 	}
616 }
617 
618 /* If currently recording a macro, record a keypress */
record_key(TilemCalcEmulator * emu,int code)619 static void record_key(TilemCalcEmulator* emu, int code)
620 {
621 	char* codechar;
622 	int type = 0;
623 
624 	if (emu->isMacroRecording) {
625 		codechar = g_strdup_printf("%04d", code);
626 		tilem_macro_add_action(emu->macro, type, codechar);
627 		g_free(codechar);
628 	}
629 }
630 
tilem_calc_emulator_press_key(TilemCalcEmulator * emu,int key)631 void tilem_calc_emulator_press_key(TilemCalcEmulator *emu, int key)
632 {
633 	g_return_if_fail(emu != NULL);
634 
635 	if (key == 0)
636 		return;
637 
638 	tilem_calc_emulator_lock(emu);
639 	tilem_keypad_press_key(emu->calc, key);
640 	tilem_calc_emulator_unlock(emu);
641 
642 	record_key(emu, key);
643 
644 	if (emu->dbg && emu->dbg->keypad_dialog)
645 		tilem_keypad_dialog_refresh(emu->dbg->keypad_dialog);
646 }
647 
tilem_calc_emulator_release_key(TilemCalcEmulator * emu,int key)648 void tilem_calc_emulator_release_key(TilemCalcEmulator *emu, int key)
649 {
650 	g_return_if_fail(emu != NULL);
651 
652 	if (key == 0)
653 		return;
654 
655 	tilem_calc_emulator_lock(emu);
656 	tilem_keypad_release_key(emu->calc, key);
657 	tilem_calc_emulator_unlock(emu);
658 
659 	if (emu->dbg && emu->dbg->keypad_dialog)
660 		tilem_keypad_dialog_refresh(emu->dbg->keypad_dialog);
661 }
662 
refresh_kpd(gpointer data)663 static gboolean refresh_kpd(gpointer data)
664 {
665 	TilemCalcEmulator *emu = data;
666 
667 	if (emu->dbg && emu->dbg->keypad_dialog)
668 		tilem_keypad_dialog_refresh(emu->dbg->keypad_dialog);
669 
670 	return FALSE;
671 }
672 
673 /* Timer callback for key sequences */
tmr_key_queue(TilemCalc * calc,void * data)674 static void tmr_key_queue(TilemCalc* calc, void* data)
675 {
676 	TilemCalcEmulator *emu = data;
677 	int nextkey;
678 
679 	if (emu->key_queue_pressed) {
680 		if (emu->key_queue_len > 0 || !emu->key_queue_hold) {
681 			tilem_keypad_release_key(calc, emu->key_queue_cur);
682 			emu->key_queue_pressed = 0;
683 			emu->key_queue_cur = 0;
684 			tilem_z80_set_timer(calc, emu->key_queue_timer,
685 			                    50000, 0, 1);
686 		}
687 		else {
688 			tilem_z80_remove_timer(calc, emu->key_queue_timer);
689 			emu->key_queue_timer = 0;
690 		}
691 	}
692 	else {
693 		if (emu->key_queue_len > 0) {
694 			nextkey = emu->key_queue[--emu->key_queue_len];
695 			tilem_keypad_press_key(calc, nextkey);
696 			emu->key_queue_pressed = 1;
697 			emu->key_queue_cur = nextkey;
698 			tilem_z80_set_timer(calc, emu->key_queue_timer,
699 			                    20000, 0, 1);
700 		}
701 		else {
702 			tilem_z80_remove_timer(calc, emu->key_queue_timer);
703 			emu->key_queue_timer = 0;
704 		}
705 	}
706 
707 	g_idle_add(&refresh_kpd, emu);
708 }
709 
queue_keys(TilemCalcEmulator * emu,const byte * keys,int nkeys)710 static void queue_keys(TilemCalcEmulator *emu, const byte *keys, int nkeys)
711 {
712 	byte *q;
713 	int i;
714 
715 	q = g_new(byte, emu->key_queue_len + nkeys);
716 
717 	for (i = 0; i < nkeys; i++) {
718 		q[nkeys - i - 1] = keys[i];
719 		record_key(emu, keys[i]);
720 	}
721 
722 	if (emu->key_queue_len)
723 		memcpy(q + nkeys, emu->key_queue, emu->key_queue_len);
724 
725 	g_free(emu->key_queue);
726 	emu->key_queue = q;
727 	emu->key_queue_len += nkeys;
728 	emu->key_queue_hold = 1;
729 
730 	if (!emu->key_queue_timer) {
731 		emu->key_queue_timer
732 			= tilem_z80_add_timer(emu->calc, 1, 0, 1,
733 			                      &tmr_key_queue, emu);
734 	}
735 }
736 
tilem_calc_emulator_queue_keys(TilemCalcEmulator * emu,const byte * keys,int nkeys)737 void tilem_calc_emulator_queue_keys(TilemCalcEmulator *emu,
738                                     const byte *keys, int nkeys)
739 {
740 	g_return_if_fail(emu != NULL);
741 	g_return_if_fail(keys != NULL);
742 	g_return_if_fail(nkeys > 0);
743 
744 	tilem_calc_emulator_lock(emu);
745 	queue_keys(emu, keys, nkeys);
746 	tilem_calc_emulator_unlock(emu);
747 }
748 
tilem_calc_emulator_release_queued_key(TilemCalcEmulator * emu)749 void tilem_calc_emulator_release_queued_key(TilemCalcEmulator *emu)
750 {
751 	g_return_if_fail(emu != NULL);
752 
753 	tilem_calc_emulator_lock(emu);
754 	if (emu->key_queue_timer) {
755 		emu->key_queue_hold = 0;
756 	}
757 	else if (emu->key_queue_pressed) {
758 		tilem_keypad_release_key(emu->calc, emu->key_queue_cur);
759 		emu->key_queue_pressed = 0;
760 		emu->key_queue_cur = 0;
761 	}
762 	tilem_calc_emulator_unlock(emu);
763 
764 	if (emu->dbg && emu->dbg->keypad_dialog)
765 		tilem_keypad_dialog_refresh(emu->dbg->keypad_dialog);
766 }
767 
tilem_calc_emulator_press_or_queue(TilemCalcEmulator * emu,int key)768 gboolean tilem_calc_emulator_press_or_queue(TilemCalcEmulator *emu,
769                                             int key)
770 {
771 	byte b;
772 	gboolean status;
773 
774 	g_return_val_if_fail(emu != NULL, FALSE);
775 
776 	tilem_calc_emulator_lock(emu);
777 
778 	if (emu->key_queue_timer) {
779 		b = key;
780 		queue_keys(emu, &b, 1);
781 		status = TRUE;
782 	}
783 	else {
784 		tilem_keypad_press_key(emu->calc, key);
785 		status = FALSE;
786 	}
787 	tilem_calc_emulator_unlock(emu);
788 
789 	if (emu->dbg && emu->dbg->keypad_dialog)
790 		tilem_keypad_dialog_refresh(emu->dbg->keypad_dialog);
791 
792 	return status;
793 }
794 
tilem_calc_emulator_get_screenshot(TilemCalcEmulator * emu,gboolean grayscale)795 TilemAnimation * tilem_calc_emulator_get_screenshot(TilemCalcEmulator *emu,
796                                                     gboolean grayscale)
797 {
798 	TilemAnimation *anim;
799 
800 	g_return_val_if_fail(emu != NULL, NULL);
801 	g_return_val_if_fail(emu->calc != NULL, NULL);
802 
803 	anim = tilem_animation_new(emu->calc->hw.lcdwidth,
804 	                           emu->calc->hw.lcdheight);
805 
806 	tilem_calc_emulator_lock(emu);
807 
808 	if (grayscale && emu->glcd)
809 		tilem_gray_lcd_get_frame(emu->glcd, emu->tmp_lcd_buffer);
810 	else
811 		tilem_lcd_get_frame(emu->calc, emu->tmp_lcd_buffer);
812 
813 	tilem_animation_append_frame(anim, emu->tmp_lcd_buffer, 1);
814 
815 	tilem_calc_emulator_unlock(emu);
816 
817 	return anim;
818 }
819 
tilem_calc_emulator_begin_animation(TilemCalcEmulator * emu,gboolean grayscale)820 void tilem_calc_emulator_begin_animation(TilemCalcEmulator *emu,
821                                          gboolean grayscale)
822 {
823 	g_return_if_fail(emu != NULL);
824 	g_return_if_fail(emu->calc != NULL);
825 
826 	tilem_calc_emulator_lock(emu);
827 	cancel_animation(emu);
828 	emu->anim = tilem_animation_new(emu->calc->hw.lcdwidth,
829 	                                emu->calc->hw.lcdheight);
830 	emu->anim_grayscale = grayscale;
831 	tilem_calc_emulator_unlock(emu);
832 }
833 
tilem_calc_emulator_end_animation(TilemCalcEmulator * emu)834 TilemAnimation * tilem_calc_emulator_end_animation(TilemCalcEmulator *emu)
835 {
836 	TilemAnimation *anim;
837 
838 	g_return_val_if_fail(emu != NULL, NULL);
839 	g_return_val_if_fail(emu->anim != NULL, NULL);
840 
841 	tilem_calc_emulator_lock(emu);
842 	anim = emu->anim;
843 	emu->anim = NULL;
844 	tilem_calc_emulator_unlock(emu);
845 
846 	return anim;
847 }
848 
849 /* Prompt for a ROM file to open */
tilem_calc_emulator_prompt_open_rom(TilemCalcEmulator * emu)850 int tilem_calc_emulator_prompt_open_rom(TilemCalcEmulator *emu)
851 {
852 	char *dir, *filename;
853 	GError *err = NULL;
854 	const char *modelname;
855 
856 	if (emu->rom_file_name)
857 		dir = g_path_get_dirname(emu->rom_file_name);
858 	else
859 		dir = g_get_current_dir();
860 
861 	filename = prompt_open_file("Open Calculator", GTK_WINDOW(get_toplevel(emu)),
862 	                            dir, "ROM files", "*.rom;*.clc;*.bin",
863 	                            "All files", "*", NULL);
864 	g_free(dir);
865 	if (!filename)
866 		return 0;
867 
868 	if (tilem_calc_emulator_load_state(emu, filename, NULL,
869 	                                   0, &err)) {
870 		modelname = emu->calc->hw.name;
871 		tilem_config_set(modelname,
872 		                 "rom_file/f", emu->rom_file_name,
873 		                 "state_file/f", emu->state_file_name,
874 		                 NULL);
875 		tilem_config_set("recent", "last_model/s", modelname, NULL);
876 	}
877 	g_free(filename);
878 
879 	if (err) {
880 		messagebox01(GTK_WINDOW(get_toplevel(emu)), GTK_MESSAGE_ERROR,
881 		             "Unable to load calculator state",
882 		             "%s", err->message);
883 		g_error_free(err);
884 		return -1;
885 	}
886 	else {
887 		return 1;
888 	}
889 }
890 
891 /* Run slowly to play macro (used instead run_with_key() function) */
run_with_key_slowly(TilemCalc * calc,int key)892 void run_with_key_slowly(TilemCalc* calc, int key)
893 {
894 	tilem_z80_run_time(calc, 5000000, NULL); /* Wait */
895 	tilem_keypad_press_key(calc, key); /* Press */
896 	tilem_z80_run_time(calc, 10000, NULL); /* Wait (don't forget to wait) */
897 	tilem_keypad_release_key(calc, key); /* Release */
898 	tilem_z80_run_time(calc, 50, NULL); /* Wait */
899 }
900