1 // license:BSD-3-Clause
2 // copyright-holders:Aaron Giles, Vas Crabb
3 //============================================================
4 //
5 //  debugwininfo.c - Win32 debug window handling
6 //
7 //============================================================
8 
9 #include "emu.h"
10 #include "debugwininfo.h"
11 
12 #include "debugviewinfo.h"
13 
14 #include "debugger.h"
15 #include "debug/debugcon.h"
16 #include "debug/debugcpu.h"
17 #include "window.h"
18 #include "winutf8.h"
19 
20 #include "winutil.h"
21 #include "modules/lib/osdobj_common.h"
22 
23 
24 bool debugwin_info::s_window_class_registered = false;
25 
26 
debugwin_info(debugger_windows_interface & debugger,bool is_main_console,LPCSTR title,WNDPROC handler)27 debugwin_info::debugwin_info(debugger_windows_interface &debugger, bool is_main_console, LPCSTR title, WNDPROC handler) :
28 	debugbase_info(debugger),
29 	m_is_main_console(is_main_console),
30 	m_wnd(nullptr),
31 	m_handler(handler),
32 	m_minwidth(200),
33 	m_maxwidth(0),
34 	m_minheight(200),
35 	m_maxheight(0),
36 	m_ignore_char_lparam(0)
37 {
38 	register_window_class();
39 
40 	m_wnd = win_create_window_ex_utf8(DEBUG_WINDOW_STYLE_EX, "MAMEDebugWindow", title, DEBUG_WINDOW_STYLE,
41 			0, 0, 100, 100, std::static_pointer_cast<win_window_info>(osd_common_t::s_window_list.front())->platform_window(), create_standard_menubar(), GetModuleHandleUni(), this);
42 	if (m_wnd == nullptr)
43 		return;
44 
45 	RECT work_bounds;
46 	SystemParametersInfo(SPI_GETWORKAREA, 0, &work_bounds, 0);
47 	m_maxwidth = work_bounds.right - work_bounds.left;
48 	m_maxheight = work_bounds.bottom - work_bounds.top;
49 }
50 
51 
~debugwin_info()52 debugwin_info::~debugwin_info()
53 {
54 }
55 
56 
destroy()57 void debugwin_info::destroy()
58 {
59 	for (int curview = 0; curview < MAX_VIEWS; curview++)
60 		m_views[curview].reset();
61 	DestroyWindow(m_wnd);
62 }
63 
set_default_focus()64 bool debugwin_info::set_default_focus()
65 {
66 	return false;
67 }
68 
69 
prev_view(debugview_info * curview)70 void debugwin_info::prev_view(debugview_info *curview)
71 {
72 	// count the number of views
73 	int numviews;
74 	for (numviews = 0; numviews < MAX_VIEWS; numviews++)
75 	{
76 		if (m_views[numviews] == nullptr)
77 			break;
78 	}
79 
80 	// if we have a curview, find out its index
81 	int curindex = 1;
82 	if (curview)
83 	{
84 		for (curindex = numviews - 1; curindex > 0; curindex--)
85 		{
86 			if (m_views[curindex].get() == curview)
87 				break;
88 		}
89 		if (curindex < 0)
90 			curindex = 1;
91 	}
92 
93 	// loop until we find someone to take focus
94 	while (1)
95 	{
96 		// advance to the previous index
97 		curindex--;
98 		if (curindex < -1)
99 			curindex = numviews - 1;
100 
101 		if (curindex < 0 && set_default_focus())
102 		{
103 			// negative numbers mean the focuswnd
104 			break;
105 		}
106 		else if (curindex >= 0 && m_views[curindex] != nullptr && m_views[curindex]->cursor_supported())
107 		{
108 			// positive numbers mean a view
109 			m_views[curindex]->set_focus();
110 			break;
111 		}
112 	}
113 }
114 
115 
next_view(debugview_info * curview)116 void debugwin_info::next_view(debugview_info *curview)
117 {
118 	// count the number of views
119 	int numviews;
120 	for (numviews = 0; numviews < MAX_VIEWS; numviews++)
121 	{
122 		if (m_views[numviews] == nullptr)
123 			break;
124 	}
125 
126 	// if we have a curview, find out its index
127 	int curindex = -1;
128 	if (curview)
129 	{
130 		for (curindex = numviews - 1; curindex > 0; curindex--)
131 		{
132 			if (m_views[curindex].get() == curview)
133 				break;
134 		}
135 	}
136 
137 	// loop until we find someone to take focus
138 	while (1)
139 	{
140 		// advance to the previous index
141 		curindex++;
142 		if (curindex >= numviews)
143 			curindex = -1;
144 
145 		if (curindex < 0 && set_default_focus())
146 		{
147 			// negative numbers mean the focuswnd
148 			break;
149 		}
150 		else if (curindex >= 0 && m_views[curindex] != nullptr && m_views[curindex]->cursor_supported())
151 		{
152 			// positive numbers mean a view
153 			m_views[curindex]->set_focus();
154 			break;
155 		}
156 	}
157 }
158 
159 
handle_key(WPARAM wparam,LPARAM lparam)160 bool debugwin_info::handle_key(WPARAM wparam, LPARAM lparam)
161 {
162 	/* ignore any keys that are received while the debug key is down */
163 	if (!waiting_for_debugger() && seq_pressed())
164 		return true;
165 
166 	switch (wparam)
167 	{
168 	case VK_F3:
169 		if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
170 			SendMessage(m_wnd, WM_COMMAND, ID_HARD_RESET, 0);
171 		else
172 			SendMessage(m_wnd, WM_COMMAND, ID_SOFT_RESET, 0);
173 		return true;
174 
175 	case VK_F4:
176 		if (GetAsyncKeyState(VK_MENU) & 0x8000)
177 		{
178 			/* ajg - never gets here since 'alt' seems to be captured somewhere else - menu maybe? */
179 			SendMessage(m_wnd, WM_COMMAND, ID_EXIT, 0);
180 			return true;
181 		}
182 		break;
183 
184 	case VK_F5:
185 		SendMessage(m_wnd, WM_COMMAND, ID_RUN, 0);
186 		return true;
187 
188 	case VK_F6:
189 		SendMessage(m_wnd, WM_COMMAND, ID_NEXT_CPU, 0);
190 		return true;
191 
192 	case VK_F7:
193 		SendMessage(m_wnd, WM_COMMAND, ID_RUN_IRQ, 0);
194 		return true;
195 
196 	case VK_F8:
197 		SendMessage(m_wnd, WM_COMMAND, ID_RUN_VBLANK, 0);
198 		return true;
199 
200 	case VK_F10:
201 		SendMessage(m_wnd, WM_COMMAND, ID_STEP_OVER, 0);
202 		return true;
203 
204 	case VK_F11:
205 		if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) && ((GetAsyncKeyState(VK_CONTROL) & 0x8000) == 0))
206 			SendMessage(m_wnd, WM_COMMAND, ID_STEP_OUT, 0);
207 		else if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
208 			SendMessage(m_wnd, WM_COMMAND, ID_REWIND_STEP, 0);
209 		else
210 			SendMessage(m_wnd, WM_COMMAND, ID_STEP, 0);
211 		return true;
212 
213 	case VK_F12:
214 		SendMessage(m_wnd, WM_COMMAND, ID_RUN_AND_HIDE, 0);
215 		return true;
216 
217 	case 'M':
218 		if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
219 		{
220 			SendMessage(m_wnd, WM_COMMAND, ID_NEW_MEMORY_WND, 0);
221 			return true;
222 		}
223 		break;
224 
225 	case 'D':
226 		if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
227 		{
228 			SendMessage(m_wnd, WM_COMMAND, ID_NEW_DISASM_WND, 0);
229 			return true;
230 		}
231 		break;
232 
233 	case 'L':
234 		if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
235 		{
236 			SendMessage(m_wnd, WM_COMMAND, ID_NEW_LOG_WND, 0);
237 			return true;
238 		}
239 		break;
240 
241 	case 'B':
242 		if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
243 		{
244 			SendMessage(m_wnd, WM_COMMAND, ID_NEW_POINTS_WND, 0);
245 			return true;
246 		}
247 		break;
248 	}
249 
250 	return false;
251 }
252 
253 
recompute_children()254 void debugwin_info::recompute_children()
255 {
256 	if (m_views[0] != nullptr)
257 	{
258 		// compute a client rect
259 		RECT bounds;
260 		bounds.top = bounds.left = 0;
261 		bounds.right = m_views[0]->prefwidth() + (2 * EDGE_WIDTH);
262 		bounds.bottom = 200;
263 		AdjustWindowRectEx(&bounds, DEBUG_WINDOW_STYLE, FALSE, DEBUG_WINDOW_STYLE_EX);
264 
265 		// clamp the min/max size
266 		set_maxwidth(bounds.right - bounds.left);
267 
268 		// get the parent's dimensions
269 		RECT parent;
270 		GetClientRect(window(), &parent);
271 
272 		// view gets the remaining space
273 		InflateRect(&parent, -EDGE_WIDTH, -EDGE_WIDTH);
274 		m_views[0]->set_bounds(parent);
275 	}
276 }
277 
278 
handle_command(WPARAM wparam,LPARAM lparam)279 bool debugwin_info::handle_command(WPARAM wparam, LPARAM lparam)
280 {
281 	if (HIWORD(wparam) == 0)
282 	{
283 		switch (LOWORD(wparam))
284 		{
285 		case ID_NEW_MEMORY_WND:
286 			debugger().create_memory_window();
287 			return true;
288 
289 		case ID_NEW_DISASM_WND:
290 			debugger().create_disasm_window();
291 			return true;
292 
293 		case ID_NEW_LOG_WND:
294 			debugger().create_log_window();
295 			return true;
296 
297 		case ID_NEW_POINTS_WND:
298 			debugger().create_points_window();
299 			return true;
300 
301 		case ID_RUN_AND_HIDE:
302 			debugger().hide_all();
303 		case ID_RUN:
304 			machine().debugger().console().get_visible_cpu()->debug()->go();
305 			return true;
306 
307 		case ID_NEXT_CPU:
308 			machine().debugger().console().get_visible_cpu()->debug()->go_next_device();
309 			return true;
310 
311 		case ID_RUN_VBLANK:
312 			machine().debugger().console().get_visible_cpu()->debug()->go_vblank();
313 			return true;
314 
315 		case ID_RUN_IRQ:
316 			machine().debugger().console().get_visible_cpu()->debug()->go_interrupt();
317 			return true;
318 
319 		case ID_STEP:
320 			machine().debugger().console().get_visible_cpu()->debug()->single_step();
321 			return true;
322 
323 		case ID_STEP_OVER:
324 			machine().debugger().console().get_visible_cpu()->debug()->single_step_over();
325 			return true;
326 
327 		case ID_STEP_OUT:
328 			machine().debugger().console().get_visible_cpu()->debug()->single_step_out();
329 			return true;
330 
331 		case ID_REWIND_STEP:
332 			machine().rewind_step();
333 
334 			// clear all PC & memory tracks
335 			for (device_t &device : device_iterator(machine().root_device()))
336 			{
337 				device.debug()->track_pc_data_clear();
338 				device.debug()->track_mem_data_clear();
339 			}
340 
341 			// update debugger and emulator window
342 			machine().debug_view().update_all();
343 			machine().debugger().refresh_display();
344 			return true;
345 
346 		case ID_HARD_RESET:
347 			machine().schedule_hard_reset();
348 			return true;
349 
350 		case ID_SOFT_RESET:
351 			machine().schedule_soft_reset();
352 			machine().debugger().console().get_visible_cpu()->debug()->go();
353 			return true;
354 
355 		case ID_EXIT:
356 			set_default_focus();
357 			machine().schedule_exit();
358 			return true;
359 		}
360 	}
361 	return false;
362 }
363 
364 
draw_contents(HDC dc)365 void debugwin_info::draw_contents(HDC dc)
366 {
367 	// fill the background with light gray
368 	RECT parent;
369 	GetClientRect(m_wnd, &parent);
370 	FillRect(dc, &parent, (HBRUSH)GetStockObject(LTGRAY_BRUSH));
371 
372 	// draw edges around all views
373 	for (int curview = 0; curview < MAX_VIEWS; curview++)
374 	{
375 		if (m_views[curview] != nullptr)
376 		{
377 			RECT bounds;
378 			m_views[curview]->get_bounds(bounds);
379 			draw_border(dc, bounds);
380 		}
381 	}
382 }
383 
384 
draw_border(HDC dc,RECT & bounds)385 void debugwin_info::draw_border(HDC dc, RECT &bounds)
386 {
387 	ScreenToClient(m_wnd, &((POINT *)&bounds)[0]);
388 	ScreenToClient(m_wnd, &((POINT *)&bounds)[1]);
389 	InflateRect(&bounds, EDGE_WIDTH, EDGE_WIDTH);
390 	DrawEdge(dc, &bounds, EDGE_SUNKEN, BF_RECT);
391 }
392 
393 
draw_border(HDC dc,HWND child)394 void debugwin_info::draw_border(HDC dc, HWND child)
395 {
396 	RECT bounds;
397 	GetWindowRect(child, &bounds);
398 	draw_border(dc, bounds);
399 }
400 
401 
window_proc(UINT message,WPARAM wparam,LPARAM lparam)402 LRESULT debugwin_info::window_proc(UINT message, WPARAM wparam, LPARAM lparam)
403 {
404 	// handle a few messages
405 	switch (message)
406 	{
407 	// paint: draw bezels as necessary
408 	case WM_PAINT:
409 		{
410 			PAINTSTRUCT pstruct;
411 			HDC dc = BeginPaint(m_wnd, &pstruct);
412 			draw_contents(dc);
413 			EndPaint(m_wnd, &pstruct);
414 			break;
415 		}
416 
417 	// keydown: handle debugger keys
418 	case WM_KEYDOWN:
419 		if (handle_key(wparam, lparam))
420 			set_ignore_char_lparam(lparam);
421 		break;
422 
423 	// char: ignore chars associated with keys we've handled
424 	case WM_CHAR:
425 		if (check_ignore_char_lparam(lparam))
426 		{
427 			if (waiting_for_debugger() || !seq_pressed())
428 				return DefWindowProc(m_wnd, message, wparam, lparam);
429 		}
430 		break;
431 
432 	// activate: set the focus
433 	case WM_ACTIVATE:
434 		if (wparam != WA_INACTIVE)
435 			set_default_focus();
436 		break;
437 
438 	// get min/max info: set the minimum window size
439 	case WM_GETMINMAXINFO:
440 		{
441 			auto *minmax = (MINMAXINFO *)lparam;
442 			minmax->ptMinTrackSize.x = m_minwidth;
443 			minmax->ptMinTrackSize.y = m_minheight;
444 			minmax->ptMaxSize.x = minmax->ptMaxTrackSize.x = m_maxwidth;
445 			minmax->ptMaxSize.y = minmax->ptMaxTrackSize.y = m_maxheight;
446 			break;
447 		}
448 
449 	// sizing: recompute child window locations
450 	case WM_SIZE:
451 	case WM_SIZING:
452 		recompute_children();
453 		InvalidateRect(m_wnd, nullptr, FALSE);
454 		break;
455 
456 	// mouse wheel: forward to the first view
457 	case WM_MOUSEWHEEL:
458 		{
459 			static int units_carryover = 0;
460 
461 			UINT lines_per_click;
462 			if (!SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &lines_per_click, 0))
463 				lines_per_click = 3;
464 
465 			int const units = GET_WHEEL_DELTA_WPARAM(wparam) + units_carryover;
466 			int const clicks = units / WHEEL_DELTA;
467 			units_carryover = units % WHEEL_DELTA;
468 
469 			int const delta = clicks * lines_per_click;
470 			int viewnum = 0;
471 			POINT point;
472 
473 			// figure out which view we are hovering over
474 			GetCursorPos(&point);
475 			ScreenToClient(m_wnd, &point);
476 			HWND const child = ChildWindowFromPoint(m_wnd, point);
477 			if (child)
478 			{
479 				for (viewnum = 0; viewnum < MAX_VIEWS; viewnum++)
480 				{
481 					if ((m_views[viewnum] != nullptr) && m_views[viewnum]->owns_window(child))
482 						break;
483 				}
484 				if (viewnum == MAX_VIEWS)
485 					break;
486 			}
487 
488 			// send the appropriate message to this view's scrollbar
489 			if (m_views[viewnum] != nullptr)
490 				m_views[viewnum]->send_vscroll(delta);
491 
492 			break;
493 		}
494 
495 	// activate: set the focus
496 	case WM_INITMENU:
497 		update_menu();
498 		break;
499 
500 	// command: handle a comment
501 	case WM_COMMAND:
502 		if (!handle_command(wparam, lparam))
503 			return DefWindowProc(m_wnd, message, wparam, lparam);
504 		break;
505 
506 	// close: close the window if it's not the main console
507 	case WM_CLOSE:
508 		if (m_is_main_console)
509 		{
510 			debugger().hide_all();
511 			machine().debugger().console().get_visible_cpu()->debug()->go();
512 		}
513 		else
514 		{
515 			destroy();
516 		}
517 		break;
518 
519 	// destroy: close down the window
520 	case WM_NCDESTROY:
521 		m_wnd = nullptr;
522 		debugger().remove_window(*this);
523 		break;
524 
525 	// everything else: defaults
526 	default:
527 		return DefWindowProc(m_wnd, message, wparam, lparam);
528 	}
529 
530 	return 0;
531 }
532 
533 
create_standard_menubar()534 HMENU debugwin_info::create_standard_menubar()
535 {
536 	// create the debug menu
537 	HMENU const debugmenu = CreatePopupMenu();
538 	if (debugmenu == nullptr)
539 		return nullptr;
540 	AppendMenu(debugmenu, MF_ENABLED, ID_NEW_MEMORY_WND, TEXT("New Memory Window\tCtrl+M"));
541 	AppendMenu(debugmenu, MF_ENABLED, ID_NEW_DISASM_WND, TEXT("New Disassembly Window\tCtrl+D"));
542 	AppendMenu(debugmenu, MF_ENABLED, ID_NEW_LOG_WND, TEXT("New Error Log Window\tCtrl+L"));
543 	AppendMenu(debugmenu, MF_ENABLED, ID_NEW_POINTS_WND, TEXT("New (Break|Watch)points Window\tCtrl+B"));
544 	AppendMenu(debugmenu, MF_DISABLED | MF_SEPARATOR, 0, TEXT(""));
545 	AppendMenu(debugmenu, MF_ENABLED, ID_RUN, TEXT("Run\tF5"));
546 	AppendMenu(debugmenu, MF_ENABLED, ID_RUN_AND_HIDE, TEXT("Run and Hide Debugger\tF12"));
547 	AppendMenu(debugmenu, MF_ENABLED, ID_NEXT_CPU, TEXT("Run to Next CPU\tF6"));
548 	AppendMenu(debugmenu, MF_ENABLED, ID_RUN_IRQ, TEXT("Run until Next Interrupt on This CPU\tF7"));
549 	AppendMenu(debugmenu, MF_ENABLED, ID_RUN_VBLANK, TEXT("Run until Next VBLANK\tF8"));
550 	AppendMenu(debugmenu, MF_DISABLED | MF_SEPARATOR, 0, TEXT(""));
551 	AppendMenu(debugmenu, MF_ENABLED, ID_STEP, TEXT("Step Into\tF11"));
552 	AppendMenu(debugmenu, MF_ENABLED, ID_STEP_OVER, TEXT("Step Over\tF10"));
553 	AppendMenu(debugmenu, MF_ENABLED, ID_STEP_OUT, TEXT("Step Out\tShift+F11"));
554 	AppendMenu(debugmenu, MF_ENABLED, ID_REWIND_STEP, TEXT("Rewind Step\tCtrl+F11"));
555 	AppendMenu(debugmenu, MF_DISABLED | MF_SEPARATOR, 0, TEXT(""));
556 	AppendMenu(debugmenu, MF_ENABLED, ID_SOFT_RESET, TEXT("Soft Reset\tF3"));
557 	AppendMenu(debugmenu, MF_ENABLED, ID_HARD_RESET, TEXT("Hard Reset\tShift+F3"));
558 	AppendMenu(debugmenu, MF_ENABLED, ID_EXIT, TEXT("Exit"));
559 
560 	// create the menu bar
561 	HMENU const menubar = CreateMenu();
562 	if (menubar == nullptr)
563 	{
564 		DestroyMenu(debugmenu);
565 		return nullptr;
566 	}
567 	AppendMenu(menubar, MF_ENABLED | MF_POPUP, (UINT_PTR)debugmenu, TEXT("Debug"));
568 
569 	return menubar;
570 }
571 
572 
static_window_proc(HWND wnd,UINT message,WPARAM wparam,LPARAM lparam)573 LRESULT CALLBACK debugwin_info::static_window_proc(HWND wnd, UINT message, WPARAM wparam, LPARAM lparam)
574 {
575 	if (message == WM_CREATE)
576 	{
577 		// set the info pointer
578 		CREATESTRUCT const *const createinfo = (CREATESTRUCT *)lparam;
579 		auto *const info = (debugwin_info *)createinfo->lpCreateParams;
580 		SetWindowLongPtr(wnd, GWLP_USERDATA, (LONG_PTR)createinfo->lpCreateParams);
581 		if (info->m_handler)
582 			SetWindowLongPtr(wnd, GWLP_WNDPROC, (LONG_PTR)info->m_handler);
583 		return 0;
584 	}
585 
586 	auto *const info = (debugwin_info *)(uintptr_t)GetWindowLongPtr(wnd, GWLP_USERDATA);
587 	if (info == nullptr)
588 		return DefWindowProc(wnd, message, wparam, lparam);
589 
590 	assert((info->m_wnd == wnd) || (info->m_wnd == nullptr));
591 	return info->window_proc(message, wparam, lparam);
592 }
593 
594 
register_window_class()595 void debugwin_info::register_window_class()
596 {
597 	if (!s_window_class_registered)
598 	{
599 		WNDCLASS wc = { 0 };
600 
601 		// initialize the description of the window class
602 		wc.lpszClassName    = TEXT("MAMEDebugWindow");
603 		wc.hInstance        = GetModuleHandleUni();
604 		wc.lpfnWndProc      = &debugwin_info::static_window_proc;
605 		wc.hCursor          = LoadCursor(nullptr, IDC_ARROW);
606 		wc.hIcon            = LoadIcon(wc.hInstance, MAKEINTRESOURCE(2));
607 		wc.lpszMenuName     = nullptr;
608 		wc.hbrBackground    = nullptr;
609 		wc.style            = 0;
610 		wc.cbClsExtra       = 0;
611 		wc.cbWndExtra       = 0;
612 
613 		UnregisterClass(wc.lpszClassName, wc.hInstance);
614 
615 		// register the class; fail if we can't
616 		if (!RegisterClass(&wc))
617 			fatalerror("Unable to register debug window class\n");
618 
619 		s_window_class_registered = true;
620 	}
621 }
622