/* * Joystick testing control panel applet * * Copyright 2012 Lucas Fialho Zawacki * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * */ #define COBJMACROS #define CONST_VTABLE #include #include #include #include #include #include #include #include "ole2.h" #include "wine/debug.h" #include "wine/list.h" #include "joy_private.h" WINE_DEFAULT_DEBUG_CHANNEL(joycpl); struct device { struct list entry; IDirectInputDevice8W *device; }; static HMODULE hcpl; static CRITICAL_SECTION joy_cs; static CRITICAL_SECTION_DEBUG joy_cs_debug = { 0, 0, &joy_cs, { &joy_cs_debug.ProcessLocksList, &joy_cs_debug.ProcessLocksList }, 0, 0, { (DWORD_PTR)(__FILE__ ": joy_cs") } }; static CRITICAL_SECTION joy_cs = { &joy_cs_debug, -1, 0, 0, 0, 0 }; static struct list devices = LIST_INIT( devices ); /********************************************************************* * DllMain */ BOOL WINAPI DllMain(HINSTANCE hdll, DWORD reason, LPVOID reserved) { TRACE("(%p, %ld, %p)\n", hdll, reason, reserved); switch (reason) { case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls(hdll); hcpl = hdll; } return TRUE; } static BOOL CALLBACK enum_devices( const DIDEVICEINSTANCEW *instance, void *context ) { DIDEVCAPS caps = {.dwSize = sizeof(DIDEVCAPS)}; IDirectInput8W *dinput = context; struct device *entry; if (!(entry = calloc( 1, sizeof(*entry) ))) return DIENUM_STOP; IDirectInput8_CreateDevice( dinput, &instance->guidInstance, &entry->device, NULL ); IDirectInputDevice8_SetDataFormat( entry->device, &c_dfDIJoystick ); IDirectInputDevice8_GetCapabilities( entry->device, &caps ); list_add_tail( &devices, &entry->entry ); return DIENUM_CONTINUE; } static void clear_devices(void) { struct device *entry, *next; LIST_FOR_EACH_ENTRY_SAFE( entry, next, &devices, struct device, entry ) { list_remove( &entry->entry ); IDirectInputDevice8_Unacquire( entry->device ); IDirectInputDevice8_Release( entry->device ); free( entry ); } } /****************************************************************************** * get_app_key [internal] * Get the default DirectInput key and the selected app config key. */ static BOOL get_app_key(HKEY *defkey, HKEY *appkey) { *appkey = 0; /* Registry key can be found in HKCU\Software\Wine\DirectInput */ if (RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Wine\\DirectInput\\Joysticks", 0, NULL, 0, KEY_SET_VALUE | KEY_READ, NULL, defkey, NULL)) *defkey = 0; return *defkey || *appkey; } /****************************************************************************** * set_config_key [internal] * Writes a string value to a registry key, deletes the key if value == NULL */ static DWORD set_config_key(HKEY defkey, HKEY appkey, const WCHAR *name, const WCHAR *value, DWORD size) { if (value == NULL) { if (appkey && !RegDeleteValueW(appkey, name)) return 0; if (defkey && !RegDeleteValueW(defkey, name)) return 0; } else { if (appkey && !RegSetValueExW(appkey, name, 0, REG_SZ, (const BYTE*) value, (size + 1)*sizeof(WCHAR))) return 0; if (defkey && !RegSetValueExW(defkey, name, 0, REG_SZ, (const BYTE*) value, (size + 1)*sizeof(WCHAR))) return 0; } return ERROR_FILE_NOT_FOUND; } /****************************************************************************** * enable_joystick [internal] * Writes to the DirectInput registry key that enables/disables a joystick * from being enumerated. */ static void enable_joystick(WCHAR *joy_name, BOOL enable) { HKEY hkey, appkey; get_app_key(&hkey, &appkey); if (!enable) set_config_key(hkey, appkey, joy_name, L"disabled", wcslen(L"disabled")); else set_config_key(hkey, appkey, joy_name, NULL, 0); if (hkey) RegCloseKey(hkey); if (appkey) RegCloseKey(appkey); } static void refresh_joystick_list( HWND hwnd ) { IDirectInput8W *dinput; struct device *entry; HKEY hkey, appkey; DWORD values = 0; LSTATUS status; DWORD i; clear_devices(); DirectInput8Create( GetModuleHandleW( NULL ), DIRECTINPUT_VERSION, &IID_IDirectInput8W, (void **)&dinput, NULL ); IDirectInput8_EnumDevices( dinput, DI8DEVCLASS_GAMECTRL, enum_devices, dinput, DIEDFL_ATTACHEDONLY ); IDirectInput8_Release( dinput ); SendDlgItemMessageW(hwnd, IDC_JOYSTICKLIST, LB_RESETCONTENT, 0, 0); SendDlgItemMessageW(hwnd, IDC_DISABLEDLIST, LB_RESETCONTENT, 0, 0); SendDlgItemMessageW(hwnd, IDC_XINPUTLIST, LB_RESETCONTENT, 0, 0); LIST_FOR_EACH_ENTRY( entry, &devices, struct device, entry ) { DIDEVICEINSTANCEW info = {.dwSize = sizeof(DIDEVICEINSTANCEW)}; DIPROPGUIDANDPATH prop = { .diph = { .dwSize = sizeof(DIPROPGUIDANDPATH), .dwHeaderSize = sizeof(DIPROPHEADER), .dwHow = DIPH_DEVICE, }, }; if (FAILED(IDirectInputDevice8_GetDeviceInfo( entry->device, &info ))) continue; if (FAILED(IDirectInputDevice8_GetProperty( entry->device, DIPROP_GUIDANDPATH, &prop.diph ))) continue; if (wcsstr( prop.wszPath, L"&ig_" )) SendDlgItemMessageW( hwnd, IDC_XINPUTLIST, LB_ADDSTRING, 0, (LPARAM)info.tszInstanceName ); else SendDlgItemMessageW( hwnd, IDC_JOYSTICKLIST, LB_ADDSTRING, 0, (LPARAM)info.tszInstanceName ); } /* Search for disabled joysticks */ get_app_key(&hkey, &appkey); RegQueryInfoKeyW(hkey, NULL, NULL, NULL, NULL, NULL, NULL, &values, NULL, NULL, NULL, NULL); for (i=0; i < values; i++) { DWORD name_len = MAX_PATH, data_len = MAX_PATH; WCHAR buf_name[MAX_PATH + 9], buf_data[MAX_PATH]; status = RegEnumValueW(hkey, i, buf_name, &name_len, NULL, NULL, (BYTE*) buf_data, &data_len); if (status == ERROR_SUCCESS && !wcscmp(L"disabled", buf_data)) SendDlgItemMessageW(hwnd, IDC_DISABLEDLIST, LB_ADDSTRING, 0, (LPARAM) buf_name); } if (hkey) RegCloseKey(hkey); if (appkey) RegCloseKey(appkey); } static void override_joystick(WCHAR *joy_name, BOOL override) { HKEY hkey, appkey; get_app_key(&hkey, &appkey); if (override) set_config_key(hkey, appkey, joy_name, L"override", wcslen(L"override")); else set_config_key(hkey, appkey, joy_name, NULL, 0); if (hkey) RegCloseKey(hkey); if (appkey) RegCloseKey(appkey); } /********************************************************************* * list_dlgproc [internal] * */ static INT_PTR CALLBACK list_dlgproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { WCHAR instance_name[MAX_PATH] = {0}; int sel; TRACE("(%p, 0x%08x/%d, 0x%Ix)\n", hwnd, msg, msg, lparam); switch (msg) { case WM_INITDIALOG: { refresh_joystick_list( hwnd ); EnableWindow(GetDlgItem(hwnd, IDC_BUTTONENABLE), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_BUTTONDISABLE), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_BUTTONRESET), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_BUTTONOVERRIDE), FALSE); return TRUE; } case WM_COMMAND: switch (LOWORD(wparam)) { case IDC_BUTTONDISABLE: { if ((sel = SendDlgItemMessageW(hwnd, IDC_JOYSTICKLIST, LB_GETCURSEL, 0, 0)) >= 0) SendDlgItemMessageW(hwnd, IDC_JOYSTICKLIST, LB_GETTEXT, sel, (LPARAM)instance_name); if ((sel = SendDlgItemMessageW(hwnd, IDC_XINPUTLIST, LB_GETCURSEL, 0, 0)) >= 0) SendDlgItemMessageW(hwnd, IDC_XINPUTLIST, LB_GETTEXT, sel, (LPARAM)instance_name); if (instance_name[0]) { enable_joystick(instance_name, FALSE); refresh_joystick_list( hwnd ); } } break; case IDC_BUTTONENABLE: { if ((sel = SendDlgItemMessageW(hwnd, IDC_DISABLEDLIST, LB_GETCURSEL, 0, 0)) >= 0) SendDlgItemMessageW(hwnd, IDC_DISABLEDLIST, LB_GETTEXT, sel, (LPARAM)instance_name); if (instance_name[0]) { enable_joystick(instance_name, TRUE); refresh_joystick_list( hwnd ); } } break; case IDC_BUTTONRESET: { if ((sel = SendDlgItemMessageW(hwnd, IDC_JOYSTICKLIST, LB_GETCURSEL, 0, 0)) >= 0) { SendDlgItemMessageW(hwnd, IDC_JOYSTICKLIST, LB_GETTEXT, sel, (LPARAM)instance_name); override_joystick(instance_name, FALSE); refresh_joystick_list( hwnd ); } } break; case IDC_BUTTONOVERRIDE: { if ((sel = SendDlgItemMessageW(hwnd, IDC_XINPUTLIST, LB_GETCURSEL, 0, 0)) >= 0) { SendDlgItemMessageW(hwnd, IDC_XINPUTLIST, LB_GETTEXT, sel, (LPARAM)instance_name); override_joystick(instance_name, TRUE); refresh_joystick_list( hwnd ); } } break; case IDC_JOYSTICKLIST: SendDlgItemMessageW(hwnd, IDC_DISABLEDLIST, LB_SETCURSEL, -1, 0); SendDlgItemMessageW(hwnd, IDC_XINPUTLIST, LB_SETCURSEL, -1, 0); EnableWindow(GetDlgItem(hwnd, IDC_BUTTONENABLE), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_BUTTONDISABLE), TRUE); EnableWindow(GetDlgItem(hwnd, IDC_BUTTONOVERRIDE), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_BUTTONRESET), TRUE); break; case IDC_XINPUTLIST: SendDlgItemMessageW(hwnd, IDC_JOYSTICKLIST, LB_SETCURSEL, -1, 0); SendDlgItemMessageW(hwnd, IDC_DISABLEDLIST, LB_SETCURSEL, -1, 0); EnableWindow(GetDlgItem(hwnd, IDC_BUTTONENABLE), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_BUTTONDISABLE), TRUE); EnableWindow(GetDlgItem(hwnd, IDC_BUTTONOVERRIDE), TRUE); EnableWindow(GetDlgItem(hwnd, IDC_BUTTONRESET), FALSE); break; case IDC_DISABLEDLIST: SendDlgItemMessageW(hwnd, IDC_JOYSTICKLIST, LB_SETCURSEL, -1, 0); SendDlgItemMessageW(hwnd, IDC_XINPUTLIST, LB_SETCURSEL, -1, 0); EnableWindow(GetDlgItem(hwnd, IDC_BUTTONENABLE), TRUE); EnableWindow(GetDlgItem(hwnd, IDC_BUTTONDISABLE), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_BUTTONOVERRIDE), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_BUTTONRESET), FALSE); break; } return TRUE; case WM_NOTIFY: return TRUE; default: break; } return FALSE; } /****************************************************************************** * propsheet_callback [internal] */ static int CALLBACK propsheet_callback(HWND hwnd, UINT msg, LPARAM lparam) { TRACE("(%p, 0x%08x/%d, 0x%Ix)\n", hwnd, msg, msg, lparam); switch (msg) { case PSCB_INITIALIZED: break; } return 0; } static void display_cpl_sheets( HWND parent ) { INITCOMMONCONTROLSEX init = { .dwSize = sizeof(INITCOMMONCONTROLSEX), .dwICC = ICC_LISTVIEW_CLASSES | ICC_BAR_CLASSES, }; PROPSHEETPAGEW pages[] = { { .dwSize = sizeof(PROPSHEETPAGEW), .hInstance = hcpl, .pszTemplate = MAKEINTRESOURCEW( IDD_LIST ), .pfnDlgProc = list_dlgproc, }, { .dwSize = sizeof(PROPSHEETPAGEW), .hInstance = hcpl, .pszTemplate = MAKEINTRESOURCEW( IDD_TEST_DI ), .pfnDlgProc = test_di_dialog_proc, }, { .dwSize = sizeof(PROPSHEETPAGEW), .hInstance = hcpl, .pszTemplate = MAKEINTRESOURCEW( IDD_TEST_XI ), .pfnDlgProc = test_xi_dialog_proc, }, }; PROPSHEETHEADERW header = { .dwSize = sizeof(PROPSHEETHEADERW), .dwFlags = PSH_PROPSHEETPAGE | PSH_USEICONID | PSH_USECALLBACK, .hwndParent = parent, .hInstance = hcpl, .pszCaption = MAKEINTRESOURCEW( IDS_CPL_NAME ), .nPages = ARRAY_SIZE(pages), .ppsp = pages, .pfnCallback = propsheet_callback, }; ACTCTXW context_desc = { .cbSize = sizeof(ACTCTXW), .hModule = hcpl, .lpResourceName = MAKEINTRESOURCEW( 124 ), .dwFlags = ACTCTX_FLAG_HMODULE_VALID | ACTCTX_FLAG_RESOURCE_NAME_VALID, }; ULONG_PTR cookie; HANDLE context; BOOL activated; OleInitialize( NULL ); context = CreateActCtxW( &context_desc ); if (context == INVALID_HANDLE_VALUE) activated = FALSE; else activated = ActivateActCtx( context, &cookie ); InitCommonControlsEx( &init ); PropertySheetW( &header ); if (activated) DeactivateActCtx( 0, cookie ); ReleaseActCtx( context ); OleUninitialize(); } static void register_window_class(void) { WNDCLASSW xi_class = { .hInstance = hcpl, .lpfnWndProc = &test_xi_window_proc, .lpszClassName = L"JoyCplXInput", }; WNDCLASSW di_axes_class = { .hInstance = hcpl, .lpfnWndProc = &test_di_axes_window_proc, .lpszClassName = L"JoyCplDInputAxes", }; WNDCLASSW di_povs_class = { .hInstance = hcpl, .lpfnWndProc = &test_di_povs_window_proc, .lpszClassName = L"JoyCplDInputPOVs", }; WNDCLASSW di_buttons_class = { .hInstance = hcpl, .lpfnWndProc = &test_di_buttons_window_proc, .lpszClassName = L"JoyCplDInputButtons", }; RegisterClassW( &xi_class ); RegisterClassW( &di_axes_class ); RegisterClassW( &di_povs_class ); RegisterClassW( &di_buttons_class ); } static void unregister_window_class(void) { UnregisterClassW( L"JoyCplDInputAxes", hcpl ); UnregisterClassW( L"JoyCplDInputPOVs", hcpl ); UnregisterClassW( L"JoyCplDInputButtons", hcpl ); UnregisterClassW( L"JoyCplXInput", hcpl ); } /********************************************************************* * CPlApplet (joy.cpl.@) * * Control Panel entry point * * PARAMS * hWnd [I] Handle for the Control Panel Window * command [I] CPL_* Command * lParam1 [I] first extra Parameter * lParam2 [I] second extra Parameter * * RETURNS * Depends on the command * */ LONG CALLBACK CPlApplet(HWND hwnd, UINT command, LPARAM lParam1, LPARAM lParam2) { TRACE("(%p, %u, 0x%Ix, 0x%Ix)\n", hwnd, command, lParam1, lParam2); switch (command) { case CPL_INIT: register_window_class(); return TRUE; case CPL_GETCOUNT: return 1; case CPL_INQUIRE: { CPLINFO *appletInfo = (CPLINFO *) lParam2; appletInfo->idIcon = ICO_MAIN; appletInfo->idName = IDS_CPL_NAME; appletInfo->idInfo = IDS_CPL_INFO; appletInfo->lData = 0; return TRUE; } case CPL_DBLCLK: display_cpl_sheets( hwnd ); break; case CPL_STOP: clear_devices(); unregister_window_class(); break; } return FALSE; }