This repository has been archived on 2025-05-24. You can view files and clone it, but cannot push or open issues or pull requests.
Files
VARCem/src/win/win_ui.c
waltje 636fcfbacb Many more cleanups all over, renaming variables for consistency etc.
More cleanups in the keyboard drivers, especially the AT one (with new changes from 86Box.)
Small update to fix the CD sound volume when using SoundBlaster.
2019-04-28 00:54:35 -05:00

1309 lines
30 KiB
C

/*
* VARCem Virtual ARchaeological Computer EMulator.
* An emulator of (mostly) x86-based PC systems and devices,
* using the ISA,EISA,VLB,MCA and PCI system buses, roughly
* spanning the era between 1981 and 1995.
*
* This file is part of the VARCem Project.
*
* Implement the user Interface module.
*
* Version: @(#)win_ui.c 1.0.35 2019/04/26
*
* Authors: Fred N. van Kempen, <decwiz@yahoo.com>
* Miran Grca, <mgrca8@gmail.com>
* Sarah Walker, <tommowalker@tommowalker.co.uk>
*
* Copyright 2017-2019 Fred N. van Kempen.
* Copyright 2016-2018 Miran Grca.
* Copyright 2008-2018 Sarah Walker.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the:
*
* Free Software Foundation, Inc.
* 59 Temple Place - Suite 330
* Boston, MA 02111-1307
* USA.
*/
#define UNICODE
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <commdlg.h>
#ifdef USE_HOST_CDROM
# include <dbt.h>
#endif
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <wchar.h>
#include "../emu.h"
#include "../version.h"
#include "../config.h"
#include "../device.h"
#include "../ui/ui.h"
#include "../plat.h"
#include "../devices/input/keyboard.h"
#include "../devices/input/mouse.h"
#include "../devices/video/video.h"
#include "../devices/cdrom/cdrom.h"
#include "win.h"
#include "resource.h"
#define TIMER_1SEC 1 /* ID of the one-second timer */
#define ICONS_MAX 256 /* number of icons we can cache */
/* Platform Public data, specific. */
HWND hwndMain = NULL, /* application main window */
hwndRender = NULL; /* machine render window */
HICON hIcon[ICONS_MAX]; /* icon data loaded from resources */
RECT oldclip; /* mouse rect */
/* Local data. */
static wchar_t wTitle[512];
static HHOOK hKeyboardHook;
static LONG_PTR input_orig_proc,
stbar_orig_proc;
static HWND input_orig_hwnd = NULL,
hwndSBAR = NULL; /* application status bar */
static HMENU menuMain = NULL, /* application menu bar */
menuSBAR = NULL, /* status bar menu bar */
*sb_menu = NULL;
static int sb_nparts = 0;
static const sbpart_t *sb_parts = NULL;
static int infocus = 1;
static int hook_enabled = 0;
static int save_window_pos = 0;
static int cruft_x = 0,
cruft_y = 0,
cruft_sb = 0;
static int kbd_flags, /* current keyboard flags */
win_kbd_flags; /* original host keyboard flags */
static VOID APIENTRY
PopupMenu(HWND hwnd, POINT pt, int part)
{
if (part >= (sb_nparts - 1)) return;
pt.x = part * SB_ICON_WIDTH; /* justify to the left */
pt.y = 1; /* justify to the top */
ClientToScreen(hwnd, (LPPOINT)&pt);
TrackPopupMenu(sb_menu[part],
TPM_LEFTALIGN | TPM_BOTTOMALIGN | TPM_LEFTBUTTON,
pt.x, pt.y, 0, hwndSBAR, NULL);
}
/* Handle messages for the Status Bar window. */
static WIN_RESULT CALLBACK
sb_dlg_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
RECT r;
POINT pt;
int idm, tag;
switch (message) {
case WM_COMMAND:
idm = LOWORD(wParam) & 0xff00; /* low 8 bits */
tag = LOWORD(wParam) & 0x00ff; /* high 8 bits */
ui_sb_menu_command(idm, tag);
return(0);
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
GetClientRect(hwnd, (LPRECT)&r);
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
if (PtInRect((LPRECT)&r, pt))
PopupMenu(hwnd, pt, (pt.x / SB_ICON_WIDTH));
break;
case WM_LBUTTONDBLCLK:
GetClientRect(hwnd, (LPRECT)&r);
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
tag = (pt.x / SB_ICON_WIDTH);
if (PtInRect((LPRECT)&r, pt))
ui_sb_click(tag);
break;
default:
return(CallWindowProc((WNDPROC)stbar_orig_proc,
hwnd, message, wParam, lParam));
}
return(0);
}
/* Create and set up the Status Bar window. */
static void
StatusBarCreate(uintptr_t id)
{
int borders[3];
intptr_t i;
int dw, dh;
RECT r;
/* Load our icons into the cache for faster access. */
for (i = 0; i < ICONS_MAX; i++)
hIcon[i] = LoadIconEx((PCTSTR)i);
GetWindowRect(hwndMain, &r);
dw = r.right - r.left;
dh = r.bottom - r.top;
/* Create the window. */
hwndSBAR = CreateWindow(STATUSCLASSNAME,
NULL,
WS_CHILD|WS_VISIBLE|SBT_TOOLTIPS|SBARS_SIZEGRIP,
0, dh,
dw, SB_HEIGHT,
hwndMain,
(HMENU)id,
hInstance,
NULL);
/* Retrieve the width of the border the status bar got. */
memset(borders, 0x00, sizeof(borders));
SendMessage(hwndSBAR, SB_GETBORDERS, 0, (LPARAM)borders);
if (borders[1] == 0)
borders[1] = 2;
cruft_sb = SB_HEIGHT + (2 * borders[1]) + (2 * SB_PADDING);
/* Replace the original procedure with ours. */
stbar_orig_proc = GetWindowLongPtr(hwndSBAR, GWLP_WNDPROC);
SetWindowLongPtr(hwndSBAR, GWLP_WNDPROC, (LONG_PTR)sb_dlg_proc);
SendMessage(hwndSBAR, SB_SETMINHEIGHT, (WPARAM)SB_HEIGHT, (LPARAM)0);
/* Load the dummy menu for this window. */
menuSBAR = LoadMenu(hInstance, MENU_SB_NAME);
/* Clear the menus, just in case.. */
sb_nparts = 0;
sb_menu = NULL;
}
/*
* Calculate the edges of all status bar parts.
*
* We do it here, and in the platform module, because not all
* systems implement them the same way Microsoft did. For that
* reason, the UI code actually specifies widths for all the
* parts, which we "convert" here.
*/
static void
StatusBarResize(int w)
{
int i, *edges;
int j, k, x;
int grippy;
/* If no parts yet, bail out. */
if (sb_nparts == 0) return;
/* Create local 'edges' array and populate it. */
edges = (int *)mem_alloc(sb_nparts * sizeof(int));
memset(edges, 0x00, sb_nparts * sizeof(int));
/* If we have a grippy on the status bar... */
//FIXME: how to determine this programmatically? --FvK
grippy = 8;
/*
* We start at the leftmost edge of the window, and
* then move towards the right. The parameter given
* to Windows is the *right* side edge!
*
* No variable-part offset yet.
*/
k = x = 0;
for (i = 0; i < sb_nparts; i++) {
if (sb_parts[i].width == 0) {
/*
* This is the variable-length part, which
* extends to the right edge of the window.
* So, we must get the window length, and
* then calculate length for this part, and
* then the position for all parts following.
*
* First, see how much space we need for the
* parts following this one.
*/
k = w - x - grippy;
for (j = i + 1; j < sb_nparts; j++)
k -= sb_parts[j].width;
/* OK, now we know how wide this part can be. */
edges[i] = x + k;
} else {
x += sb_parts[i].width;
if (i == (sb_nparts - 1))
x += grippy;
edges[i] = k + x;
}
}
/* Send the list to the status bar window. */
SendMessage(hwndSBAR, SB_SETPARTS, (WPARAM)sb_nparts, (LPARAM)edges);
free(edges);
}
static LRESULT CALLBACK
LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
BOOL bControlKeyDown;
KBDLLHOOKSTRUCT *p;
if (nCode < 0 || nCode != HC_ACTION || (!mouse_capture && !vid_fullscreen))
return(CallNextHookEx(hKeyboardHook, nCode, wParam, lParam));
p = (KBDLLHOOKSTRUCT*)lParam;
/* disable alt-tab */
if (p->vkCode == VK_TAB && p->flags & LLKHF_ALTDOWN) return(1);
/* disable alt-space */
if (p->vkCode == VK_SPACE && p->flags & LLKHF_ALTDOWN) return(1);
/* disable alt-escape */
if (p->vkCode == VK_ESCAPE && p->flags & LLKHF_ALTDOWN) return(1);
/* disable windows keys */
if((p->vkCode == VK_LWIN) || (p->vkCode == VK_RWIN)) return(1);
/* checks ctrl key pressed */
bControlKeyDown = GetAsyncKeyState(VK_CONTROL)>>((sizeof(SHORT)*8)-1);
/* disable ctrl-escape */
if (p->vkCode == VK_ESCAPE && bControlKeyDown) return(1);
return(CallNextHookEx(hKeyboardHook, nCode, wParam, lParam));
}
#ifdef USE_HOST_CDROM
static void
HandleMediaEvent(WPARAM wParam, LPARAM lParam)
{
PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb;
int f = -1, i;
char d;
switch (wParam) {
case DBT_DEVICEARRIVAL:
if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) {
if (lpdbv->dbcv_flags & DBTF_MEDIA)
f = 1;
}
break;
case DBT_DEVICEREMOVECOMPLETE:
if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) {
if (lpdbv->dbcv_flags & DBTF_MEDIA)
f = 0;
}
break;
default:
/* We don't care.. */
break;
}
/* Now report all 'changed' drives to the CD-ROM handler. */
if (f != -1) {
for (i = 0; i < 26; i++) {
if (lpdbv->dbcv_unitmask & 1) {
/* Get the drive letter. */
d = 'A' + i;
cdrom_notify(&d, f);
}
lpdbv->dbcv_unitmask >>= 1;
}
}
}
#endif
static LRESULT CALLBACK
MainWindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
DWORD flags;
RECT rect;
int x, y;
int idm;
switch (message) {
case WM_CREATE:
SetTimer(hwnd, TIMER_1SEC, 1000, NULL);
hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL,
LowLevelKeyboardProc,
GetModuleHandle(NULL), 0);
hook_enabled = 1;
break;
case WM_COMMAND:
UpdateWindow(hwnd);
idm = LOWORD(wParam);
/* Let the general UI handle it first, and then we do. */
if (! ui_menu_command(idm)) switch(idm) {
case IDM_EXIT:
PostQuitMessage(0);
break;
case IDM_RESIZE:
/* Set up for resizing if configured. */
flags = WS_OVERLAPPEDWINDOW;
if (! vid_resize)
flags &= ~(WS_SIZEBOX | WS_THICKFRAME |
WS_MAXIMIZEBOX);
SetWindowLongPtr(hwnd, GWL_STYLE, flags);
/* Main Window. */
GetWindowRect(hwnd, &rect);
get_screen_size_natural(&x, &y);
MoveWindow(hwnd, rect.left, rect.top,
x + cruft_x, y + cruft_y + cruft_sb,
TRUE);
break;
case IDM_REMEMBER:
GetWindowRect(hwnd, &rect);
if (window_remember) {
window_x = rect.left;
window_y = rect.top;
window_w = rect.right - rect.left;
window_h = rect.bottom - rect.top;
}
config_save();
break;
}
return(0);
case WM_ENTERMENULOOP:
break;
case WM_SIZE:
/* Note: this is the *client area* size!! */
x = (int)(lParam & 0xffff);
y = (int)(lParam >> 16);
if (cruft_x == 0) {
/* Determine the window cruft. */
cruft_x = (scrnsz_x - x);
cruft_y = (scrnsz_y - y);
/* Update window size with cruft. */
x += cruft_x;
y += (cruft_y + cruft_sb);
}
y -= cruft_sb;
/* Request a re-size if needed. */
if ((x != scrnsz_x) || (y != scrnsz_y)) doresize = 1;
/* Set the new panel size. */
scrnsz_x = x;
scrnsz_y = y;
/* Update the render window. */
if (hwndRender != NULL)
MoveWindow(hwndRender, 0, 0, scrnsz_x, scrnsz_y, TRUE);
/* Update the Status bar. */
MoveWindow(hwndSBAR, 0, scrnsz_y, scrnsz_x, cruft_sb, TRUE);
StatusBarResize(scrnsz_x);
/* Update the renderer if needed. */
vidapi_resize(scrnsz_x, scrnsz_y);
/* Re-clip the mouse area if needed. */
if (mouse_capture) {
GetWindowRect(hwndRender, &rect);
ClipCursor(&rect);
}
if (window_remember) {
GetWindowRect(hwndMain, &rect);
window_x = rect.left;
window_y = rect.top;
window_w = rect.right - rect.left;
window_h = rect.bottom - rect.top;
save_window_pos = 1;
config_save();
}
break;
case WM_MOVE:
/*
* If window is not resizable, then tell the main thread * to resize it, as sometimes, moves can mess up the window
* size.
*/
if (! vid_resize)
doresize = 1;
if (window_remember) {
GetWindowRect(hwndMain, &rect);
window_x = rect.left;
window_y = rect.top;
window_w = rect.right - rect.left;
window_h = rect.bottom - rect.top;
save_window_pos = 1;
}
break;
case WM_TIMER:
if (wParam == TIMER_1SEC)
pc_onesec();
break;
case WM_RESET_D3D:
plat_startblit();
vidapi_reset();
plat_endblit();
break;
case WM_LEAVE_FS:
ui_fullscreen(0);
config_save();
break;
case WM_KEYDOWN:
case WM_KEYUP:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
return(0);
case WM_DESTROY:
UnhookWindowsHookEx(hKeyboardHook);
KillTimer(hwnd, TIMER_1SEC);
PostQuitMessage(0);
break;
case WM_SHOW_CFG:
pc_pause(1);
if (dlg_settings(1) == 2)
pc_reset_hard_init();
pc_pause(0);
break;
case WM_PAUSE:
pc_pause(dopause ^ 1);
menu_set_item(IDM_PAUSE, dopause);
break;
case WM_HARD_RESET:
pc_reset(1);
break;
case WM_CTRLALTDEL:
pc_reset(0);
break;
case WM_SHUTDOWN:
PostQuitMessage(0);
break;
#ifdef USE_HOST_CDROM
case WM_DEVICECHANGE:
HandleMediaEvent(wParam, lParam);
break;
#endif
case WM_SYSCOMMAND:
/*
* Disable ALT key *ALWAYS*,
* I don't think there's any use for
* reaching the menu that way.
*/
if (wParam == SC_KEYMENU && HIWORD(lParam) <= 0) {
return 0; /*disable ALT key for menu*/
}
default:
return(DefWindowProc(hwnd, message, wParam, lParam));
}
return(FALSE);
}
/* Dummy window procedure, used by D3D when in full-screen mode. */
static LRESULT CALLBACK
SubWindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return(DefWindowProc(hwnd, message, wParam, lParam));
}
/* Catch WM_INPUT messages for 'current focus' window. */
static WIN_RESULT CALLBACK
input_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) {
case WM_INPUT:
keyboard_handle(lParam, infocus);
break;
case WM_SETFOCUS:
infocus = 1;
if (! hook_enabled) {
hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL,
LowLevelKeyboardProc,
GetModuleHandle(NULL),
0);
hook_enabled = 1;
}
/* Update host keyboard state if needed. */
if (kbd_flags != win_kbd_flags)
plat_set_kbd_state(kbd_flags);
break;
case WM_KILLFOCUS:
infocus = 0;
ui_mouse_capture(0);
if (hook_enabled) {
UnhookWindowsHookEx(hKeyboardHook);
hook_enabled = 0;
}
/* Update host keyboard state if needed. */
if (kbd_flags != win_kbd_flags)
plat_set_kbd_state(win_kbd_flags);
break;
case WM_LBUTTONUP:
if (! vid_fullscreen)
ui_mouse_capture(1);
break;
case WM_MBUTTONUP:
if (mouse_get_buttons() < 3)
ui_mouse_capture(0);
break;
default:
return(CallWindowProc((WNDPROC)input_orig_proc,
hwnd, message, wParam, lParam));
}
return(0);
}
/* Set up a handler for the 'currently active' window. */
void
plat_set_input(HWND h)
{
/* If needed, rest the old one first. */
if (input_orig_hwnd != NULL) {
SetWindowLongPtr(input_orig_hwnd, GWLP_WNDPROC,
(LONG_PTR)input_orig_proc);
}
/* Redirect the window procedure so we can catch WM_INPUT. */
input_orig_proc = GetWindowLongPtr(h, GWLP_WNDPROC);
input_orig_hwnd = h;
SetWindowLongPtr(h, GWLP_WNDPROC, (LONG_PTR)input_proc);
}
/* UI: reset the lowlevel (platform) UI. */
void
ui_plat_reset(void)
{
/* Load the main menu from the appropriate DLL. */
menuMain = LoadMenu(plat_lang_dll(), MENU_MAIN_NAME);
SetMenu(hwndMain, menuMain);
/* Load the statusbar menu. */
menuSBAR = LoadMenu(hInstance, MENU_SB_NAME);
}
/* UI: initialize the Win32 User Interface module. */
int
ui_init(int nCmdShow)
{
WCHAR title[200];
WNDCLASSEX wincl; /* buffer for main window's class */
INITCOMMONCONTROLSEX icex; /* common controls, new style */
RAWINPUTDEVICE ridev; /* RawInput device */
MSG messages; /* received-messages buffer */
HACCEL haccel; /* handle to accelerator table */
DWORD flags;
int ret;
/* Register the new version of the Common Controls. */
memset(&icex, 0x00, sizeof(INITCOMMONCONTROLSEX));
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&icex);
if (settings_only) {
if (! pc_init()) {
/* Dang, no ROMs found at all! */
ui_msgbox(MBX_ERROR, (wchar_t *)IDS_ERR_NOROMS);
return(6);
}
(void)dlg_settings(0);
return(0);
}
/* Create our main window's class and register it. */
wincl.hInstance = hInstance;
wincl.lpszClassName = CLASS_NAME;
wincl.lpfnWndProc = MainWindowProcedure;
wincl.style = CS_DBLCLKS; /* Catch double-clicks */
wincl.cbSize = sizeof(WNDCLASSEX);
wincl.hIcon = LoadIcon(hInstance, (LPCTSTR)ICON_MAIN);
wincl.hIconSm = LoadIcon(hInstance, (LPCTSTR)ICON_MAIN);
wincl.hCursor = NULL;
wincl.lpszMenuName = NULL;
wincl.cbClsExtra = 0;
wincl.cbWndExtra = 0;
wincl.hbrBackground = CreateSolidBrush(RGB(0,0,0));
if (! RegisterClassEx(&wincl))
return(2);
/* Register a class for the Fullscreen renderer window. */
wincl.lpszClassName = FS_CLASS_NAME;
wincl.lpfnWndProc = SubWindowProcedure;
if (! RegisterClassEx(&wincl))
return(2);
/* Set up main window for resizing if configured. */
flags = WS_OVERLAPPEDWINDOW;
if (! vid_resize)
flags &= ~(WS_SIZEBOX | WS_THICKFRAME | WS_MAXIMIZEBOX);
/*
* Create our main window.
*
* We want our (initial) render panel to be a certain
* size (default 640x480), and we have no idea what
* our window decorations are, size-wise.
*
* Rather than depending on the GetSystemMetrics API
* calls (which return incorrect data as of Vista+),
* the simplest trick is to just set the desired window
* size, and create the window. The first WM_SIZE
* message will indicate the client size left after
* creating all the decorations, and the difference
* is the decoration overhead ('cruft') we need to
* always keep in mind when changing the window size.
*/
swprintf(title, sizeof_w(title), L"%s %s", EMU_NAME, emu_version);
hwndMain = CreateWindowEx(
#if 0
WS_EX_LAYOUTRTL | WS_EX_RIGHT,
#else
0,
#endif
CLASS_NAME, /* class name */
title, /* Title Text */
flags, /* style flags */
CW_USEDEFAULT, CW_USEDEFAULT, /* no preset position */
scrnsz_x, scrnsz_y, /* window size in pixels */
HWND_DESKTOP, /* child of desktop */
NULL, /* menu */
hInstance, /* Program Instance handler */
NULL); /* no Window Creation data */
/* Create the status bar window. */
StatusBarCreate(IDC_STATBAR);
ui_window_title(title);
/* Reset all menus to their defaults. */
ui_reset();
/* Move to the last-saved position if needed. */
if (window_remember)
MoveWindow(hwndMain, window_x, window_y, window_w, window_h, FALSE);
/* Load the accelerator table */
haccel = LoadAccelerators(hInstance, ACCEL_NAME);
if (haccel == NULL) {
ui_msgbox(MBX_CONFIG, (wchar_t *)IDS_ERR_ACCEL);
return(3);
}
/* Make the window visible on the screen. */
ShowWindow(hwndMain, nCmdShow);
/* Initialize the RawInput (keyboard) module. */
memset(&ridev, 0x00, sizeof(ridev));
ridev.usUsagePage = 0x01;
ridev.usUsage = 0x06;
ridev.dwFlags = RIDEV_NOHOTKEYS;
ridev.hwndTarget = NULL; /* current focus window */
if (! RegisterRawInputDevices(&ridev, 1, sizeof(ridev))) {
ui_msgbox(MBX_CONFIG, (wchar_t *)IDS_ERR_INPUT);
return(4);
}
keyboard_getkeymap();
/* Set up the main window for RawInput. */
plat_set_input(hwndMain);
/* Grab the current state of the (host) keyboard. */
win_kbd_flags = plat_get_kbd_state();
kbd_flags = win_kbd_flags;
/* Create the Machine Rendering window. */
hwndRender = CreateWindow(L"STATIC",
NULL,
WS_CHILD|SS_BITMAP,
0, 0,
scrnsz_x, scrnsz_y,
hwndMain,
NULL,
hInstance,
NULL);
/* That looks good, now continue setting up the machine. */
switch (pc_init()) {
case -1: /* General failure during init, give up. */
return(6);
case 0: /* Configuration error, user wants to exit. */
return(0);
case 1: /* All good. */
break;
case 2: /* Configuration error, user wants to re-config. */
if (dlg_settings(0)) {
/* Save the new configuration to file. */
config_save();
/* Remind them to restart. */
ui_msgbox(MBX_INFO, (wchar_t *)IDS_MSG_RESTART);
}
return(0);
}
/* Activate the render window, this will also set the screen size. */
if (hwndRender != NULL)
MoveWindow(hwndRender, 0, 0, scrnsz_x, scrnsz_y, TRUE);
/* Initialize the configured Video API. */
again:
if (! vidapi_set(vid_api)) {
/*
* Selected renderer is not available.
*
* This can happen if one of the optional renderers
* was selected previously, but is currently not
* available for whatever reason.
*
* Inform the user, and ask if they want to reset
* to the system default one instead.
*/
swprintf(title, sizeof_w(title),
get_string(IDS_ERR_NORENDR),
vidapi_get_internal_name(vid_api));
if (ui_msgbox(MBX_CONFIG, title) != 0) {
/* Nope, they don't, so just exit. */
return(5);
}
/* OK, reset to the default one and retry. */
vid_api = vidapi_from_internal_name("default");
goto again;
}
/* Initialize the rendering window, or fullscreen. */
if (start_in_fullscreen)
ui_fullscreen(1);
/* Initialize the mouse module. */
win_mouse_init();
/* Fire up the machine. */
pc_reset_hard();
/* Set the PAUSE mode depending on the renderer. */
pc_pause(0);
#ifdef USE_MANAGER
/*
* If so requested via the command line, inform the
* application that started us of our HWND, using the
* the hWnd and unique ID the application has given
* us.
*/
if (source_hwnd)
PostMessage((HWND) (uintptr_t) source_hwnd,
WM_SEND_HWND, (WPARAM)unique_id, (LPARAM)hwndMain);
#endif
/*
* Everything has been configured, and all seems to work,
* so now it is time to start the main thread to do some
* real work, and we will hang in here, dealing with the
* UI until we're done.
*/
plat_start();
/* Run the message loop. It will run until GetMessage() returns 0 */
while (! quited) {
ret = GetMessage(&messages, NULL, 0, 0);
if ((ret == 0) || quited) break;
if (ret == -1) {
fatal("ret is -1\n");
}
if (messages.message == WM_QUIT) {
quited = 1;
break;
}
if (! TranslateAccelerator(hwndMain, haccel, &messages)) {
TranslateMessage(&messages);
DispatchMessage(&messages);
}
if (mouse_capture && keyboard_ismsexit()) {
/* Release the in-app mouse. */
ui_mouse_capture(0);
}
if (vid_fullscreen && keyboard_isfsexit()) {
/* Signal "exit fullscreen mode". */
/* INFO("leave full screen though key combination\n"); */
ui_fullscreen(0);
}
}
timeEndPeriod(1);
if (mouse_capture)
ui_mouse_capture(0);
/* Close down the emulator. */
plat_stop();
UnregisterClass(CLASS_NAME, hInstance);
UnregisterClass(FS_CLASS_NAME, hInstance);
win_mouse_close();
/* Restore host keyboard state. */
plat_set_kbd_state(win_kbd_flags);
return((int)messages.wParam);
}
/* UI support: tell the UI about a new screen resolution. */
void
ui_resize(int x, int y)
{
RECT r;
/* First, see if we should resize the UI window. */
if (vid_resize) return;
video_blit_wait();
/* Re-position and re-size the main window. */
GetWindowRect(hwndMain, &r);
MoveWindow(hwndMain, r.left, r.top,
x+cruft_x, y+cruft_y+cruft_sb, TRUE);
}
/* UI support: update the application's title bar. */
wchar_t *
ui_window_title(const wchar_t *s)
{
if (! vid_fullscreen) {
if (s != NULL)
wcscpy(wTitle, s);
else
s = wTitle;
SetWindowText(hwndMain, s);
} else {
if (s == NULL)
s = wTitle;
}
return((wchar_t *)s);
}
/* UI support: set cursor visible or not. */
void
ui_show_cursor(int val)
{
static int vis = -1;
if (val == vis)
return;
if (val == 0) {
while (1) {
if (ShowCursor(FALSE) < 0) break;
}
} else {
ShowCursor(TRUE);
}
vis = val;
}
/* UI support: show or hide the render window. */
void
ui_show_render(int on)
{
#ifndef USE_WX
if (on)
ShowWindow(hwndRender, SW_SHOW);
else
ShowWindow(hwndRender, SW_HIDE);
#endif
}
/* Set the desired fullscreen/windowed mode. */
void
plat_fullscreen(int on)
{
win_mouse_close();
#ifdef USE_WX
wx_set_fullscreen(on);
#endif
win_mouse_init();
}
/* Platform support for the PAUSE action. */
void
plat_pause(int paused)
{
#ifdef USE_MANAGER
/* Send the WM to a manager if needed. */
if (source_hwnd)
PostMessage((HWND) (uintptr_t) source_hwnd,
WM_SENDSTATUS, (WPARAM)paused, (LPARAM)hwndMain);
#endif
}
/* Grab the current keyboard state. */
int
plat_get_kbd_state(void)
{
BYTE kbdata[256];
int ret = 0x00;
/* Grab the system keyboard state. */
memset(kbdata, 0x00, sizeof(kbdata));
GetKeyboardState(kbdata);
/* Pick out the keys we are interested in. */
if (kbdata[VK_NUMLOCK]) ret |= KBD_FLAG_NUM;
if (kbdata[VK_CAPITAL]) ret |= KBD_FLAG_CAPS;
if (kbdata[VK_SCROLL]) ret |= KBD_FLAG_SCROLL;
if (kbdata[VK_PAUSE]) ret |= KBD_FLAG_PAUSE;
return(ret);
}
/* Set the active keyboard state. */
void
plat_set_kbd_state(int flags)
{
INPUT kbdata[2];
memset(kbdata, 0x00, sizeof(kbdata));
kbdata[0].type = kbdata[1].type = INPUT_KEYBOARD;
kbdata[1].ki.dwFlags = KEYEVENTF_KEYUP;
INFO("WIN kbd_state(%04x) [%04x] focus=%d\n" , flags, kbd_flags, infocus);
if (kbd_flags != flags) {
/* Save the new flags. */
kbd_flags = flags;
/* If we are active, update the host keyboard state. */
if (infocus) {
/* Pick out the keys we are interested in. */
if (flags & KBD_FLAG_NUM) {
kbdata[0].ki.wVk = kbdata[1].ki.wVk = VK_NUMLOCK;
if (SendInput(2, kbdata, sizeof(INPUT)) == 0) {
ERRLOG("WIN: cannot SendInput(%i): error %i\n",
kbdata[0].ki.wVk, GetLastError());
}
}
if (flags & KBD_FLAG_CAPS) {
kbdata[0].ki.wVk = kbdata[1].ki.wVk = VK_CAPITAL;
if (SendInput(2, kbdata, sizeof(INPUT)) == 0) {
ERRLOG("WIN: cannot SendInput(%i): error %i\n",
kbdata[0].ki.wVk, GetLastError());
}
}
if (flags & KBD_FLAG_SCROLL) {
kbdata[0].ki.wVk = kbdata[1].ki.wVk = VK_SCROLL;
if (SendInput(2, kbdata, sizeof(INPUT)) == 0) {
ERRLOG("WIN: cannot SendInput(%i): error %i\n",
kbdata[0].ki.wVk, GetLastError());
}
}
if (flags & KBD_FLAG_PAUSE) {
kbdata[0].ki.wVk = kbdata[1].ki.wVk = VK_PAUSE;
if (SendInput(2, kbdata, sizeof(INPUT)) == 0) {
ERRLOG("WIN: cannot SendInput(%i): error %i\n",
kbdata[0].ki.wVk, GetLastError());
}
}
}
}
}
/* UI support: enable or disable mouse clipping. */
void
plat_mouse_capture(int on)
{
RECT rect;
if ((on == -1) || !on) {
/* Disable the in-app mouse. */
if (on == -1)
GetClipCursor(&oldclip);
else
ClipCursor(&oldclip);
} else if (on && !mouse_capture) {
/* Enable the in-app mouse. */
GetClipCursor(&oldclip);
GetWindowRect(hwndRender, &rect);
ClipCursor(&rect);
}
}
/* UI support: add an item to a menu. */
void
menu_add_item(int idm, int type, int new_id, const wchar_t *str)
{
MENUITEMINFO info;
HMENU menu;
/* Get the handle for the intended (sub)menu. */
memset(&info, 0x00, sizeof(info));
info.cbSize = sizeof(info);
info.fMask = MIIM_SUBMENU;
if (! GetMenuItemInfo(menuMain, idm, FALSE, &info)) {
ERRLOG("UI: cannot find submenu %d\n", idm);
return;
}
menu = info.hSubMenu;
switch(type) {
case ITEM_SEPARATOR:
AppendMenu(menu, MF_SEPARATOR, 0, NULL);
break;
default:
AppendMenu(menu, MF_STRING, new_id, str);
}
/* We changed the menu bar, so redraw it. */
DrawMenuBar(hwndMain);
}
/* UI support: enable or disable a menu item. */
void
menu_enable_item(int idm, int val)
{
EnableMenuItem(menuMain, idm, (val) ? MF_ENABLED : MF_DISABLED);
}
/* UI support: set/check a menu item. */
void
menu_set_item(int idm, int val)
{
CheckMenuItem(menuMain, idm, val ? MF_CHECKED : MF_UNCHECKED);
}
/* UI support: set a radio group menu item. */
void
menu_set_radio_item(int idm, int num, int val)
{
int i;
if (val < 0) return;
for (i = 0; i < num; i++)
menu_set_item(idm + i, 0);
menu_set_item(idm + val, 1);
}
/* UI support: delete all menus from the status bar. */
static void
menu_destroy(void)
{
int i;
if (!sb_nparts || (sb_menu == NULL)) return;
for (i = 0; i < sb_nparts; i++) {
if (sb_menu[i] != NULL)
DestroyMenu(sb_menu[i]);
}
free(sb_menu);
sb_menu = NULL;
}
/* UI support: reset the status bar. */
void
sb_setup(int nparts, const sbpart_t *data)
{
RECT r;
/* Set the info. */
sb_nparts = nparts;
sb_parts = data;
if (sb_menu != NULL) {
menu_destroy();
}
/* First, get width of the status bar window. */
if (nparts > 0) {
/* Calculate and set up the parts. */
GetWindowRect(hwndSBAR, &r);
StatusBarResize(r.right - r.left);
/* Allocate and clear a new menu. */
sb_menu = (HMENU *)mem_alloc(nparts * sizeof(HMENU));
memset(sb_menu, 0x00, nparts * sizeof(HMENU));
}
}
/* UI support: set the icon ID for a status bar field. */
void
sb_set_icon(int part, int icon)
{
HANDLE ptr;
if (icon == 255) ptr = NULL;
else ptr = hIcon[(intptr_t)icon];
SendMessage(hwndSBAR, SB_SETICON, part, (LPARAM)ptr);
}
/* UI support: set a text label for a status bar field. */
void
sb_set_text(int part, const wchar_t *str)
{
int flags;
// flags = (part < (sb_nparts - 2)) ? SBT_NOBORDERS : SBT_POPOUT;
flags = (part < (sb_nparts - 2)) ? SBT_NOBORDERS : 0;
SendMessage(hwndSBAR, SB_SETTEXT, part | flags, (LPARAM)str);
}
/* UI support: set a tooltip for a status bar field. */
void
sb_set_tooltip(int part, const wchar_t *str)
{
SendMessage(hwndSBAR, SB_SETTIPTEXT, part, (LPARAM)str);
}
/* UI support: create a menu for a status bar part. */
void
sb_menu_create(int part)
{
HMENU h;
h = CreatePopupMenu();
sb_menu[part] = h;
}
/* UI support: add an item to a (status bar) menu. */
void
sb_menu_add_item(int part, int idm, const wchar_t *str)
{
if (idm >= 0)
AppendMenu(sb_menu[part], MF_STRING, idm, str);
else
AppendMenu(sb_menu[part], MF_SEPARATOR, 0, NULL);
}
/* UI support: enable or disable a status bar menu item. */
void
sb_menu_enable_item(int part, int idm, int val)
{
EnableMenuItem(sb_menu[part], idm,
val ? MF_BYCOMMAND|MF_ENABLED : MF_BYCOMMAND|MF_GRAYED);
}
/* UI support: set or reset a status bar menu item. */
void
sb_menu_set_item(int part, int idm, int val)
{
CheckMenuItem(sb_menu[part], idm, val ? MF_CHECKED : MF_UNCHECKED);
}