diff --git a/MAINTAINERS b/MAINTAINERS index 8c30f8c6d1d..22a5114e70d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -181,8 +181,12 @@ F: dlls/win32u/rawinput.c F: server/queue.c Input methods -M: Aric Stewart +M: Rémi Bernon +P: Aric Stewart F: dlls/imm32/ +F: dlls/win32u/imm.c +F: dlls/winemac.drv/ime.c +F: dlls/winex11.drv/ime.c JavaScript M: Jacek Caban @@ -214,6 +218,7 @@ F: dlls/winegstreamer/h264_decoder.c F: dlls/winegstreamer/resampler.c F: dlls/winegstreamer/video_decoder.c F: dlls/winegstreamer/video_processor.c +F: dlls/winegstreamer/wg_source.c F: dlls/winegstreamer/wg_sample.c F: dlls/winegstreamer/wg_transform.c F: dlls/winegstreamer/wma_decoder.c diff --git a/configure.ac b/configure.ac index 100df2f395a..c58fced1539 100644 --- a/configure.ac +++ b/configure.ac @@ -60,6 +60,7 @@ AC_ARG_WITH(udev, AS_HELP_STRING([--without-udev],[do not use udev (plug an AC_ARG_WITH(unwind, AS_HELP_STRING([--without-unwind],[do not use the libunwind library (exception handling)])) AC_ARG_WITH(usb, AS_HELP_STRING([--without-usb],[do not use the libusb library])) AC_ARG_WITH(v4l2, AS_HELP_STRING([--without-v4l2],[do not use v4l2 (video capture)])) +AC_ARG_WITH(vosk, AS_HELP_STRING([--without-vosk],[do not use Vosk])) AC_ARG_WITH(vulkan, AS_HELP_STRING([--without-vulkan],[do not use Vulkan])) AC_ARG_WITH(xcomposite,AS_HELP_STRING([--without-xcomposite],[do not use the Xcomposite extension]), [if test "x$withval" = "xno"; then ac_cv_header_X11_extensions_Xcomposite_h=no; fi]) @@ -488,7 +489,8 @@ AC_CHECK_HEADERS(\ syscall.h \ utime.h \ valgrind/memcheck.h \ - valgrind/valgrind.h + valgrind/valgrind.h \ + vosk_api.h ) WINE_HEADER_MAJOR() AC_HEADER_STAT() @@ -1196,13 +1198,6 @@ then # include #endif]) - dnl *** Check for X keyboard extension - if test "$ac_cv_header_X11_XKBlib_h" = "yes" - then - AC_CHECK_LIB(X11, XkbQueryExtension, - AC_DEFINE(HAVE_XKB, 1, [Define if you have the XKB extension]),,[$X_LIBS $X_EXTRA_LIBS]) - fi - dnl *** Check for X cursor if test "$ac_cv_header_X11_Xcursor_Xcursor_h" = "yes" then @@ -1800,6 +1795,14 @@ then WINE_WARNING([No sound system was found. Windows applications will be silent.]) fi +dnl **** Check for Vosk **** +if test x$with_vosk != xno +then + WINE_CHECK_SONAME(vosk,vosk_recognizer_new) +fi +WINE_NOTICE_WITH(vosk,[test x$ac_cv_lib_soname_vosk = x], + [libvosk ${notice_platform}development files not found, speech recognition won't be supported.]) + dnl *** Check for Vulkan *** if test "x$with_vulkan" != "xno" then @@ -2710,6 +2713,7 @@ WINE_CONFIG_MAKEFILE(dlls/inseng) WINE_CONFIG_MAKEFILE(dlls/iphlpapi) WINE_CONFIG_MAKEFILE(dlls/iphlpapi/tests) WINE_CONFIG_MAKEFILE(dlls/iprop) +WINE_CONFIG_MAKEFILE(dlls/ir50_32) WINE_CONFIG_MAKEFILE(dlls/irprops.cpl) WINE_CONFIG_MAKEFILE(dlls/itircl) WINE_CONFIG_MAKEFILE(dlls/itss) diff --git a/dlls/cfgmgr32/Makefile.in b/dlls/cfgmgr32/Makefile.in index 10621fa5dc7..f05b3176aa3 100644 --- a/dlls/cfgmgr32/Makefile.in +++ b/dlls/cfgmgr32/Makefile.in @@ -1,3 +1,6 @@ MODULE = cfgmgr32.dll IMPORTLIB = cfgmgr32 IMPORTS = setupapi + +C_SRCS = \ + main.c diff --git a/dlls/cfgmgr32/cfgmgr32.spec b/dlls/cfgmgr32/cfgmgr32.spec index 69ec784de68..3b4f6106618 100644 --- a/dlls/cfgmgr32/cfgmgr32.spec +++ b/dlls/cfgmgr32/cfgmgr32.spec @@ -126,6 +126,7 @@ @ stdcall CM_Locate_DevNodeW(ptr wstr long) setupapi.CM_Locate_DevNodeW @ stdcall CM_Locate_DevNode_ExA(ptr str long long) setupapi.CM_Locate_DevNode_ExA @ stdcall CM_Locate_DevNode_ExW(ptr wstr long long) setupapi.CM_Locate_DevNode_ExW +@ stdcall CM_MapCrToWin32Err(long long) @ stub CM_Merge_Range_List @ stub CM_Modify_Res_Des @ stub CM_Modify_Res_Des_Ex diff --git a/dlls/cfgmgr32/main.c b/dlls/cfgmgr32/main.c new file mode 100644 index 00000000000..fee3c42a5c4 --- /dev/null +++ b/dlls/cfgmgr32/main.c @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 Mohamad Al-Jaf + * + * 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 + */ + +#include "wine/debug.h" +#include "cfgmgr32.h" + +WINE_DEFAULT_DEBUG_CHANNEL(setupapi); + +/*********************************************************************** + * CM_MapCrToWin32Err (cfgmgr32.@) + */ +DWORD WINAPI CM_MapCrToWin32Err( CONFIGRET code, DWORD default_error ) +{ + TRACE( "code: %#lx, default_error: %ld\n", code, default_error ); + + switch (code) + { + case CR_SUCCESS: return ERROR_SUCCESS; + case CR_OUT_OF_MEMORY: return ERROR_NOT_ENOUGH_MEMORY; + case CR_INVALID_POINTER: return ERROR_INVALID_USER_BUFFER; + case CR_INVALID_FLAG: return ERROR_INVALID_FLAGS; + case CR_INVALID_DEVNODE: + case CR_INVALID_DEVICE_ID: + case CR_INVALID_MACHINENAME: + case CR_INVALID_PROPERTY: + case CR_INVALID_REFERENCE_STRING: return ERROR_INVALID_DATA; + case CR_NO_SUCH_DEVNODE: + case CR_NO_SUCH_VALUE: + case CR_NO_SUCH_DEVICE_INTERFACE: return ERROR_NOT_FOUND; + case CR_ALREADY_SUCH_DEVNODE: return ERROR_ALREADY_EXISTS; + case CR_BUFFER_SMALL: return ERROR_INSUFFICIENT_BUFFER; + case CR_NO_REGISTRY_HANDLE: return ERROR_INVALID_HANDLE; + case CR_REGISTRY_ERROR: return ERROR_REGISTRY_CORRUPT; + case CR_NO_SUCH_REGISTRY_KEY: return ERROR_FILE_NOT_FOUND; + case CR_REMOTE_COMM_FAILURE: + case CR_MACHINE_UNAVAILABLE: + case CR_NO_CM_SERVICES: return ERROR_SERVICE_NOT_ACTIVE; + case CR_ACCESS_DENIED: return ERROR_ACCESS_DENIED; + case CR_CALL_NOT_IMPLEMENTED: return ERROR_CALL_NOT_IMPLEMENTED; + } + + return default_error; +} diff --git a/dlls/combase/apartment.c b/dlls/combase/apartment.c index eb63fea6ef8..38a58aa0e72 100644 --- a/dlls/combase/apartment.c +++ b/dlls/combase/apartment.c @@ -1159,6 +1159,11 @@ void leave_apartment(struct tlsdata *data) if (data->ole_inits) WARN( "Uninitializing apartment while Ole is still initialized\n" ); apartment_release(data->apt); + if (data->implicit_mta) + { + apartment_release(data->implicit_mta); + data->implicit_mta = NULL; + } data->apt = NULL; data->flags &= ~(OLETLS_DISABLE_OLE1DDE | OLETLS_APARTMENTTHREADED | OLETLS_MULTITHREADED); } @@ -1290,3 +1295,30 @@ void apartment_global_cleanup(void) apartment_release_dlls(); DeleteCriticalSection(&apt_cs); } + +HRESULT reference_implicit_mta_from_sta(void) +{ + struct tlsdata *data; + HRESULT hr; + struct apartment *apt, *apt_mt; + + if (FAILED(hr = com_get_tlsdata(&data))) + return hr; + if ((apt = data->apt) && (data->implicit_mta || apt->multi_threaded)) + return S_OK; + + EnterCriticalSection(&apt_cs); + if (apt && !mta) + apt_mt = mta = apartment_construct(COINIT_MULTITHREADED); + else if ((apt_mt = mta)) + apartment_addref(mta); + LeaveCriticalSection(&apt_cs); + + if (!apt_mt) + { + ERR("Apartment not initialized.\n"); + return CO_E_NOTINITIALIZED; + } + data->implicit_mta = apt_mt; + return S_OK; +} diff --git a/dlls/combase/combase.c b/dlls/combase/combase.c index 0695bb77405..16c149679df 100644 --- a/dlls/combase/combase.c +++ b/dlls/combase/combase.c @@ -410,6 +410,9 @@ static void com_cleanup_tlsdata(void) if (tlsdata->apt) apartment_release(tlsdata->apt); + if (tlsdata->implicit_mta) + apartment_release(tlsdata->implicit_mta); + if (tlsdata->errorinfo) IErrorInfo_Release(tlsdata->errorinfo); if (tlsdata->state) diff --git a/dlls/combase/combase_private.h b/dlls/combase/combase_private.h index 19e3def0b4e..79f3aba91b0 100644 --- a/dlls/combase/combase_private.h +++ b/dlls/combase/combase_private.h @@ -92,6 +92,7 @@ struct tlsdata struct list spies; /* Spies installed with CoRegisterInitializeSpy */ DWORD spies_lock; DWORD cancelcount; + struct apartment *implicit_mta; /* mta referenced by roapi from sta thread */ }; extern HRESULT WINAPI InternalTlsAllocData(struct tlsdata **data); @@ -161,6 +162,7 @@ void apartment_release(struct apartment *apt) DECLSPEC_HIDDEN; struct apartment * apartment_get_current_or_mta(void) DECLSPEC_HIDDEN; HRESULT apartment_increment_mta_usage(CO_MTA_USAGE_COOKIE *cookie) DECLSPEC_HIDDEN; void apartment_decrement_mta_usage(CO_MTA_USAGE_COOKIE cookie) DECLSPEC_HIDDEN; +HRESULT reference_implicit_mta_from_sta(void) DECLSPEC_HIDDEN; struct apartment * apartment_get_mta(void) DECLSPEC_HIDDEN; HRESULT apartment_get_inproc_class_object(struct apartment *apt, const struct class_reg_data *regdata, REFCLSID rclsid, REFIID riid, DWORD class_context, void **ppv) DECLSPEC_HIDDEN; diff --git a/dlls/combase/roapi.c b/dlls/combase/roapi.c index 78f35de39d4..807a9059fa7 100644 --- a/dlls/combase/roapi.c +++ b/dlls/combase/roapi.c @@ -24,6 +24,8 @@ #include "roerrorapi.h" #include "winstring.h" +#include "combase_private.h" + #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(combase); @@ -163,6 +165,9 @@ HRESULT WINAPI RoGetActivationFactory(HSTRING classid, REFIID iid, void **class_ if (!iid || !class_factory) return E_INVALIDARG; + if (FAILED(hr = reference_implicit_mta_from_sta())) + return hr; + hr = get_library_for_classid(WindowsGetStringRawBuffer(classid, NULL), &library); if (FAILED(hr)) { diff --git a/dlls/combase/tests/roapi.c b/dlls/combase/tests/roapi.c index f10cbb4507b..ef0035dae4e 100644 --- a/dlls/combase/tests/roapi.c +++ b/dlls/combase/tests/roapi.c @@ -116,12 +116,210 @@ static void test_ActivationFactories(void) RoUninitialize(); } +static APTTYPE check_thread_apttype; +static APTTYPEQUALIFIER check_thread_aptqualifier; +static HRESULT check_thread_hr; + +static DWORD WINAPI check_apartment_thread(void *dummy) +{ + check_thread_apttype = 0xdeadbeef; + check_thread_aptqualifier = 0xdeadbeef; + check_thread_hr = CoGetApartmentType(&check_thread_apttype, &check_thread_aptqualifier); + return 0; +} + +#define check_thread_apartment(a) check_thread_apartment_(__LINE__, FALSE, a) +#define check_thread_apartment_broken(a) check_thread_apartment_(__LINE__, TRUE, a) +static void check_thread_apartment_(unsigned int line, BOOL broken_fail, HRESULT expected_hr_thread) +{ + HANDLE thread; + + check_thread_hr = 0xdeadbeef; + thread = CreateThread(NULL, 0, check_apartment_thread, NULL, 0, NULL); + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + ok_(__FILE__, line)(check_thread_hr == expected_hr_thread + || broken(broken_fail && expected_hr_thread == S_OK && check_thread_hr == CO_E_NOTINITIALIZED), + "got %#lx, expected %#lx.\n", check_thread_hr, expected_hr_thread); + if (SUCCEEDED(check_thread_hr)) + { + ok_(__FILE__, line)(check_thread_apttype == APTTYPE_MTA, "got %d.\n", check_thread_apttype); + ok_(__FILE__, line)(check_thread_aptqualifier == APTTYPEQUALIFIER_IMPLICIT_MTA, "got %d.\n", check_thread_aptqualifier); + } +} + +static HANDLE mta_init_thread_init_done_event, mta_init_thread_done_event; + +static DWORD WINAPI mta_init_thread(void *dummy) +{ + HRESULT hr; + + hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + ok(hr == S_OK, "got %#lx.\n", hr); + SetEvent(mta_init_thread_init_done_event); + + WaitForSingleObject(mta_init_thread_done_event, INFINITE); + CoUninitialize(); + return 0; +} + +static DWORD WINAPI mta_init_implicit_thread(void *dummy) +{ + IActivationFactory *factory; + HSTRING str; + HRESULT hr; + + hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = WindowsCreateString(L"Does.Not.Exist", ARRAY_SIZE(L"Does.Not.Exist") - 1, &str); + ok(hr == S_OK, "got %#lx.\n", hr); + hr = RoGetActivationFactory(str, &IID_IActivationFactory, (void **)&factory); + ok(hr == REGDB_E_CLASSNOTREG, "got %#lx.\n", hr); + WindowsDeleteString(str); + + SetEvent(mta_init_thread_init_done_event); + WaitForSingleObject(mta_init_thread_done_event, INFINITE); + + /* No CoUninitialize(), testing cleanup on thread exit. */ + return 0; +} + +static void test_implicit_mta(void) +{ + static const struct + { + BOOL ro_init; + BOOL mta; + BOOL ro_uninit; + } + tests[] = + { + { TRUE, TRUE, TRUE }, + { TRUE, FALSE, FALSE }, + { TRUE, FALSE, TRUE }, + { FALSE, FALSE, FALSE }, + { FALSE, FALSE, TRUE }, + }; + APTTYPEQUALIFIER aptqualifier; + IActivationFactory *factory; + APTTYPE apttype; + unsigned int i; + HANDLE thread; + HSTRING str; + HRESULT hr; + + hr = WindowsCreateString(L"Does.Not.Exist", ARRAY_SIZE(L"Does.Not.Exist") - 1, &str); + ok(hr == S_OK, "got %#lx.\n", hr); + /* RoGetActivationFactory doesn't implicitly initialize COM. */ + hr = RoGetActivationFactory(str, &IID_IActivationFactory, (void **)&factory); + ok(hr == CO_E_NOTINITIALIZED, "got %#lx.\n", hr); + + check_thread_apartment(CO_E_NOTINITIALIZED); + + /* RoGetActivationFactory initializes implicit MTA. */ + for (i = 0; i < ARRAY_SIZE(tests); ++i) + { + winetest_push_context("test %u", i); + if (tests[i].ro_init) + hr = RoInitialize(tests[i].mta ? RO_INIT_MULTITHREADED : RO_INIT_SINGLETHREADED); + else + hr = CoInitializeEx(NULL, tests[i].mta ? COINIT_MULTITHREADED : COINIT_APARTMENTTHREADED); + ok(hr == S_OK, "got %#lx.\n", hr); + check_thread_apartment(tests[i].mta ? S_OK : CO_E_NOTINITIALIZED); + hr = RoGetActivationFactory(str, &IID_IActivationFactory, (void **)&factory); + ok(hr == REGDB_E_CLASSNOTREG, "got %#lx.\n", hr); + check_thread_apartment_broken(S_OK); /* Broken on Win8. */ + hr = RoGetActivationFactory(str, &IID_IActivationFactory, (void **)&factory); + ok(hr == REGDB_E_CLASSNOTREG, "got %#lx.\n", hr); + check_thread_apartment_broken(S_OK); /* Broken on Win8. */ + if (tests[i].ro_uninit) + RoUninitialize(); + else + CoUninitialize(); + check_thread_apartment(CO_E_NOTINITIALIZED); + winetest_pop_context(); + } + + mta_init_thread_init_done_event = CreateEventW(NULL, FALSE, FALSE, NULL); + mta_init_thread_done_event = CreateEventW(NULL, FALSE, FALSE, NULL); + + /* RoGetActivationFactory references implicit MTA in a current thread + * even if implicit MTA was already initialized: check with STA init + * after RoGetActivationFactory(). */ + thread = CreateThread(NULL, 0, mta_init_thread, NULL, 0, NULL); + ok(!!thread, "failed.\n"); + WaitForSingleObject(mta_init_thread_init_done_event, INFINITE); + check_thread_apartment(S_OK); + + hr = RoGetActivationFactory(str, &IID_IActivationFactory, (void **)&factory); + ok(hr == REGDB_E_CLASSNOTREG, "got %#lx.\n", hr); + check_thread_apartment(S_OK); + + hr = CoGetApartmentType(&apttype, &aptqualifier); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(apttype == APTTYPE_MTA, "got %d.\n", apttype); + ok(aptqualifier == APTTYPEQUALIFIER_IMPLICIT_MTA, "got %d.\n", aptqualifier); + + hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = CoGetApartmentType(&apttype, &aptqualifier); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(apttype == APTTYPE_MAINSTA, "got %d.\n", apttype); + ok(aptqualifier == APTTYPEQUALIFIER_NONE, "got %d.\n", aptqualifier); + + SetEvent(mta_init_thread_done_event); + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + check_thread_apartment_broken(S_OK); /* Broken on Win8. */ + CoUninitialize(); + check_thread_apartment(CO_E_NOTINITIALIZED); + + /* RoGetActivationFactory references implicit MTA in a current thread + * even if implicit MTA was already initialized: check with STA init + * before RoGetActivationFactory(). */ + thread = CreateThread(NULL, 0, mta_init_thread, NULL, 0, NULL); + ok(!!thread, "failed.\n"); + WaitForSingleObject(mta_init_thread_init_done_event, INFINITE); + check_thread_apartment(S_OK); + + hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = RoGetActivationFactory(str, &IID_IActivationFactory, (void **)&factory); + ok(hr == REGDB_E_CLASSNOTREG, "got %#lx.\n", hr); + check_thread_apartment(S_OK); + + SetEvent(mta_init_thread_done_event); + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + check_thread_apartment_broken(S_OK); /* Broken on Win8. */ + CoUninitialize(); + check_thread_apartment(CO_E_NOTINITIALIZED); + + /* Test implicit MTA apartment thread exit. */ + thread = CreateThread(NULL, 0, mta_init_implicit_thread, NULL, 0, NULL); + ok(!!thread, "failed.\n"); + WaitForSingleObject(mta_init_thread_init_done_event, INFINITE); + check_thread_apartment_broken(S_OK); /* Broken on Win8. */ + SetEvent(mta_init_thread_done_event); + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + check_thread_apartment(CO_E_NOTINITIALIZED); + + CloseHandle(mta_init_thread_init_done_event); + CloseHandle(mta_init_thread_done_event); + WindowsDeleteString(str); +} + START_TEST(roapi) { BOOL ret; load_resource(L"wine.combase.test.dll"); + test_implicit_mta(); test_ActivationFactories(); SetLastError(0xdeadbeef); diff --git a/dlls/d3dx9_36/mesh.c b/dlls/d3dx9_36/mesh.c index f033ac14cb7..47a0cadde67 100644 --- a/dlls/d3dx9_36/mesh.c +++ b/dlls/d3dx9_36/mesh.c @@ -1624,6 +1624,13 @@ static HRESULT remap_faces_for_attrsort(struct d3dx9_mesh *This, const DWORD *in return D3D_OK; } +static DWORD adjacency_remap(DWORD *face_remap, DWORD index) +{ + if (index == 0xffffffff) + return index; + return face_remap[index]; +} + static HRESULT WINAPI d3dx9_mesh_OptimizeInplace(ID3DXMesh *iface, DWORD flags, const DWORD *adjacency_in, DWORD *adjacency_out, DWORD *face_remap_out, ID3DXBuffer **vertex_remap_out) { @@ -1778,9 +1785,10 @@ static HRESULT WINAPI d3dx9_mesh_OptimizeInplace(ID3DXMesh *iface, DWORD flags, for (i = 0; i < This->numfaces; i++) { DWORD old_pos = i * 3; DWORD new_pos = face_remap[i] * 3; - adjacency_out[new_pos++] = face_remap[adjacency_in[old_pos++]]; - adjacency_out[new_pos++] = face_remap[adjacency_in[old_pos++]]; - adjacency_out[new_pos++] = face_remap[adjacency_in[old_pos++]]; + + adjacency_out[new_pos++] = adjacency_remap(face_remap, adjacency_in[old_pos++]); + adjacency_out[new_pos++] = adjacency_remap(face_remap, adjacency_in[old_pos++]); + adjacency_out[new_pos] = adjacency_remap(face_remap, adjacency_in[old_pos]); } } else { memcpy(adjacency_out, adjacency_in, This->numfaces * 3 * sizeof(*adjacency_out)); diff --git a/dlls/d3dx9_36/tests/mesh.c b/dlls/d3dx9_36/tests/mesh.c index f44e8f9be52..c988c368382 100644 --- a/dlls/d3dx9_36/tests/mesh.c +++ b/dlls/d3dx9_36/tests/mesh.c @@ -11439,6 +11439,122 @@ static void test_load_skin_mesh_from_xof(void) DestroyWindow(hwnd); } +static void test_mesh_optimize(void) +{ +/* + * . _ . + * / \ / \ + * . _ . _ . + * \ / \ / + * . _ . + */ + static const struct + { + float c[3]; + float n[3]; + float t[2]; + } + vertices[] = + { + { {-0.5f, -1.0f, 0.0f,}, {0.0f, 0.0f, 1.0f}, {-0.5f, -1.0f} }, + { { 0.5f, -1.0f, 0.0f,}, {0.0f, 0.0f, 1.0f}, { 0.5f, -1.0f} }, + + { {-1.0f, 0.0f, 0.0f,}, {0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f} }, + { { 0.0f, 0.0f, 0.0f,}, {0.0f, 0.0f, 1.0f}, { 0.0f, 0.0f} }, + { { 1.0f, 0.0f, 0.0f,}, {0.0f, 0.0f, 1.0f}, { 1.0f, 0.0f} }, + + { {-0.5f, 1.0f, 0.0f,}, {0.0f, 0.0f, 1.0f}, {-0.5f, 1.0f} }, + { { 0.5f, 1.0f, 0.0f,}, {0.0f, 0.0f, 1.0f}, { 0.5f, 1.0f} }, + }; + static const unsigned short indices[] = + { + 3, 0, 2, + 3, 2, 5, + 3, 5, 6, + 3, 6, 4, + 3, 4, 1, + 3, 1, 0, + }; + static const DWORD attrs[] = { 1, 2, 1, 2, 1, 2 }; + static const DWORD expected_adjacency[] = + { + 5, 0xffffffff, 1, + 0, 0xffffffff, 2, + 1, 0xffffffff, 3, + 2, 0xffffffff, 4, + 3, 0xffffffff, 5, + 4, 0xffffffff, 0, + }; + static const DWORD expected_adjacency_out[] = + { + 5, 0xffffffff, 3, + 3, 0xffffffff, 4, + 4, 0xffffffff, 5, + 0, 0xffffffff, 1, + 1, 0xffffffff, 2, + 2, 0xffffffff, 0, + }; + + DWORD adjacency[6 * 3], adjacency_out[6 * 3]; + struct test_context *test_context; + IDirect3DDevice9 *device; + ID3DXBuffer *buffer; + ID3DXMesh *mesh; + unsigned int i; + DWORD size; + HRESULT hr; + void *data; + + test_context = new_test_context(); + if (!test_context) + { + skip("Couldn't create test context\n"); + return; + } + device = test_context->device; + + hr = D3DXCreateMeshFVF(ARRAY_SIZE(attrs), ARRAY_SIZE(vertices), D3DXMESH_VB_MANAGED | D3DXMESH_IB_MANAGED, + D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1, device, &mesh); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = mesh->lpVtbl->LockVertexBuffer(mesh, 0, &data); + ok(hr == S_OK, "got %#lx.\n", hr); + memcpy(data, vertices, sizeof(vertices)); + hr = mesh->lpVtbl->UnlockVertexBuffer(mesh); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = mesh->lpVtbl->LockIndexBuffer(mesh, 0, &data); + ok(hr == S_OK, "got %#lx.\n", hr); + memcpy(data, indices, sizeof(indices)); + hr = mesh->lpVtbl->UnlockIndexBuffer(mesh); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = mesh->lpVtbl->LockAttributeBuffer(mesh, 0, (DWORD **)&data); + ok(hr == S_OK, "got %#lx.\n", hr); + memcpy(data, attrs, sizeof(attrs)); + hr = mesh->lpVtbl->UnlockAttributeBuffer(mesh); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = mesh->lpVtbl->GenerateAdjacency(mesh, 0.0f, adjacency); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(!memcmp(adjacency, expected_adjacency, sizeof(adjacency)), "data mismatch.\n"); + + hr = mesh->lpVtbl->OptimizeInplace(mesh, D3DXMESHOPT_IGNOREVERTS | D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_DONOTSPLIT, + adjacency, adjacency_out, NULL, &buffer); + ok(hr == S_OK, "got %#lx.\n", hr); + + size = buffer->lpVtbl->GetBufferSize(buffer); + ok(size == sizeof(DWORD) * ARRAY_SIZE(vertices), "got %lu.\n", size); + data = buffer->lpVtbl->GetBufferPointer(buffer); + for (i = 0; i < ARRAY_SIZE(vertices); ++i) + ok(((DWORD *)data)[i] == i, "i %u, got %lu.\n", i, ((DWORD *)data)[i]); + ok(!memcmp(adjacency_out, expected_adjacency_out, sizeof(adjacency)), "data mismatch.\n"); + + buffer->lpVtbl->Release(buffer); + mesh->lpVtbl->Release(mesh); + free_test_context(test_context); +} + START_TEST(mesh) { D3DXBoundProbeTest(); @@ -11472,4 +11588,5 @@ START_TEST(mesh) test_compute_normals(); test_D3DXFrameFind(); test_load_skin_mesh_from_xof(); + test_mesh_optimize(); } diff --git a/dlls/dcomp/dcomp.spec b/dlls/dcomp/dcomp.spec index 8d625cc036a..2120ce8b6f6 100644 --- a/dlls/dcomp/dcomp.spec +++ b/dlls/dcomp/dcomp.spec @@ -14,7 +14,7 @@ @ stub DCompositionAttachMouseDragToHwnd @ stub DCompositionAttachMouseWheelToHwnd @ stdcall DCompositionCreateDevice2(ptr ptr ptr) -@ stub DCompositionCreateDevice3 +@ stdcall DCompositionCreateDevice3(ptr ptr ptr) @ stdcall DCompositionCreateDevice(ptr ptr ptr) @ stub DCompositionCreateSurfaceHandle @ stub DeserializeEffectDescription diff --git a/dlls/dcomp/device.c b/dlls/dcomp/device.c index 2d0600ce50a..2744d758e91 100644 --- a/dlls/dcomp/device.c +++ b/dlls/dcomp/device.c @@ -38,3 +38,10 @@ HRESULT WINAPI DCompositionCreateDevice2(IUnknown *rendering_device, REFIID iid, return E_NOTIMPL; } + +HRESULT WINAPI DCompositionCreateDevice3(IUnknown *rendering_device, REFIID iid, void **device) +{ + FIXME("%p, %s, %p.\n", rendering_device, debugstr_guid(iid), device); + + return E_NOTIMPL; +} diff --git a/dlls/dinput/mouse.c b/dlls/dinput/mouse.c index ec30c825733..2d4e0a6b42c 100644 --- a/dlls/dinput/mouse.c +++ b/dlls/dinput/mouse.c @@ -306,20 +306,13 @@ static void warp_check( struct mouse *impl, BOOL force ) if (force || (impl->need_warp && (now - impl->last_warped > interval))) { - RECT rect, new_rect; POINT mapped_center; + RECT rect; impl->last_warped = now; impl->need_warp = FALSE; if (!GetClientRect( impl->base.win, &rect )) return; MapWindowPoints( impl->base.win, 0, (POINT *)&rect, 2 ); - if (!impl->clipped) - { - mapped_center.x = (rect.left + rect.right) / 2; - mapped_center.y = (rect.top + rect.bottom) / 2; - TRACE( "Warping mouse to x %+ld, y %+ld.\n", mapped_center.x, mapped_center.y ); - SetCursorPos( mapped_center.x, mapped_center.y ); - } if (impl->base.dwCoopLevel & DISCL_EXCLUSIVE) { /* make sure we clip even if the window covers the whole screen */ @@ -328,8 +321,14 @@ static void warp_check( struct mouse *impl, BOOL force ) rect.right = min( rect.right, rect.left + GetSystemMetrics( SM_CXVIRTUALSCREEN ) - 2 ); rect.bottom = min( rect.bottom, rect.top + GetSystemMetrics( SM_CYVIRTUALSCREEN ) - 2 ); TRACE("Clipping mouse to %s\n", wine_dbgstr_rect( &rect )); - ClipCursor( &rect ); - impl->clipped = GetClipCursor( &new_rect ) && EqualRect( &rect, &new_rect ); + impl->clipped = ClipCursor( &rect ); + } + if (!impl->clipped) + { + mapped_center.x = (rect.left + rect.right) / 2; + mapped_center.y = (rect.top + rect.bottom) / 2; + TRACE( "Warping mouse to x %+ld, y %+ld.\n", mapped_center.x, mapped_center.y ); + SetCursorPos( mapped_center.x, mapped_center.y ); } } } diff --git a/dlls/dsound/capture.c b/dlls/dsound/capture.c index 6ddae8286cd..a2b866f29c3 100644 --- a/dlls/dsound/capture.c +++ b/dlls/dsound/capture.c @@ -859,10 +859,6 @@ static ULONG DirectSoundCaptureDevice_Release( if (!ref) { TRACE("deleting object\n"); - EnterCriticalSection(&DSOUND_capturers_lock); - list_remove(&device->entry); - LeaveCriticalSection(&DSOUND_capturers_lock); - if (device->capture_buffer) IDirectSoundCaptureBufferImpl_Release(&device->capture_buffer->IDirectSoundCaptureBuffer8_iface); @@ -1038,12 +1034,9 @@ static HRESULT DirectSoundCaptureDevice_Initialize( if(FAILED(hr)) return hr; - EnterCriticalSection(&DSOUND_capturers_lock); - hr = DirectSoundCaptureDevice_Create(&device); if (hr != DS_OK) { WARN("DirectSoundCaptureDevice_Create failed\n"); - LeaveCriticalSection(&DSOUND_capturers_lock); return hr; } @@ -1061,7 +1054,6 @@ static HRESULT DirectSoundCaptureDevice_Initialize( device->lock.DebugInfo->Spare[0] = 0; DeleteCriticalSection(&device->lock); HeapFree(GetProcessHeap(), 0, device); - LeaveCriticalSection(&DSOUND_capturers_lock); return DSERR_NODRIVER; } @@ -1074,12 +1066,8 @@ static HRESULT DirectSoundCaptureDevice_Initialize( } IAudioClient_Release(client); - list_add_tail(&DSOUND_capturers, &device->entry); - *ppDevice = device; - LeaveCriticalSection(&DSOUND_capturers_lock); - return S_OK; } diff --git a/dlls/dsound/dsound_main.c b/dlls/dsound/dsound_main.c index 69cbec72ea3..d18733294d0 100644 --- a/dlls/dsound/dsound_main.c +++ b/dlls/dsound/dsound_main.c @@ -76,19 +76,6 @@ static CRITICAL_SECTION_DEBUG DSOUND_renderers_lock_debug = }; CRITICAL_SECTION DSOUND_renderers_lock = { &DSOUND_renderers_lock_debug, -1, 0, 0, 0, 0 }; -struct list DSOUND_capturers = LIST_INIT(DSOUND_capturers); -CRITICAL_SECTION DSOUND_capturers_lock; -static CRITICAL_SECTION_DEBUG DSOUND_capturers_lock_debug = -{ - 0, 0, &DSOUND_capturers_lock, - { &DSOUND_capturers_lock_debug.ProcessLocksList, &DSOUND_capturers_lock_debug.ProcessLocksList }, - 0, 0, { (DWORD_PTR)(__FILE__ ": DSOUND_capturers_lock") } -}; -CRITICAL_SECTION DSOUND_capturers_lock = { &DSOUND_capturers_lock_debug, -1, 0, 0, 0, 0 }; - -GUID DSOUND_renderer_guids[MAXWAVEDRIVERS]; -GUID DSOUND_capture_guids[MAXWAVEDRIVERS]; - const WCHAR wine_vxd_drv[] = L"winemm.vxd"; /* All default settings, you most likely don't want to touch these, see wiki on UsefulRegistryKeys */ @@ -401,13 +388,13 @@ HRESULT get_mmdevice(EDataFlow flow, const GUID *tgt, IMMDevice **device) return DSERR_INVALIDPARAM; } -static BOOL send_device(IMMDevice *device, GUID *guid, - LPDSENUMCALLBACKW cb, void *user) +static BOOL send_device(IMMDevice *device, LPDSENUMCALLBACKW cb, void *user) { IPropertyStore *ps; PROPVARIANT pv; BOOL keep_going; HRESULT hr; + GUID guid; PropVariantInit(&pv); @@ -417,7 +404,7 @@ static BOOL send_device(IMMDevice *device, GUID *guid, return TRUE; } - hr = get_mmdevice_guid(device, ps, guid); + hr = get_mmdevice_guid(device, ps, &guid); if(FAILED(hr)){ IPropertyStore_Release(ps); return TRUE; @@ -431,10 +418,10 @@ static BOOL send_device(IMMDevice *device, GUID *guid, return TRUE; } - TRACE("Calling back with %s (%s)\n", wine_dbgstr_guid(guid), + TRACE("Calling back with %s (%s)\n", wine_dbgstr_guid(&guid), wine_dbgstr_w(pv.pwszVal)); - keep_going = cb(guid, pv.pwszVal, wine_vxd_drv, user); + keep_going = cb(&guid, pv.pwszVal, wine_vxd_drv, user); PropVariantClear(&pv); IPropertyStore_Release(ps); @@ -444,13 +431,12 @@ static BOOL send_device(IMMDevice *device, GUID *guid, /* S_FALSE means the callback returned FALSE at some point * S_OK means the callback always returned TRUE */ -HRESULT enumerate_mmdevices(EDataFlow flow, GUID *guids, - LPDSENUMCALLBACKW cb, void *user) +HRESULT enumerate_mmdevices(EDataFlow flow, LPDSENUMCALLBACKW cb, void *user) { IMMDeviceEnumerator *devenum; IMMDeviceCollection *coll; IMMDevice *defdev = NULL; - UINT count, i, n; + UINT count, i; BOOL keep_going; HRESULT hr, init_hr; @@ -489,10 +475,8 @@ HRESULT enumerate_mmdevices(EDataFlow flow, GUID *guids, eMultimedia, &defdev); if(FAILED(hr)){ defdev = NULL; - n = 0; }else{ - keep_going = send_device(defdev, &guids[0], cb, user); - n = 1; + keep_going = send_device(defdev, cb, user); } } @@ -506,8 +490,7 @@ HRESULT enumerate_mmdevices(EDataFlow flow, GUID *guids, } if(device != defdev){ - keep_going = send_device(device, &guids[n], cb, user); - ++n; + keep_going = send_device(device, cb, user); } IMMDevice_Release(device); @@ -550,8 +533,7 @@ HRESULT WINAPI DirectSoundEnumerateW( setup_dsound_options(); - hr = enumerate_mmdevices(eRender, DSOUND_renderer_guids, - lpDSEnumCallback, lpContext); + hr = enumerate_mmdevices(eRender, lpDSEnumCallback, lpContext); return SUCCEEDED(hr) ? DS_OK : hr; } @@ -614,8 +596,7 @@ DirectSoundCaptureEnumerateW( setup_dsound_options(); - hr = enumerate_mmdevices(eCapture, DSOUND_capture_guids, - lpDSEnumCallback, lpContext); + hr = enumerate_mmdevices(eCapture, lpDSEnumCallback, lpContext); return SUCCEEDED(hr) ? DS_OK : hr; } @@ -782,7 +763,6 @@ BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpvReserved) case DLL_PROCESS_DETACH: if (lpvReserved) break; DeleteCriticalSection(&DSOUND_renderers_lock); - DeleteCriticalSection(&DSOUND_capturers_lock); break; } return TRUE; diff --git a/dlls/dsound/dsound_private.h b/dlls/dsound/dsound_private.h index c39d9ec40ea..081a56b2ee7 100644 --- a/dlls/dsound/dsound_private.h +++ b/dlls/dsound/dsound_private.h @@ -252,12 +252,8 @@ HRESULT IDirectSoundCaptureImpl_Create(IUnknown *outer_unk, REFIID riid, void ** #define STATE_STOPPING 3 extern CRITICAL_SECTION DSOUND_renderers_lock DECLSPEC_HIDDEN; -extern CRITICAL_SECTION DSOUND_capturers_lock DECLSPEC_HIDDEN; -extern struct list DSOUND_capturers DECLSPEC_HIDDEN; extern struct list DSOUND_renderers DECLSPEC_HIDDEN; -extern GUID DSOUND_renderer_guids[MAXWAVEDRIVERS] DECLSPEC_HIDDEN; -extern GUID DSOUND_capture_guids[MAXWAVEDRIVERS] DECLSPEC_HIDDEN; extern const WCHAR wine_vxd_drv[] DECLSPEC_HIDDEN; @@ -267,8 +263,7 @@ HRESULT get_mmdevice(EDataFlow flow, const GUID *tgt, IMMDevice **device) DECLSP BOOL DSOUND_check_supported(IAudioClient *client, DWORD rate, DWORD depth, WORD channels) DECLSPEC_HIDDEN; -HRESULT enumerate_mmdevices(EDataFlow flow, GUID *guids, - LPDSENUMCALLBACKW cb, void *user) DECLSPEC_HIDDEN; +HRESULT enumerate_mmdevices(EDataFlow flow, LPDSENUMCALLBACKW cb, void *user) DECLSPEC_HIDDEN; static inline WCHAR *strdupW( const WCHAR *str ) { diff --git a/dlls/dsound/propset.c b/dlls/dsound/propset.c index f42cfbf4163..e72757bdb4b 100644 --- a/dlls/dsound/propset.c +++ b/dlls/dsound/propset.c @@ -136,11 +136,9 @@ static HRESULT DSPROPERTY_WaveDeviceMappingW( search.found_guid = &ppd->DeviceId; if (ppd->DataFlow == DIRECTSOUNDDEVICE_DATAFLOW_RENDER) - hr = enumerate_mmdevices(eRender, DSOUND_renderer_guids, - search_callback, &search); + hr = enumerate_mmdevices(eRender, search_callback, &search); else if (ppd->DataFlow == DIRECTSOUNDDEVICE_DATAFLOW_CAPTURE) - hr = enumerate_mmdevices(eCapture, DSOUND_capture_guids, - search_callback, &search); + hr = enumerate_mmdevices(eCapture, search_callback, &search); else return DSERR_INVALIDPARAM; @@ -318,12 +316,10 @@ static HRESULT DSPROPERTY_EnumerateW( return E_PROP_ID_UNSUPPORTED; } - hr = enumerate_mmdevices(eRender, DSOUND_renderer_guids, - enum_callback, ppd); + hr = enumerate_mmdevices(eRender, enum_callback, ppd); if(hr == S_OK) - hr = enumerate_mmdevices(eCapture, DSOUND_capture_guids, - enum_callback, ppd); + hr = enumerate_mmdevices(eCapture, enum_callback, ppd); return SUCCEEDED(hr) ? DS_OK : hr; } diff --git a/dlls/evr/evr_private.h b/dlls/evr/evr_private.h index ef38b0f70cf..93047b50c94 100644 --- a/dlls/evr/evr_private.h +++ b/dlls/evr/evr_private.h @@ -55,4 +55,6 @@ HRESULT evr_filter_create(IUnknown *outer_unk, void **ppv) DECLSPEC_HIDDEN; HRESULT evr_mixer_create(IUnknown *outer_unk, void **ppv) DECLSPEC_HIDDEN; HRESULT evr_presenter_create(IUnknown *outer_unk, void **ppv) DECLSPEC_HIDDEN; +HRESULT create_video_sample_allocator(BOOL lock_notify_release, REFIID riid, void **obj); + #endif /* __EVR_PRIVATE_INCLUDED__ */ diff --git a/dlls/evr/presenter.c b/dlls/evr/presenter.c index 06592f8766e..2dc01608caf 100644 --- a/dlls/evr/presenter.c +++ b/dlls/evr/presenter.c @@ -55,7 +55,6 @@ enum streaming_thread_message { EVRM_STOP = WM_USER, EVRM_PRESENT = WM_USER + 1, - EVRM_PROCESS_INPUT = WM_USER + 2, }; struct sample_queue @@ -66,6 +65,7 @@ struct sample_queue unsigned int front; unsigned int back; IMFSample *last_presented; + CRITICAL_SECTION cs; }; struct streaming_thread @@ -425,6 +425,7 @@ static HRESULT video_presenter_sample_queue_init(struct video_presenter *present queue->size = presenter->allocator_capacity; queue->back = queue->size - 1; + InitializeCriticalSection(&queue->cs); return S_OK; } @@ -435,7 +436,7 @@ static void video_presenter_sample_queue_push(struct video_presenter *presenter, struct sample_queue *queue = &presenter->thread.queue; unsigned int idx; - EnterCriticalSection(&presenter->cs); + EnterCriticalSection(&queue->cs); if (queue->used != queue->size) { if (at_front) @@ -446,14 +447,14 @@ static void video_presenter_sample_queue_push(struct video_presenter *presenter, queue->used++; IMFSample_AddRef(sample); } - LeaveCriticalSection(&presenter->cs); + LeaveCriticalSection(&queue->cs); } static BOOL video_presenter_sample_queue_pop(struct video_presenter *presenter, IMFSample **sample) { struct sample_queue *queue = &presenter->thread.queue; - EnterCriticalSection(&presenter->cs); + EnterCriticalSection(&queue->cs); if (queue->used) { *sample = queue->samples[queue->front]; @@ -462,11 +463,24 @@ static BOOL video_presenter_sample_queue_pop(struct video_presenter *presenter, } else *sample = NULL; - LeaveCriticalSection(&presenter->cs); + LeaveCriticalSection(&queue->cs); return *sample != NULL; } + +static void video_presenter_sample_queue_free(struct video_presenter *presenter) +{ + struct sample_queue *queue = &presenter->thread.queue; + IMFSample *sample; + + while (video_presenter_sample_queue_pop(presenter, &sample)) + IMFSample_Release(sample); + + free(queue->samples); + DeleteCriticalSection(&queue->cs); +} + static HRESULT video_presenter_get_sample_surface(IMFSample *sample, IDirect3DSurface9 **surface) { IMFMediaBuffer *buffer; @@ -490,6 +504,7 @@ static void video_presenter_sample_present(struct video_presenter *presenter, IM { IDirect3DSurface9 *surface, *backbuffer; IDirect3DDevice9 *device; + struct sample_queue *queue = &presenter->thread.queue; HRESULT hr; if (FAILED(hr = video_presenter_get_sample_surface(sample, &surface))) @@ -515,12 +530,12 @@ static void video_presenter_sample_present(struct video_presenter *presenter, IM WARN("Failed to get a backbuffer, hr %#lx.\n", hr); } - EnterCriticalSection(&presenter->cs); - if (presenter->thread.queue.last_presented) - IMFSample_Release(presenter->thread.queue.last_presented); - presenter->thread.queue.last_presented = sample; - IMFSample_AddRef(presenter->thread.queue.last_presented); - LeaveCriticalSection(&presenter->cs); + EnterCriticalSection(&queue->cs); + if (queue->last_presented) + IMFSample_Release(queue->last_presented); + queue->last_presented = sample; + IMFSample_AddRef(queue->last_presented); + LeaveCriticalSection(&queue->cs); IDirect3DSurface9_Release(surface); } @@ -690,11 +705,6 @@ static DWORD CALLBACK video_presenter_streaming_thread(void *arg) } break; - case EVRM_PROCESS_INPUT: - EnterCriticalSection(&presenter->cs); - video_presenter_process_input(presenter); - LeaveCriticalSection(&presenter->cs); - break; default: ; } @@ -754,6 +764,7 @@ static HRESULT video_presenter_end_streaming(struct video_presenter *presenter) if (presenter->thread.queue.last_presented) IMFSample_Release(presenter->thread.queue.last_presented); + video_presenter_sample_queue_free(presenter); memset(&presenter->thread, 0, sizeof(presenter->thread)); video_presenter_set_allocator_callback(presenter, NULL); @@ -1794,9 +1805,9 @@ static HRESULT WINAPI video_presenter_allocator_cb_NotifyRelease(IMFVideoSampleA { struct video_presenter *presenter = impl_from_IMFVideoSampleAllocatorNotify(iface); - /* Release notification is executed under allocator lock, instead of processing samples here - notify streaming thread. */ - PostThreadMessageW(presenter->thread.tid, EVRM_PROCESS_INPUT, 0, 0); + EnterCriticalSection(&presenter->cs); + video_presenter_process_input(presenter); + LeaveCriticalSection(&presenter->cs); return S_OK; } @@ -2132,7 +2143,7 @@ static HRESULT video_presenter_init_d3d(struct video_presenter *presenter) if (FAILED(hr)) WARN("Failed to set new device for the manager, hr %#lx.\n", hr); - if (SUCCEEDED(hr = MFCreateVideoSampleAllocator(&IID_IMFVideoSampleAllocator, (void **)&presenter->allocator))) + if (SUCCEEDED(hr = create_video_sample_allocator(FALSE, &IID_IMFVideoSampleAllocator, (void **)&presenter->allocator))) { hr = IMFVideoSampleAllocator_SetDirectXManager(presenter->allocator, (IUnknown *)presenter->device_manager); } diff --git a/dlls/evr/sample.c b/dlls/evr/sample.c index 6a1bbf564f5..aa3f120b115 100644 --- a/dlls/evr/sample.c +++ b/dlls/evr/sample.c @@ -395,6 +395,7 @@ struct sample_allocator unsigned int free_sample_count; struct list free_samples; struct list used_samples; + BOOL lock_notify_release; CRITICAL_SECTION cs; }; @@ -809,6 +810,7 @@ static HRESULT WINAPI sample_allocator_tracking_callback_Invoke(IMFAsyncCallback struct queued_sample *iter; IUnknown *object = NULL; IMFSample *sample = NULL; + IMFVideoSampleAllocatorNotify *callback = NULL; HRESULT hr; if (FAILED(IMFAsyncResult_GetObject(result, &object))) @@ -836,10 +838,24 @@ static HRESULT WINAPI sample_allocator_tracking_callback_Invoke(IMFAsyncCallback IMFSample_Release(sample); if (allocator->callback) - IMFVideoSampleAllocatorNotify_NotifyRelease(allocator->callback); + { + if (allocator->lock_notify_release) + IMFVideoSampleAllocatorNotify_NotifyRelease(allocator->callback); + else + { + callback = allocator->callback; + IMFVideoSampleAllocatorNotify_AddRef(callback); + } + } LeaveCriticalSection(&allocator->cs); + if (callback) + { + IMFVideoSampleAllocatorNotify_NotifyRelease(callback); + IMFVideoSampleAllocatorNotify_Release(callback); + } + return S_OK; } @@ -852,13 +868,11 @@ static const IMFAsyncCallbackVtbl sample_allocator_tracking_callback_vtbl = sample_allocator_tracking_callback_Invoke, }; -HRESULT WINAPI MFCreateVideoSampleAllocator(REFIID riid, void **obj) +HRESULT create_video_sample_allocator(BOOL lock_notify_release, REFIID riid, void **obj) { struct sample_allocator *object; HRESULT hr; - TRACE("%s, %p.\n", debugstr_guid(riid), obj); - if (!(object = calloc(1, sizeof(*object)))) return E_OUTOFMEMORY; @@ -868,6 +882,7 @@ HRESULT WINAPI MFCreateVideoSampleAllocator(REFIID riid, void **obj) object->refcount = 1; list_init(&object->used_samples); list_init(&object->free_samples); + object->lock_notify_release = lock_notify_release; InitializeCriticalSection(&object->cs); hr = IMFVideoSampleAllocator_QueryInterface(&object->IMFVideoSampleAllocator_iface, riid, obj); @@ -876,6 +891,13 @@ HRESULT WINAPI MFCreateVideoSampleAllocator(REFIID riid, void **obj) return hr; } +HRESULT WINAPI MFCreateVideoSampleAllocator(REFIID riid, void **obj) +{ + TRACE("%s, %p.\n", debugstr_guid(riid), obj); + + return create_video_sample_allocator(TRUE, riid, obj); +} + static HRESULT WINAPI video_sample_QueryInterface(IMFSample *iface, REFIID riid, void **out) { struct video_sample *sample = impl_from_IMFSample(iface); diff --git a/dlls/gdi32/objects.c b/dlls/gdi32/objects.c index 24ac5b015ea..bddc29a3007 100644 --- a/dlls/gdi32/objects.c +++ b/dlls/gdi32/objects.c @@ -418,7 +418,7 @@ HGDIOBJ WINAPI GetCurrentObject( HDC hdc, UINT type ) /*********************************************************************** * GetStockObject (GDI32.@) */ -HGDIOBJ WINAPI GetStockObject( INT obj ) +HGDIOBJ WINAPI DECLSPEC_HOTPATCH GetStockObject( INT obj ) { if (obj < 0 || obj > STOCK_LAST + 1 || obj == 9) return 0; diff --git a/dlls/imm32/Makefile.in b/dlls/imm32/Makefile.in index 29de6063792..baf10c5f1dc 100644 --- a/dlls/imm32/Makefile.in +++ b/dlls/imm32/Makefile.in @@ -1,9 +1,10 @@ MODULE = imm32.dll IMPORTLIB = imm32 -IMPORTS = user32 gdi32 advapi32 win32u +IMPORTS = user32 gdi32 advapi32 kernelbase win32u DELAYIMPORTS = ole32 C_SRCS = \ + ime.c \ imm.c RC_SRCS = version.rc diff --git a/dlls/imm32/ime.c b/dlls/imm32/ime.c new file mode 100644 index 00000000000..38a2a070117 --- /dev/null +++ b/dlls/imm32/ime.c @@ -0,0 +1,766 @@ +/* + * Copyright 2023 Rémi Bernon for CodeWeavers + * + * 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 + */ + +#include +#include + +#include "imm_private.h" + +WINE_DEFAULT_DEBUG_CHANNEL(imm); + +struct ime_private +{ + BOOL in_composition; + HFONT hfont; +}; + +static const char *debugstr_imn( WPARAM wparam ) +{ + switch (wparam) + { + case IMN_OPENSTATUSWINDOW: return "IMN_OPENSTATUSWINDOW"; + case IMN_CLOSESTATUSWINDOW: return "IMN_CLOSESTATUSWINDOW"; + case IMN_OPENCANDIDATE: return "IMN_OPENCANDIDATE"; + case IMN_CHANGECANDIDATE: return "IMN_CHANGECANDIDATE"; + case IMN_CLOSECANDIDATE: return "IMN_CLOSECANDIDATE"; + case IMN_SETCONVERSIONMODE: return "IMN_SETCONVERSIONMODE"; + case IMN_SETSENTENCEMODE: return "IMN_SETSENTENCEMODE"; + case IMN_SETOPENSTATUS: return "IMN_SETOPENSTATUS"; + case IMN_SETCANDIDATEPOS: return "IMN_SETCANDIDATEPOS"; + case IMN_SETCOMPOSITIONFONT: return "IMN_SETCOMPOSITIONFONT"; + case IMN_SETCOMPOSITIONWINDOW: return "IMN_SETCOMPOSITIONWINDOW"; + case IMN_GUIDELINE: return "IMN_GUIDELINE"; + case IMN_SETSTATUSWINDOWPOS: return "IMN_SETSTATUSWINDOWPOS"; + case IMN_WINE_SET_OPEN_STATUS: return "IMN_WINE_SET_OPEN_STATUS"; + case IMN_WINE_SET_COMP_STRING: return "IMN_WINE_SET_COMP_STRING"; + default: return wine_dbg_sprintf( "%#Ix", wparam ); + } +} + +static const char *debugstr_imc( WPARAM wparam ) +{ + switch (wparam) + { + case IMC_GETCANDIDATEPOS: return "IMC_GETCANDIDATEPOS"; + case IMC_SETCANDIDATEPOS: return "IMC_SETCANDIDATEPOS"; + case IMC_GETCOMPOSITIONFONT: return "IMC_GETCOMPOSITIONFONT"; + case IMC_SETCOMPOSITIONFONT: return "IMC_SETCOMPOSITIONFONT"; + case IMC_GETCOMPOSITIONWINDOW: return "IMC_GETCOMPOSITIONWINDOW"; + case IMC_SETCOMPOSITIONWINDOW: return "IMC_SETCOMPOSITIONWINDOW"; + case IMC_GETSTATUSWINDOWPOS: return "IMC_GETSTATUSWINDOWPOS"; + case IMC_SETSTATUSWINDOWPOS: return "IMC_SETSTATUSWINDOWPOS"; + case IMC_CLOSESTATUSWINDOW: return "IMC_CLOSESTATUSWINDOW"; + case IMC_OPENSTATUSWINDOW: return "IMC_OPENSTATUSWINDOW"; + default: return wine_dbg_sprintf( "%#Ix", wparam ); + } +} + +static WCHAR *input_context_get_comp_str( INPUTCONTEXT *ctx, BOOL result, UINT *length ) +{ + COMPOSITIONSTRING *string; + WCHAR *text = NULL; + UINT len, off; + + if (!(string = ImmLockIMCC( ctx->hCompStr ))) return NULL; + len = result ? string->dwResultStrLen : string->dwCompStrLen; + off = result ? string->dwResultStrOffset : string->dwCompStrOffset; + + if (len && off && (text = malloc( (len + 1) * sizeof(WCHAR) ))) + { + memcpy( text, (BYTE *)string + off, len * sizeof(WCHAR) ); + text[len] = 0; + *length = len; + } + + ImmUnlockIMCC( ctx->hCompStr ); + return text; +} + +static void input_context_set_comp_str( INPUTCONTEXT *ctx, const WCHAR *str, UINT len ) +{ + COMPOSITIONSTRING *compstr; + HIMCC himcc; + UINT size; + BYTE *dst; + + TRACE( "ctx %p, str %s\n", ctx, debugstr_wn( str, len ) ); + + size = sizeof(*compstr); + size += len * sizeof(WCHAR); /* GCS_COMPSTR */ + size += len; /* GCS_COMPSTRATTR */ + size += 2 * sizeof(DWORD); /* GCS_COMPSTRCLAUSE */ + + if (!(himcc = ImmReSizeIMCC( ctx->hCompStr, size ))) + WARN( "Failed to resize input context composition string\n" ); + else if (!(compstr = ImmLockIMCC( (ctx->hCompStr = himcc) ))) + WARN( "Failed to lock input context composition string\n" ); + else + { + memset( compstr, 0, sizeof(*compstr) ); + compstr->dwSize = sizeof(*compstr); + + if (len) + { + compstr->dwCursorPos = len; + + compstr->dwCompStrLen = len; + compstr->dwCompStrOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwCompStrOffset; + memcpy( dst, str, compstr->dwCompStrLen * sizeof(WCHAR) ); + compstr->dwSize += compstr->dwCompStrLen * sizeof(WCHAR); + + compstr->dwCompClauseLen = 2 * sizeof(DWORD); + compstr->dwCompClauseOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwCompClauseOffset; + *((DWORD *)dst + 0) = 0; + *((DWORD *)dst + 1) = compstr->dwCompStrLen; + compstr->dwSize += compstr->dwCompClauseLen; + + compstr->dwCompAttrLen = compstr->dwCompStrLen; + compstr->dwCompAttrOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwCompAttrOffset; + memset( dst, ATTR_INPUT, compstr->dwCompAttrLen ); + compstr->dwSize += compstr->dwCompAttrLen; + } + + ImmUnlockIMCC( ctx->hCompStr ); + } +} + +static HFONT input_context_select_ui_font( INPUTCONTEXT *ctx, HDC hdc ) +{ + struct ime_private *priv; + HFONT font = NULL; + if (!(priv = ImmLockIMCC( ctx->hPrivate ))) return NULL; + if (priv->hfont) font = SelectObject( hdc, priv->hfont ); + ImmUnlockIMCC( ctx->hPrivate ); + return font; +} + +static void ime_send_message( HIMC himc, UINT message, WPARAM wparam, LPARAM lparam ) +{ + INPUTCONTEXT *ctx; + TRANSMSG *msgs; + HIMCC himcc; + + if (!(ctx = ImmLockIMC( himc ))) return; + if (!(himcc = ImmReSizeIMCC( ctx->hMsgBuf, (ctx->dwNumMsgBuf + 1) * sizeof(*msgs) ))) + WARN( "Failed to resize input context message buffer\n" ); + else if (!(msgs = ImmLockIMCC( (ctx->hMsgBuf = himcc) ))) + WARN( "Failed to lock input context message buffer\n" ); + else + { + TRANSMSG msg = {.message = message, .wParam = wparam, .lParam = lparam}; + msgs[ctx->dwNumMsgBuf++] = msg; + ImmUnlockIMCC( ctx->hMsgBuf ); + } + + ImmUnlockIMC( himc ); + ImmGenerateMessage( himc ); +} + +static UINT ime_set_composition_status( HIMC himc, BOOL composition ) +{ + struct ime_private *priv; + INPUTCONTEXT *ctx; + UINT msg = 0; + + if (!(ctx = ImmLockIMC( himc ))) return 0; + if ((priv = ImmLockIMCC( ctx->hPrivate ))) + { + if (!priv->in_composition && composition) msg = WM_IME_STARTCOMPOSITION; + else if (priv->in_composition && !composition) msg = WM_IME_ENDCOMPOSITION; + priv->in_composition = composition; + ImmUnlockIMCC( ctx->hPrivate ); + } + ImmUnlockIMC( himc ); + + return msg; +} + +static void ime_ui_paint( HIMC himc, HWND hwnd ) +{ + PAINTSTRUCT ps; + RECT rect, new_rect; + HDC hdc; + HMONITOR monitor; + MONITORINFO mon_info; + INPUTCONTEXT *ctx; + POINT offset; + WCHAR *str; + UINT len; + + if (!(ctx = ImmLockIMC( himc ))) return; + + hdc = BeginPaint( hwnd, &ps ); + + GetClientRect( hwnd, &rect ); + FillRect( hdc, &rect, (HBRUSH)(COLOR_WINDOW + 1) ); + new_rect = rect; + + if ((str = input_context_get_comp_str( ctx, FALSE, &len ))) + { + HFONT font = input_context_select_ui_font( ctx, hdc ); + SIZE size; + POINT pt; + + GetTextExtentPoint32W( hdc, str, len, &size ); + pt.x = size.cx; + pt.y = size.cy; + LPtoDP( hdc, &pt, 1 ); + + /* + * How this works based on tests on windows: + * CFS_POINT: then we start our window at the point and grow it as large + * as it needs to be for the string. + * CFS_RECT: we still use the ptCurrentPos as a starting point and our + * window is only as large as we need for the string, but we do not + * grow such that our window exceeds the given rect. Wrapping if + * needed and possible. If our ptCurrentPos is outside of our rect + * then no window is displayed. + * CFS_FORCE_POSITION: appears to behave just like CFS_POINT + * maybe because the default MSIME does not do any IME adjusting. + */ + if (ctx->cfCompForm.dwStyle != CFS_DEFAULT) + { + POINT cpt = ctx->cfCompForm.ptCurrentPos; + ClientToScreen( ctx->hWnd, &cpt ); + rect.left = cpt.x; + rect.top = cpt.y; + rect.right = rect.left + pt.x; + rect.bottom = rect.top + pt.y; + offset.x = offset.y = 0; + monitor = MonitorFromPoint( cpt, MONITOR_DEFAULTTOPRIMARY ); + } + else /* CFS_DEFAULT */ + { + /* Windows places the default IME window in the bottom left */ + HWND target = ctx->hWnd; + if (!target) target = GetFocus(); + + GetWindowRect( target, &rect ); + rect.top = rect.bottom; + rect.right = rect.left + pt.x + 20; + rect.bottom = rect.top + pt.y + 20; + offset.x = offset.y = 10; + monitor = MonitorFromWindow( target, MONITOR_DEFAULTTOPRIMARY ); + } + + if (ctx->cfCompForm.dwStyle == CFS_RECT) + { + RECT client = ctx->cfCompForm.rcArea; + MapWindowPoints( ctx->hWnd, 0, (POINT *)&client, 2 ); + IntersectRect( &rect, &rect, &client ); + DrawTextW( hdc, str, len, &rect, DT_WORDBREAK | DT_CALCRECT ); + } + + if (ctx->cfCompForm.dwStyle == CFS_DEFAULT) + { + /* make sure we are on the desktop */ + mon_info.cbSize = sizeof(mon_info); + GetMonitorInfoW( monitor, &mon_info ); + + if (rect.bottom > mon_info.rcWork.bottom) + { + int shift = rect.bottom - mon_info.rcWork.bottom; + rect.top -= shift; + rect.bottom -= shift; + } + if (rect.left < 0) + { + rect.right -= rect.left; + rect.left = 0; + } + if (rect.right > mon_info.rcWork.right) + { + int shift = rect.right - mon_info.rcWork.right; + rect.left -= shift; + rect.right -= shift; + } + } + + new_rect = rect; + OffsetRect( &rect, offset.x - rect.left, offset.y - rect.top ); + DrawTextW( hdc, str, len, &rect, DT_WORDBREAK ); + + if (font) SelectObject( hdc, font ); + free( str ); + } + + EndPaint( hwnd, &ps ); + ImmUnlockIMC( himc ); + + if (!EqualRect( &rect, &new_rect )) + SetWindowPos( hwnd, HWND_TOPMOST, new_rect.left, new_rect.top, new_rect.right - new_rect.left, + new_rect.bottom - new_rect.top, SWP_NOACTIVATE ); +} + +static void ime_ui_update_window( INPUTCONTEXT *ctx, HWND hwnd ) +{ + WCHAR *str; + UINT len; + + if (!(str = input_context_get_comp_str( ctx, FALSE, &len )) || !*str) + ShowWindow( hwnd, SW_HIDE ); + else + { + ShowWindow( hwnd, SW_SHOWNOACTIVATE ); + RedrawWindow( hwnd, NULL, NULL, RDW_ERASENOW | RDW_INVALIDATE ); + } + free( str ); + + ctx->hWnd = GetFocus(); +} + +static void ime_ui_composition( HIMC himc, HWND hwnd, LPARAM lparam ) +{ + INPUTCONTEXT *ctx; + if (lparam & GCS_RESULTSTR) return; + if (!(ctx = ImmLockIMC( himc ))) return; + ime_ui_update_window( ctx, hwnd ); + ImmUnlockIMC( himc ); +} + +static void ime_ui_start_composition( HIMC himc, HWND hwnd ) +{ + INPUTCONTEXT *ctx; + if (!(ctx = ImmLockIMC( himc ))) return; + ime_ui_update_window( ctx, hwnd ); + ImmUnlockIMC( himc ); +} + +static UINT ime_set_comp_string( HIMC himc, LPARAM lparam ) +{ + union + { + struct + { + UINT uMsgCount; + TRANSMSG TransMsg[10]; + }; + TRANSMSGLIST list; + } buffer = {.uMsgCount = ARRAY_SIZE(buffer.TransMsg)}; + INPUTCONTEXT *ctx; + TRANSMSG *msgs; + HIMCC himcc; + UINT count; + + TRACE( "himc %p\n", himc ); + + if (!(ctx = ImmLockIMC( himc ))) return 0; + + count = ImeToAsciiEx( VK_PROCESSKEY, lparam, NULL, &buffer.list, 0, himc ); + if (!count) + TRACE( "ImeToAsciiEx returned no messages\n" ); + else if (count >= buffer.uMsgCount) + WARN( "ImeToAsciiEx returned %#x messages\n", count ); + else if (!(himcc = ImmReSizeIMCC( ctx->hMsgBuf, (ctx->dwNumMsgBuf + count) * sizeof(*msgs) ))) + WARN( "Failed to resize input context message buffer\n" ); + else if (!(msgs = ImmLockIMCC( (ctx->hMsgBuf = himcc) ))) + WARN( "Failed to lock input context message buffer\n" ); + else + { + memcpy( msgs + ctx->dwNumMsgBuf, buffer.TransMsg, count * sizeof(*msgs) ); + ImmUnlockIMCC( ctx->hMsgBuf ); + ctx->dwNumMsgBuf += count; + } + + ImmUnlockIMC( himc ); + ImmGenerateMessage( himc ); + + return count; +} + +static LRESULT ime_ui_notify( HIMC himc, HWND hwnd, WPARAM wparam, LPARAM lparam ) +{ + TRACE( "himc %p, hwnd %p, wparam %s, lparam %#Ix\n", hwnd, himc, debugstr_imn(wparam), lparam ); + + switch (wparam) + { + case IMN_WINE_SET_OPEN_STATUS: + return ImmSetOpenStatus( himc, lparam ); + case IMN_WINE_SET_COMP_STRING: + return ime_set_comp_string( himc, lparam ); + default: + FIXME( "himc %p, hwnd %p, wparam %s, lparam %#Ix stub!\n", hwnd, himc, debugstr_imn(wparam), lparam ); + return 0; + } +} + +static LRESULT WINAPI ime_ui_window_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) +{ + HIMC himc = (HIMC)GetWindowLongPtrW( hwnd, IMMGWL_IMC ); + + TRACE( "hwnd %p, himc %p, msg %s, wparam %#Ix, lparam %#Ix\n", + hwnd, himc, debugstr_wm_ime(msg), wparam, lparam ); + + switch (msg) + { + case WM_CREATE: + return TRUE; + case WM_PAINT: + ime_ui_paint( himc, hwnd ); + return FALSE; + case WM_SETFOCUS: + if (wparam) SetFocus( (HWND)wparam ); + else FIXME( "Received focus, should never have focus\n" ); + break; + case WM_IME_COMPOSITION: + ime_ui_composition( himc, hwnd, lparam ); + break; + case WM_IME_STARTCOMPOSITION: + ime_ui_start_composition( himc, hwnd ); + break; + case WM_IME_ENDCOMPOSITION: + ShowWindow( hwnd, SW_HIDE ); + break; + case WM_IME_NOTIFY: + return ime_ui_notify( himc, hwnd, wparam, lparam ); + case WM_IME_CONTROL: + FIXME( "hwnd %p, himc %p, msg %s, wparam %s, lparam %#Ix stub!\n", hwnd, himc, + debugstr_wm_ime(msg), debugstr_imc(wparam), lparam ); + return 0; + } + + return DefWindowProcW( hwnd, msg, wparam, lparam ); +} + +static WNDCLASSEXW ime_ui_class = +{ + .cbSize = sizeof(WNDCLASSEXW), + .style = CS_GLOBALCLASS | CS_IME | CS_HREDRAW | CS_VREDRAW, + .lpfnWndProc = ime_ui_window_proc, + .cbWndExtra = 2 * sizeof(LONG_PTR), + .lpszClassName = L"Wine IME", + .hbrBackground = (HBRUSH)(COLOR_WINDOW + 1), +}; + +BOOL WINAPI ImeInquire( IMEINFO *info, WCHAR *ui_class, DWORD flags ) +{ + TRACE( "info %p, ui_class %p, flags %#lx\n", info, ui_class, flags ); + + ime_ui_class.hInstance = imm32_module; + ime_ui_class.hCursor = LoadCursorW( NULL, (LPWSTR)IDC_ARROW ); + ime_ui_class.hIcon = LoadIconW( NULL, (LPWSTR)IDI_APPLICATION ); + RegisterClassExW( &ime_ui_class ); + + wcscpy( ui_class, ime_ui_class.lpszClassName ); + memset( info, 0, sizeof(*info) ); + info->dwPrivateDataSize = sizeof(struct ime_private); + info->fdwProperty = IME_PROP_UNICODE | IME_PROP_AT_CARET; + info->fdwConversionCaps = IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE; + info->fdwSentenceCaps = IME_SMODE_AUTOMATIC; + info->fdwUICaps = UI_CAP_2700; + /* Tell App we cannot accept ImeSetCompositionString calls */ + info->fdwSCSCaps = 0; + info->fdwSelectCaps = SELECT_CAP_CONVERSION; + + return TRUE; +} + +BOOL WINAPI ImeDestroy( UINT force ) +{ + TRACE( "force %u\n", force ); + UnregisterClassW( ime_ui_class.lpszClassName, imm32_module ); + return TRUE; +} + +BOOL WINAPI ImeSelect( HIMC himc, BOOL select ) +{ + struct ime_private *priv; + INPUTCONTEXT *ctx; + + TRACE( "himc %p, select %u\n", himc, select ); + + if (!himc || !select) return TRUE; + if (!(ctx = ImmLockIMC( himc ))) return FALSE; + + ImmSetOpenStatus( himc, FALSE ); + + if ((priv = ImmLockIMCC( ctx->hPrivate ))) + { + memset( priv, 0, sizeof(*priv) ); + ImmUnlockIMCC( ctx->hPrivate ); + } + + ImmUnlockIMC( himc ); + return TRUE; +} + +BOOL WINAPI ImeSetActiveContext( HIMC himc, BOOL flag ) +{ + static int once; + if (!once++) FIXME( "himc %p, flag %#x stub!\n", himc, flag ); + return TRUE; +} + +BOOL WINAPI ImeProcessKey( HIMC himc, UINT vkey, LPARAM lparam, BYTE *state ) +{ + struct ime_driver_call_params params = {.himc = himc, .state = state}; + INPUTCONTEXT *ctx; + LRESULT ret; + + TRACE( "himc %p, vkey %#x, lparam %#Ix, state %p\n", himc, vkey, lparam, state ); + + if (!(ctx = ImmLockIMC( himc ))) return FALSE; + ret = NtUserMessageCall( ctx->hWnd, WINE_IME_PROCESS_KEY, vkey, lparam, ¶ms, + NtUserImeDriverCall, FALSE ); + ImmUnlockIMC( himc ); + + return ret; +} + +UINT WINAPI ImeToAsciiEx( UINT vkey, UINT vsc, BYTE *state, TRANSMSGLIST *msgs, UINT flags, HIMC himc ) +{ + COMPOSITIONSTRING *compstr; + UINT size, count = 0; + INPUTCONTEXT *ctx; + NTSTATUS status; + + TRACE( "vkey %#x, vsc %#x, state %p, msgs %p, flags %#x, himc %p\n", + vkey, vsc, state, msgs, flags, himc ); + + if (!(ctx = ImmLockIMC( himc ))) return 0; + if (!(compstr = ImmLockIMCC( ctx->hCompStr ))) goto done; + size = max( compstr->dwSize, sizeof(COMPOSITIONSTRING) ); + + do + { + struct ime_driver_call_params params = {.himc = himc, .state = state}; + HIMCC himcc; + + ImmUnlockIMCC( ctx->hCompStr ); + if (!(himcc = ImmReSizeIMCC( ctx->hCompStr, size ))) goto done; + if (!(compstr = ImmLockIMCC( (ctx->hCompStr = himcc) ))) goto done; + + params.compstr = compstr; + status = NtUserMessageCall( ctx->hWnd, WINE_IME_TO_ASCII_EX, vkey, vsc, ¶ms, + NtUserImeDriverCall, FALSE ); + size = compstr->dwSize; + } while (status == STATUS_BUFFER_TOO_SMALL); + + if (status) WARN( "WINE_IME_TO_ASCII_EX returned status %#lx\n", status ); + else + { + TRANSMSG status_msg = {.message = ime_set_composition_status( himc, !!compstr->dwCompStrOffset )}; + if (status_msg.message) msgs->TransMsg[count++] = status_msg; + + if (compstr->dwResultStrOffset) + { + const WCHAR *result = (WCHAR *)((BYTE *)compstr + compstr->dwResultStrOffset); + TRANSMSG msg = {.message = WM_IME_COMPOSITION, .wParam = result[0], .lParam = GCS_RESULTSTR}; + if (compstr->dwResultClauseOffset) msg.lParam |= GCS_RESULTCLAUSE; + msgs->TransMsg[count++] = msg; + } + + if (compstr->dwCompStrOffset) + { + const WCHAR *comp = (WCHAR *)((BYTE *)compstr + compstr->dwCompStrOffset); + TRANSMSG msg = {.message = WM_IME_COMPOSITION, .wParam = comp[0], .lParam = GCS_COMPSTR | GCS_CURSORPOS | GCS_DELTASTART}; + if (compstr->dwCompAttrOffset) msg.lParam |= GCS_COMPATTR; + if (compstr->dwCompClauseOffset) msg.lParam |= GCS_COMPCLAUSE; + else msg.lParam |= CS_INSERTCHAR|CS_NOMOVECARET; + msgs->TransMsg[count++] = msg; + } + } + + ImmUnlockIMCC( ctx->hCompStr ); + +done: + if (count >= msgs->uMsgCount) FIXME( "More than %u messages queued, messages possibly lost\n", msgs->uMsgCount ); + else TRACE( "Returning %u messages queued\n", count ); + ImmUnlockIMC( himc ); + return count; +} + +BOOL WINAPI ImeConfigure( HKL hkl, HWND hwnd, DWORD mode, void *data ) +{ + FIXME( "hkl %p, hwnd %p, mode %lu, data %p stub!\n", hkl, hwnd, mode, data ); + SetLastError( ERROR_CALL_NOT_IMPLEMENTED ); + return FALSE; +} + +DWORD WINAPI ImeConversionList( HIMC himc, const WCHAR *source, CANDIDATELIST *dest, DWORD dest_len, UINT flag ) +{ + FIXME( "himc %p, source %s, dest %p, dest_len %lu, flag %#x stub!\n", + himc, debugstr_w(source), dest, dest_len, flag ); + SetLastError( ERROR_CALL_NOT_IMPLEMENTED ); + return 0; +} + +BOOL WINAPI ImeSetCompositionString( HIMC himc, DWORD index, const void *comp, DWORD comp_len, + const void *read, DWORD read_len ) +{ + INPUTCONTEXT *ctx; + + FIXME( "himc %p, index %lu, comp %p, comp_len %lu, read %p, read_len %lu semi-stub!\n", + himc, index, comp, comp_len, read, read_len ); + if (read && read_len) FIXME( "Read string unimplemented\n" ); + if (index != SCS_SETSTR && index != SCS_CHANGECLAUSE && index != SCS_CHANGEATTR) return FALSE; + + if (!(ctx = ImmLockIMC( himc ))) return FALSE; + + if (index != SCS_SETSTR) + FIXME( "index %#lx not implemented\n", index ); + else + { + UINT msg, flags = GCS_COMPSTR | GCS_COMPCLAUSE | GCS_COMPATTR | GCS_DELTASTART | GCS_CURSORPOS; + WCHAR wparam = comp && comp_len >= sizeof(WCHAR) ? *(WCHAR *)comp : 0; + input_context_set_comp_str( ctx, comp, comp_len / sizeof(WCHAR) ); + if ((msg = ime_set_composition_status( himc, TRUE ))) ime_send_message( himc, msg, 0, 0 ); + ime_send_message( himc, WM_IME_COMPOSITION, wparam, flags ); + } + + ImmUnlockIMC( himc ); + + return TRUE; +} + +BOOL WINAPI NotifyIME( HIMC himc, DWORD action, DWORD index, DWORD value ) +{ + struct ime_private *priv; + INPUTCONTEXT *ctx; + UINT msg; + + TRACE( "himc %p, action %#lx, index %#lx, value %#lx stub!\n", himc, action, index, value ); + + if (!(ctx = ImmLockIMC( himc ))) return FALSE; + + switch (action) + { + case NI_CONTEXTUPDATED: + switch (value) + { + case IMC_SETCOMPOSITIONFONT: + if ((priv = ImmLockIMCC( ctx->hPrivate ))) + { + if (priv->hfont) DeleteObject( priv->hfont ); + priv->hfont = CreateFontIndirectW( &ctx->lfFont.W ); + ImmUnlockIMCC( ctx->hPrivate ); + } + break; + case IMC_SETOPENSTATUS: + if (!ctx->fOpen) + { + input_context_set_comp_str( ctx, NULL, 0 ); + if ((msg = ime_set_composition_status( himc, FALSE ))) ime_send_message( himc, msg, 0, 0 ); + } + NtUserNotifyIMEStatus( ctx->hWnd, ctx->fOpen ); + break; + default: + FIXME( "himc %p, action %#lx, index %#lx, value %#lx stub!\n", himc, action, index, value ); + break; + } + break; + + case NI_COMPOSITIONSTR: + switch (index) + { + case CPS_COMPLETE: + { + COMPOSITIONSTRING *compstr; + + if (!(compstr = ImmLockIMCC( ctx->hCompStr ))) + WARN( "Failed to lock input context composition string\n" ); + else + { + WCHAR wchr = *(WCHAR *)((BYTE *)compstr + compstr->dwCompStrOffset); + COMPOSITIONSTRING tmp = *compstr; + UINT flags = 0; + + memset( compstr, 0, sizeof(*compstr) ); + compstr->dwSize = tmp.dwSize; + compstr->dwResultStrLen = tmp.dwCompStrLen; + compstr->dwResultStrOffset = tmp.dwCompStrOffset; + compstr->dwResultClauseLen = tmp.dwCompClauseLen; + compstr->dwResultClauseOffset = tmp.dwCompClauseOffset; + ImmUnlockIMCC( ctx->hCompStr ); + + if (tmp.dwCompStrLen) flags |= GCS_RESULTSTR; + if (tmp.dwCompClauseLen) flags |= GCS_RESULTCLAUSE; + if (flags) ime_send_message( himc, WM_IME_COMPOSITION, wchr, flags ); + } + + ImmSetOpenStatus( himc, FALSE ); + break; + } + case CPS_CANCEL: + input_context_set_comp_str( ctx, NULL, 0 ); + if ((msg = ime_set_composition_status( himc, FALSE ))) ime_send_message( himc, msg, 0, 0 ); + NtUserNotifyIMEStatus( ctx->hWnd, FALSE ); + break; + default: + FIXME( "himc %p, action %#lx, index %#lx, value %#lx stub!\n", himc, action, index, value ); + break; + } + break; + + default: + FIXME( "himc %p, action %#lx, index %#lx, value %#lx stub!\n", himc, action, index, value ); + break; + } + + ImmUnlockIMC( himc ); + return TRUE; +} + +LRESULT WINAPI ImeEscape( HIMC himc, UINT escape, void *data ) +{ + FIXME( "himc %p, escape %#x, data %p stub!\n", himc, escape, data ); + SetLastError( ERROR_CALL_NOT_IMPLEMENTED ); + return 0; +} + +DWORD WINAPI ImeGetImeMenuItems( HIMC himc, DWORD flags, DWORD type, IMEMENUITEMINFOW *parent, + IMEMENUITEMINFOW *menu, DWORD size ) +{ + FIXME( "himc %p, flags %#lx, type %lu, parent %p, menu %p, size %#lx stub!\n", + himc, flags, type, parent, menu, size ); + SetLastError( ERROR_CALL_NOT_IMPLEMENTED ); + return 0; +} + +BOOL WINAPI ImeRegisterWord( const WCHAR *reading, DWORD style, const WCHAR *string ) +{ + FIXME( "reading %s, style %lu, string %s stub!\n", debugstr_w(reading), style, debugstr_w(string) ); + SetLastError( ERROR_CALL_NOT_IMPLEMENTED ); + return FALSE; +} + +UINT WINAPI ImeGetRegisterWordStyle( UINT item, STYLEBUFW *style ) +{ + FIXME( "item %u, style %p stub!\n", item, style ); + SetLastError( ERROR_CALL_NOT_IMPLEMENTED ); + return 0; +} + +BOOL WINAPI ImeUnregisterWord( const WCHAR *reading, DWORD style, const WCHAR *string ) +{ + FIXME( "reading %s, style %lu, string %s stub!\n", debugstr_w(reading), style, debugstr_w(string) ); + SetLastError( ERROR_CALL_NOT_IMPLEMENTED ); + return FALSE; +} + +UINT WINAPI ImeEnumRegisterWord( REGISTERWORDENUMPROCW proc, const WCHAR *reading, DWORD style, + const WCHAR *string, void *data ) +{ + FIXME( "proc %p, reading %s, style %lu, string %s, data %p stub!\n", + proc, debugstr_w(reading), style, debugstr_w(string), data ); + SetLastError( ERROR_CALL_NOT_IMPLEMENTED ); + return 0; +} diff --git a/dlls/imm32/imm.c b/dlls/imm32/imm.c index 3d9cbf7e198..a17593d18a8 100644 --- a/dlls/imm32/imm.c +++ b/dlls/imm32/imm.c @@ -20,49 +20,45 @@ */ #define COBJMACROS - -#include -#include - #include "initguid.h" -#include "objbase.h" -#include "windef.h" -#include "winbase.h" -#include "wingdi.h" -#include "ntuser.h" -#include "winerror.h" -#include "wine/debug.h" -#include "imm.h" -#include "immdev.h" -#include "winnls.h" -#include "winreg.h" -#include "wine/list.h" +#include "imm_private.h" WINE_DEFAULT_DEBUG_CHANNEL(imm); #define IMM_INIT_MAGIC 0x19650412 BOOL WINAPI User32InitializeImmEntryTable(DWORD); +HMODULE imm32_module; + /* MSIME messages */ -static UINT WM_MSIME_SERVICE; -static UINT WM_MSIME_RECONVERTOPTIONS; -static UINT WM_MSIME_MOUSE; -static UINT WM_MSIME_RECONVERTREQUEST; -static UINT WM_MSIME_RECONVERT; -static UINT WM_MSIME_QUERYPOSITION; -static UINT WM_MSIME_DOCUMENTFEED; - -typedef struct _tagImmHkl{ +UINT WM_MSIME_SERVICE; +UINT WM_MSIME_RECONVERTOPTIONS; +UINT WM_MSIME_MOUSE; +UINT WM_MSIME_RECONVERTREQUEST; +UINT WM_MSIME_RECONVERT; +UINT WM_MSIME_QUERYPOSITION; +UINT WM_MSIME_DOCUMENTFEED; + +struct imc_entry +{ + HIMC himc; + INPUTCONTEXT context; struct list entry; - HKL hkl; - HMODULE hIME; - IMEINFO imeInfo; - WCHAR imeClassName[17]; /* 16 character max */ - ULONG uSelected; - HWND UIWnd; - - /* Function Pointers */ - BOOL (WINAPI *pImeInquire)(IMEINFO *, WCHAR *, DWORD); +}; + +struct ime +{ + LONG refcount; /* guarded by ime_cs */ + + HKL hkl; + HMODULE module; + struct list entry; + + IMEINFO info; + WCHAR ui_class[17]; + struct list input_contexts; + + BOOL (WINAPI *pImeInquire)(IMEINFO *, void *, DWORD); BOOL (WINAPI *pImeConfigure)(HKL, HWND, DWORD, void *); BOOL (WINAPI *pImeDestroy)(UINT); LRESULT (WINAPI *pImeEscape)(HIMC, UINT, void *); @@ -70,30 +66,30 @@ typedef struct _tagImmHkl{ BOOL (WINAPI *pImeSetActiveContext)(HIMC, BOOL); UINT (WINAPI *pImeToAsciiEx)(UINT, UINT, const BYTE *, TRANSMSGLIST *, UINT, HIMC); BOOL (WINAPI *pNotifyIME)(HIMC, DWORD, DWORD, DWORD); - BOOL (WINAPI *pImeRegisterWord)(const WCHAR *, DWORD, const WCHAR *); - BOOL (WINAPI *pImeUnregisterWord)(const WCHAR *, DWORD, const WCHAR *); - UINT (WINAPI *pImeEnumRegisterWord)(REGISTERWORDENUMPROCW, const WCHAR *, DWORD, const WCHAR *, void *); - BOOL (WINAPI *pImeSetCompositionString)(HIMC, DWORD, const void *, DWORD, const void *, DWORD); - DWORD (WINAPI *pImeConversionList)(HIMC, const WCHAR *, CANDIDATELIST *, DWORD, UINT); - BOOL (WINAPI *pImeProcessKey)(HIMC, UINT, LPARAM, const BYTE *); - UINT (WINAPI *pImeGetRegisterWordStyle)(UINT, STYLEBUFW *); - DWORD (WINAPI *pImeGetImeMenuItems)(HIMC, DWORD, DWORD, IMEMENUITEMINFOW *, IMEMENUITEMINFOW *, DWORD); -} ImmHkl; + BOOL (WINAPI *pImeRegisterWord)(const void/*TCHAR*/*, DWORD, const void/*TCHAR*/*); + BOOL (WINAPI *pImeUnregisterWord)(const void/*TCHAR*/*, DWORD, const void/*TCHAR*/*); + UINT (WINAPI *pImeEnumRegisterWord)(void */*REGISTERWORDENUMPROCW*/, const void/*TCHAR*/*, DWORD, const void/*TCHAR*/*, void *); + BOOL (WINAPI *pImeSetCompositionString)(HIMC, DWORD, const void/*TCHAR*/*, DWORD, const void/*TCHAR*/*, DWORD); + DWORD (WINAPI *pImeConversionList)(HIMC, const void/*TCHAR*/*, CANDIDATELIST*, DWORD, UINT); + UINT (WINAPI *pImeGetRegisterWordStyle)(UINT, void/*STYLEBUFW*/*); + BOOL (WINAPI *pImeProcessKey)(HIMC, UINT, LPARAM, const BYTE*); + DWORD (WINAPI *pImeGetImeMenuItems)(HIMC, DWORD, DWORD, void/*IMEMENUITEMINFOW*/*, void/*IMEMENUITEMINFOW*/*, DWORD); +}; static HRESULT (WINAPI *pCoRevokeInitializeSpy)(ULARGE_INTEGER cookie); static void (WINAPI *pCoUninitialize)(void); -typedef struct tagInputContextData +struct imc { HIMC handle; DWORD dwLock; INPUTCONTEXT IMC; - DWORD threadID; - ImmHkl *immKbd; - UINT lastVK; - BOOL threadDefault; -} InputContextData; + struct ime *ime; + UINT vkey; + + HWND ui_hwnd; /* IME UI window, on the default input context */ +}; #define WINE_IMC_VALID_MAGIC 0x56434D49 @@ -111,22 +107,47 @@ struct coinit_spy } apt_flags; }; -static struct list ImmHklList = LIST_INIT(ImmHklList); +static CRITICAL_SECTION ime_cs; +static CRITICAL_SECTION_DEBUG ime_cs_debug = +{ + 0, 0, &ime_cs, + { &ime_cs_debug.ProcessLocksList, &ime_cs_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": ime_cs") } +}; +static CRITICAL_SECTION ime_cs = { &ime_cs_debug, -1, 0, 0, 0, 0 }; +static struct list ime_list = LIST_INIT( ime_list ); + +static const WCHAR layouts_formatW[] = L"System\\CurrentControlSet\\Control\\Keyboard Layouts\\%08lx"; + +static const char *debugstr_composition( const COMPOSITIONFORM *composition ) +{ + if (!composition) return "(null)"; + return wine_dbg_sprintf( "style %#lx, pos %s, area %s", composition->dwStyle, + wine_dbgstr_point( &composition->ptCurrentPos ), + wine_dbgstr_rect( &composition->rcArea ) ); +} -static const WCHAR szImeRegFmt[] = L"System\\CurrentControlSet\\Control\\Keyboard Layouts\\%08lx"; +static const char *debugstr_candidate( const CANDIDATEFORM *candidate ) +{ + if (!candidate) return "(null)"; + return wine_dbg_sprintf( "idx %#lx, style %#lx, pos %s, area %s", candidate->dwIndex, + candidate->dwStyle, wine_dbgstr_point( &candidate->ptCurrentPos ), + wine_dbgstr_rect( &candidate->rcArea ) ); +} -static inline BOOL is_himc_ime_unicode(const InputContextData *data) +static BOOL ime_is_unicode( const struct ime *ime ) { - return !!(data->immKbd->imeInfo.fdwProperty & IME_PROP_UNICODE); + return !!(ime->info.fdwProperty & IME_PROP_UNICODE); } -static inline BOOL is_kbd_ime_unicode(const ImmHkl *hkl) +static BOOL input_context_is_unicode( INPUTCONTEXT *ctx ) { - return !!(hkl->imeInfo.fdwProperty & IME_PROP_UNICODE); + struct imc *imc = CONTAINING_RECORD( ctx, struct imc, IMC ); + return !imc->ime || ime_is_unicode( imc->ime ); } static BOOL IMM_DestroyContext(HIMC hIMC); -static InputContextData* get_imc_data(HIMC hIMC); +static struct imc *get_imc_data( HIMC hIMC ); static inline WCHAR *strdupAtoW( const char *str ) { @@ -134,8 +155,7 @@ static inline WCHAR *strdupAtoW( const char *str ) if (str) { DWORD len = MultiByteToWideChar( CP_ACP, 0, str, -1, NULL, 0 ); - if ((ret = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ))) - MultiByteToWideChar( CP_ACP, 0, str, -1, ret, len ); + if ((ret = malloc( len * sizeof(WCHAR) ))) MultiByteToWideChar( CP_ACP, 0, str, -1, ret, len ); } return ret; } @@ -146,8 +166,7 @@ static inline CHAR *strdupWtoA( const WCHAR *str ) if (str) { DWORD len = WideCharToMultiByte( CP_ACP, 0, str, -1, NULL, 0, NULL, NULL ); - if ((ret = HeapAlloc( GetProcessHeap(), 0, len ))) - WideCharToMultiByte( CP_ACP, 0, str, -1, ret, len, NULL, NULL ); + if ((ret = malloc( len ))) WideCharToMultiByte( CP_ACP, 0, str, -1, ret, len, NULL, NULL ); } return ret; } @@ -295,7 +314,7 @@ static ULONG WINAPI InitializeSpy_Release(IInitializeSpy *iface) LONG ref = InterlockedDecrement(&spy->ref); if (!ref) { - HeapFree(GetProcessHeap(), 0, spy); + free( spy ); NtUserGetThreadInfo()->client_imm = 0; } return ref; @@ -374,7 +393,7 @@ static void imm_coinit_thread(void) if (!(spy = get_thread_coinit_spy())) { - if (!(spy = HeapAlloc(GetProcessHeap(), 0, sizeof(*spy)))) return; + if (!(spy = malloc( sizeof(*spy) ))) return; spy->IInitializeSpy_iface.lpVtbl = &InitializeSpyVtbl; spy->ref = 1; spy->cookie.QuadPart = 0; @@ -401,461 +420,493 @@ static void imm_coinit_thread(void) InitOnceExecuteOnce(&init_ole32_once, init_ole32_funcs, NULL, NULL); } -static BOOL IMM_IsDefaultContext(HIMC imc) +static struct imc *query_imc_data( HIMC handle ) { - InputContextData *data = get_imc_data(imc); + struct imc *ret; - if (!data) - return FALSE; + if (!handle) return NULL; + ret = (void *)NtUserQueryInputContext(handle, NtUserInputContextClientPtr); + return ret && ret->handle == handle ? ret : NULL; +} - return data->threadDefault; +/* lookup an IME from a HKL, must hold ime_cs */ +static struct ime *find_ime_from_hkl( HKL hkl ) +{ + struct ime *ime = NULL; + LIST_FOR_EACH_ENTRY( ime, &ime_list, struct ime, entry ) + if (ime->hkl == hkl) return ime; + return NULL; } -static InputContextData *query_imc_data(HIMC handle) +BOOL WINAPI ImmFreeLayout( HKL hkl ) { - InputContextData *ret; + struct imc_entry *imc_entry, *imc_next; + struct ime *ime; - if (!handle) return NULL; - ret = (void *)NtUserQueryInputContext(handle, NtUserInputContextClientPtr); - return ret && ret->handle == handle ? ret : NULL; + TRACE( "hkl %p\n", hkl ); + + EnterCriticalSection( &ime_cs ); + if ((ime = find_ime_from_hkl( hkl ))) + { + list_remove( &ime->entry ); + if (!ime->pImeDestroy( 0 )) WARN( "ImeDestroy failed\n" ); + LIST_FOR_EACH_ENTRY_SAFE( imc_entry, imc_next, &ime->input_contexts, struct imc_entry, entry ) + { + ImmDestroyIMCC( imc_entry->context.hPrivate ); + free( imc_entry ); + } + } + LeaveCriticalSection( &ime_cs ); + if (!ime) return TRUE; + + FreeLibrary( ime->module ); + free( ime ); + return TRUE; } -static BOOL free_input_context_data(HIMC hIMC) +BOOL WINAPI ImmLoadIME( HKL hkl ) { - InputContextData *data = query_imc_data(hIMC); + WCHAR buffer[MAX_PATH] = {0}; + BOOL use_default_ime; + struct ime *ime; - if (!data) - return FALSE; + TRACE( "hkl %p\n", hkl ); - TRACE("Destroying %p\n", hIMC); + EnterCriticalSection( &ime_cs ); + if ((ime = find_ime_from_hkl( hkl )) || !(ime = calloc( 1, sizeof(*ime) ))) + { + LeaveCriticalSection( &ime_cs ); + return !!ime; + } + + if (!ImmGetIMEFileNameW( hkl, buffer, MAX_PATH )) use_default_ime = TRUE; + else if (!(ime->module = LoadLibraryW( buffer ))) use_default_ime = TRUE; + else use_default_ime = FALSE; + + if (use_default_ime) + { + if (*buffer) WARN( "Failed to load %s, falling back to default.\n", debugstr_w(buffer) ); + ime->module = LoadLibraryW( L"imm32" ); + ime->pImeInquire = (void *)ImeInquire; + ime->pImeDestroy = ImeDestroy; + ime->pImeSelect = ImeSelect; + ime->pImeConfigure = ImeConfigure; + ime->pImeEscape = ImeEscape; + ime->pImeSetActiveContext = ImeSetActiveContext; + ime->pImeToAsciiEx = (void *)ImeToAsciiEx; + ime->pNotifyIME = NotifyIME; + ime->pImeRegisterWord = (void *)ImeRegisterWord; + ime->pImeUnregisterWord = (void *)ImeUnregisterWord; + ime->pImeEnumRegisterWord = (void *)ImeEnumRegisterWord; + ime->pImeSetCompositionString = ImeSetCompositionString; + ime->pImeConversionList = (void *)ImeConversionList; + ime->pImeProcessKey = (void *)ImeProcessKey; + ime->pImeGetRegisterWordStyle = (void *)ImeGetRegisterWordStyle; + ime->pImeGetImeMenuItems = (void *)ImeGetImeMenuItems; + } + else + { +#define LOAD_FUNCPTR( f ) \ + if (!(ime->p##f = (void *)GetProcAddress( ime->module, #f ))) \ + { \ + WARN( "Can't find function %s in HKL %p IME\n", #f, hkl ); \ + goto failed; \ + } - data->immKbd->uSelected--; - data->immKbd->pImeSelect(hIMC, FALSE); - SendMessageW(data->IMC.hWnd, WM_IME_SELECT, FALSE, (LPARAM)data->immKbd); + LOAD_FUNCPTR( ImeInquire ); + LOAD_FUNCPTR( ImeDestroy ); + LOAD_FUNCPTR( ImeSelect ); + LOAD_FUNCPTR( ImeConfigure ); + LOAD_FUNCPTR( ImeEscape ); + LOAD_FUNCPTR( ImeSetActiveContext ); + LOAD_FUNCPTR( ImeToAsciiEx ); + LOAD_FUNCPTR( NotifyIME ); + LOAD_FUNCPTR( ImeRegisterWord ); + LOAD_FUNCPTR( ImeUnregisterWord ); + LOAD_FUNCPTR( ImeEnumRegisterWord ); + LOAD_FUNCPTR( ImeSetCompositionString ); + LOAD_FUNCPTR( ImeConversionList ); + LOAD_FUNCPTR( ImeProcessKey ); + LOAD_FUNCPTR( ImeGetRegisterWordStyle ); + LOAD_FUNCPTR( ImeGetImeMenuItems ); +#undef LOAD_FUNCPTR + } - ImmDestroyIMCC(data->IMC.hCompStr); - ImmDestroyIMCC(data->IMC.hCandInfo); - ImmDestroyIMCC(data->IMC.hGuideLine); - ImmDestroyIMCC(data->IMC.hPrivate); - ImmDestroyIMCC(data->IMC.hMsgBuf); + ime->hkl = hkl; + if (!ime->pImeInquire( &ime->info, buffer, 0 )) goto failed; - HeapFree(GetProcessHeap(), 0, data); + if (ime_is_unicode( ime )) lstrcpynW( ime->ui_class, buffer, ARRAY_SIZE(ime->ui_class) ); + else MultiByteToWideChar( CP_ACP, 0, (char *)buffer, -1, ime->ui_class, ARRAY_SIZE(ime->ui_class) ); + list_init( &ime->input_contexts ); + list_add_tail( &ime_list, &ime->entry ); + LeaveCriticalSection( &ime_cs ); + + TRACE( "Created IME %p for HKL %p\n", ime, hkl ); return TRUE; + +failed: + LeaveCriticalSection( &ime_cs ); + + if (ime->module) FreeLibrary( ime->module ); + free( ime ); + return FALSE; } -static void IMM_FreeThreadData(void) +static struct ime *ime_acquire( HKL hkl ) { - struct coinit_spy *spy; + struct ime *ime; + + EnterCriticalSection( &ime_cs ); + + if (!ImmLoadIME( hkl )) ime = NULL; + else ime = find_ime_from_hkl( hkl ); + + if (ime) + { + ULONG ref = ++ime->refcount; + TRACE( "ime %p increasing refcount to %lu.\n", ime, ref ); + } - free_input_context_data(UlongToHandle(NtUserGetThreadInfo()->default_imc)); - if ((spy = get_thread_coinit_spy())) - IInitializeSpy_Release(&spy->IInitializeSpy_iface); + LeaveCriticalSection( &ime_cs ); + + return ime; } -static HMODULE load_graphics_driver(void) +static void ime_release( struct ime *ime ) { - static const WCHAR key_pathW[] = L"System\\CurrentControlSet\\Control\\Video\\{"; - static const WCHAR displayW[] = L"}\\0000"; + ULONG ref; - HMODULE ret = 0; - HKEY hkey; - DWORD size; - WCHAR path[MAX_PATH]; - WCHAR key[ARRAY_SIZE( key_pathW ) + ARRAY_SIZE( displayW ) + 40]; - UINT guid_atom = HandleToULong( GetPropW( GetDesktopWindow(), L"__wine_display_device_guid" )); - - if (!guid_atom) return 0; - memcpy( key, key_pathW, sizeof(key_pathW) ); - if (!GlobalGetAtomNameW( guid_atom, key + lstrlenW(key), 40 )) return 0; - lstrcatW( key, displayW ); - if (RegOpenKeyW( HKEY_LOCAL_MACHINE, key, &hkey )) return 0; - size = sizeof(path); - if (!RegQueryValueExW( hkey, L"GraphicsDriver", NULL, NULL, (BYTE *)path, &size )) - ret = LoadLibraryW( path ); - RegCloseKey( hkey ); - TRACE( "%s %p\n", debugstr_w(path), ret ); - return ret; + EnterCriticalSection( &ime_cs ); + + ref = --ime->refcount; + TRACE( "ime %p decreasing refcount to %lu.\n", ime, ref ); + + if (!ref && (ime->info.fdwProperty & IME_PROP_END_UNLOAD)) + ImmFreeLayout( ime->hkl ); + + LeaveCriticalSection( &ime_cs ); } -/* ImmHkl loading and freeing */ -#define LOAD_FUNCPTR(f) if((ptr->p##f = (LPVOID)GetProcAddress(ptr->hIME, #f)) == NULL){WARN("Can't find function %s in ime\n", #f);} -static ImmHkl *IMM_GetImmHkl(HKL hkl) +static void ime_save_input_context( struct ime *ime, HIMC himc, INPUTCONTEXT *ctx ) { - ImmHkl *ptr; - WCHAR filename[MAX_PATH]; + static INPUTCONTEXT default_input_context = + { + .cfCandForm = {{.dwIndex = -1}, {.dwIndex = -1}, {.dwIndex = -1}, {.dwIndex = -1}} + }; + const INPUTCONTEXT old = *ctx; + struct imc_entry *entry; - TRACE("Seeking ime for keyboard %p\n",hkl); + *ctx = default_input_context; + ctx->hWnd = old.hWnd; + ctx->hMsgBuf = old.hMsgBuf; + ctx->hCompStr = old.hCompStr; + ctx->hCandInfo = old.hCandInfo; + ctx->hGuideLine = old.hGuideLine; + if (!(ctx->hPrivate = ImmCreateIMCC( ime->info.dwPrivateDataSize ))) + WARN( "Failed to allocate IME private data\n" ); - LIST_FOR_EACH_ENTRY(ptr, &ImmHklList, ImmHkl, entry) - { - if (ptr->hkl == hkl) - return ptr; - } - /* not found... create it */ + if (!(entry = malloc( sizeof(*entry) ))) return; + entry->himc = himc; + entry->context = *ctx; - ptr = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(ImmHkl)); + EnterCriticalSection( &ime_cs ); - ptr->hkl = hkl; - if (ImmGetIMEFileNameW(hkl, filename, MAX_PATH)) ptr->hIME = LoadLibraryW(filename); - if (!ptr->hIME) ptr->hIME = load_graphics_driver(); - if (ptr->hIME) - { - LOAD_FUNCPTR(ImeInquire); - if (!ptr->pImeInquire || !ptr->pImeInquire(&ptr->imeInfo, ptr->imeClassName, 0)) - { - FreeLibrary(ptr->hIME); - ptr->hIME = NULL; - } - else - { - LOAD_FUNCPTR(ImeDestroy); - LOAD_FUNCPTR(ImeSelect); - if (!ptr->pImeSelect || !ptr->pImeDestroy) - { - FreeLibrary(ptr->hIME); - ptr->hIME = NULL; - } - else - { - LOAD_FUNCPTR(ImeConfigure); - LOAD_FUNCPTR(ImeEscape); - LOAD_FUNCPTR(ImeSetActiveContext); - LOAD_FUNCPTR(ImeToAsciiEx); - LOAD_FUNCPTR(NotifyIME); - LOAD_FUNCPTR(ImeRegisterWord); - LOAD_FUNCPTR(ImeUnregisterWord); - LOAD_FUNCPTR(ImeEnumRegisterWord); - LOAD_FUNCPTR(ImeSetCompositionString); - LOAD_FUNCPTR(ImeConversionList); - LOAD_FUNCPTR(ImeProcessKey); - LOAD_FUNCPTR(ImeGetRegisterWordStyle); - LOAD_FUNCPTR(ImeGetImeMenuItems); - /* make sure our classname is WCHAR */ - if (!is_kbd_ime_unicode(ptr)) - { - WCHAR bufW[17]; - MultiByteToWideChar(CP_ACP, 0, (LPSTR)ptr->imeClassName, - -1, bufW, 17); - lstrcpyW(ptr->imeClassName, bufW); - } - } - } - } - list_add_head(&ImmHklList,&ptr->entry); + /* reference the IME the first time the input context cache is used + * in the same way Windows does it, so it doesn't get destroyed and + * INPUTCONTEXT cache lost when keyboard layout is changed + */ + if (list_empty( &ime->input_contexts )) ime->refcount++; - return ptr; + list_add_tail( &ime->input_contexts, &entry->entry ); + LeaveCriticalSection( &ime_cs ); } -#undef LOAD_FUNCPTR +static INPUTCONTEXT *ime_find_input_context( struct ime *ime, HIMC himc ) +{ + struct imc_entry *entry; + + EnterCriticalSection( &ime_cs ); + LIST_FOR_EACH_ENTRY( entry, &ime->input_contexts, struct imc_entry, entry ) + if (entry->himc == himc) break; + LeaveCriticalSection( &ime_cs ); -static void IMM_FreeAllImmHkl(void) + if (&entry->entry == &ime->input_contexts) return NULL; + return &entry->context; +} + +static void imc_release_ime( struct imc *imc, struct ime *ime ) { - ImmHkl *ptr,*cursor2; + INPUTCONTEXT *ctx; - LIST_FOR_EACH_ENTRY_SAFE(ptr, cursor2, &ImmHklList, ImmHkl, entry) - { - list_remove(&ptr->entry); - if (ptr->hIME) - { - ptr->pImeDestroy(1); - FreeLibrary(ptr->hIME); - } - if (ptr->UIWnd) - DestroyWindow(ptr->UIWnd); - HeapFree(GetProcessHeap(),0,ptr); - } + if (imc->ui_hwnd) DestroyWindow( imc->ui_hwnd ); + imc->ui_hwnd = NULL; + ime->pImeSelect( imc->handle, FALSE ); + + if ((ctx = ime_find_input_context( ime, imc->handle ))) *ctx = imc->IMC; + ime_release( ime ); } -BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpReserved) +static struct ime *imc_select_ime( struct imc *imc ) { - TRACE("%p, %lx, %p\n",hInstDLL,fdwReason,lpReserved); - switch (fdwReason) + HKL hkl = GetKeyboardLayout( 0 ); + struct ime *ime; + + if ((ime = imc->ime)) { - case DLL_PROCESS_ATTACH: - if (!User32InitializeImmEntryTable(IMM_INIT_MAGIC)) - { - return FALSE; - } - break; - case DLL_THREAD_ATTACH: - break; - case DLL_THREAD_DETACH: - IMM_FreeThreadData(); - break; - case DLL_PROCESS_DETACH: - if (lpReserved) break; - IMM_FreeThreadData(); - IMM_FreeAllImmHkl(); - break; + if (ime->hkl == hkl) return ime; + imc->ime = NULL; + imc_release_ime( imc, ime ); } - return TRUE; -} -/* for posting messages as the IME */ -static void ImmInternalPostIMEMessage(InputContextData *data, UINT msg, WPARAM wParam, LPARAM lParam) -{ - HWND target = GetFocus(); - if (!target) - PostMessageW(data->IMC.hWnd,msg,wParam,lParam); + if (!(imc->ime = ime_acquire( hkl ))) + WARN( "Failed to acquire IME for HKL %p\n", hkl ); else - PostMessageW(target, msg, wParam, lParam); + { + INPUTCONTEXT *ctx; + + if ((ctx = ime_find_input_context( imc->ime, imc->handle ))) imc->IMC = *ctx; + else ime_save_input_context( imc->ime, imc->handle, &imc->IMC ); + + imc->ime->pImeSelect( imc->handle, TRUE ); + } + + return imc->ime; } -/* for sending messages as the IME */ -static void ImmInternalSendIMEMessage(InputContextData *data, UINT msg, WPARAM wParam, LPARAM lParam) +static BOOL CALLBACK enum_activate_layout( HIMC himc, LPARAM lparam ) { - HWND target = GetFocus(); - if (!target) - SendMessageW(data->IMC.hWnd,msg,wParam,lParam); - else - SendMessageW(target, msg, wParam, lParam); + if (ImmLockIMC( himc )) ImmUnlockIMC( himc ); + return TRUE; } -static LRESULT ImmInternalSendIMENotify(InputContextData *data, WPARAM notify, LPARAM lParam) +BOOL WINAPI ImmActivateLayout( HKL hkl ) { - HWND target; + TRACE( "hkl %p\n", hkl ); - target = data->IMC.hWnd; - if (!target) target = GetFocus(); + if (hkl == GetKeyboardLayout( 0 )) return TRUE; + if (!ActivateKeyboardLayout( hkl, 0 )) return FALSE; - if (target) - return SendMessageW(target, WM_IME_NOTIFY, notify, lParam); + ImmEnumInputContext( 0, enum_activate_layout, 0 ); - return 0; + return TRUE; } -static HIMCC ImmCreateBlankCompStr(void) +static BOOL free_input_context_data( HIMC hIMC ) { - HIMCC rc; - LPCOMPOSITIONSTRING ptr; - rc = ImmCreateIMCC(sizeof(COMPOSITIONSTRING)); - ptr = ImmLockIMCC(rc); - memset(ptr,0,sizeof(COMPOSITIONSTRING)); - ptr->dwSize = sizeof(COMPOSITIONSTRING); - ImmUnlockIMCC(rc); - return rc; -} + struct imc *data = query_imc_data( hIMC ); + struct ime *ime; -static BOOL IMM_IsCrossThreadAccess(HWND hWnd, HIMC hIMC) -{ - InputContextData *data; + if (!data) return FALSE; - if (hWnd) - { - DWORD thread = GetWindowThreadProcessId(hWnd, NULL); - if (thread != GetCurrentThreadId()) return TRUE; - } - data = get_imc_data(hIMC); - if (data && data->threadID != GetCurrentThreadId()) - return TRUE; + TRACE( "Destroying %p\n", hIMC ); - return FALSE; -} + if ((ime = imc_select_ime( data ))) imc_release_ime( data, ime ); -/*********************************************************************** - * ImmSetActiveContext (IMM32.@) - */ -BOOL WINAPI ImmSetActiveContext(HWND hwnd, HIMC himc, BOOL activate) -{ - InputContextData *data = get_imc_data(himc); + ImmDestroyIMCC( data->IMC.hCompStr ); + ImmDestroyIMCC( data->IMC.hCandInfo ); + ImmDestroyIMCC( data->IMC.hGuideLine ); + ImmDestroyIMCC( data->IMC.hMsgBuf ); - TRACE("(%p, %p, %x)\n", hwnd, himc, activate); + free( data ); - if (himc && !data && activate) - return FALSE; + return TRUE; +} - imm_coinit_thread(); +static void input_context_init( INPUTCONTEXT *ctx ) +{ + COMPOSITIONSTRING *str; + CANDIDATEINFO *info; + GUIDELINE *line; + UINT i; - if (data) + if (!(ctx->hMsgBuf = ImmCreateIMCC( 0 ))) + WARN( "Failed to allocate %p message buffer\n", ctx ); + + if (!(ctx->hCompStr = ImmCreateIMCC( sizeof(COMPOSITIONSTRING) ))) + WARN( "Failed to allocate %p COMPOSITIONSTRING\n", ctx ); + else if (!(str = ImmLockIMCC( ctx->hCompStr ))) + WARN( "Failed to lock IMCC for COMPOSITIONSTRING\n" ); + else { - data->IMC.hWnd = activate ? hwnd : NULL; + str->dwSize = sizeof(COMPOSITIONSTRING); + ImmUnlockIMCC( ctx->hCompStr ); + } - if (data->immKbd->hIME && data->immKbd->pImeSetActiveContext) - data->immKbd->pImeSetActiveContext(himc, activate); + if (!(ctx->hCandInfo = ImmCreateIMCC( sizeof(CANDIDATEINFO) ))) + WARN( "Failed to allocate %p CANDIDATEINFO\n", ctx ); + else if (!(info = ImmLockIMCC( ctx->hCandInfo ))) + WARN( "Failed to lock IMCC for CANDIDATEINFO\n" ); + else + { + info->dwSize = sizeof(CANDIDATEINFO); + ImmUnlockIMCC( ctx->hCandInfo ); } - if (IsWindow(hwnd)) + if (!(ctx->hGuideLine = ImmCreateIMCC( sizeof(GUIDELINE) ))) + WARN( "Failed to allocate %p GUIDELINE\n", ctx ); + else if (!(line = ImmLockIMCC( ctx->hGuideLine ))) + WARN( "Failed to lock IMCC for GUIDELINE\n" ); + else { - SendMessageW(hwnd, WM_IME_SETCONTEXT, activate, ISC_SHOWUIALL); - /* TODO: send WM_IME_NOTIFY */ + line->dwSize = sizeof(GUIDELINE); + ImmUnlockIMCC( ctx->hGuideLine ); } - SetLastError(0); - return TRUE; + + for (i = 0; i < ARRAY_SIZE(ctx->cfCandForm); i++) + ctx->cfCandForm[i].dwIndex = ~0u; } -/*********************************************************************** - * ImmAssociateContext (IMM32.@) - */ -HIMC WINAPI ImmAssociateContext(HWND hwnd, HIMC imc) +static void IMM_FreeThreadData(void) { - HIMC old; - UINT ret; + struct coinit_spy *spy; - TRACE("(%p, %p):\n", hwnd, imc); + free_input_context_data( UlongToHandle( NtUserGetThreadInfo()->default_imc ) ); + if ((spy = get_thread_coinit_spy())) IInitializeSpy_Release( &spy->IInitializeSpy_iface ); +} - old = NtUserGetWindowInputContext(hwnd); - ret = NtUserAssociateInputContext(hwnd, imc, 0); - if (ret == AICR_FOCUS_CHANGED) +static void IMM_FreeAllImmHkl(void) +{ + struct ime *ime, *ime_next; + + LIST_FOR_EACH_ENTRY_SAFE( ime, ime_next, &ime_list, struct ime, entry ) { - ImmSetActiveContext(hwnd, old, FALSE); - ImmSetActiveContext(hwnd, imc, TRUE); + struct imc_entry *imc_entry, *imc_next; + list_remove( &ime->entry ); + + ime->pImeDestroy( 1 ); + FreeLibrary( ime->module ); + LIST_FOR_EACH_ENTRY_SAFE( imc_entry, imc_next, &ime->input_contexts, struct imc_entry, entry ) + { + ImmDestroyIMCC( imc_entry->context.hPrivate ); + free( imc_entry ); + } + + free( ime ); } - return ret == AICR_FAILED ? 0 : old; } - -/* - * Helper function for ImmAssociateContextEx - */ -static BOOL CALLBACK _ImmAssociateContextExEnumProc(HWND hwnd, LPARAM lParam) +BOOL WINAPI DllMain( HINSTANCE instance, DWORD reason, void *reserved ) { - HIMC hImc = (HIMC)lParam; - ImmAssociateContext(hwnd,hImc); + TRACE( "instance %p, reason %lx, reserved %p\n", instance, reason, reserved ); + + switch (reason) + { + case DLL_PROCESS_ATTACH: + if (!User32InitializeImmEntryTable( IMM_INIT_MAGIC )) return FALSE; + imm32_module = instance; + break; + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + IMM_FreeThreadData(); + break; + case DLL_PROCESS_DETACH: + if (reserved) break; + IMM_FreeThreadData(); + IMM_FreeAllImmHkl(); + break; + } + return TRUE; } /*********************************************************************** - * ImmAssociateContextEx (IMM32.@) + * ImmSetActiveContext (IMM32.@) */ -BOOL WINAPI ImmAssociateContextEx(HWND hwnd, HIMC imc, DWORD flags) +BOOL WINAPI ImmSetActiveContext(HWND hwnd, HIMC himc, BOOL activate) { - HIMC old; - UINT ret; + struct imc *data = get_imc_data( himc ); + struct ime *ime; - TRACE("(%p, %p, 0x%lx):\n", hwnd, imc, flags); + TRACE("(%p, %p, %x)\n", hwnd, himc, activate); - if (!hwnd) + if (himc && !data && activate) return FALSE; - if (flags == IACE_CHILDREN) + imm_coinit_thread(); + + if (data) { - EnumChildWindows(hwnd, _ImmAssociateContextExEnumProc, (LPARAM)imc); - return TRUE; + if (activate) data->IMC.hWnd = hwnd; + if ((ime = imc_select_ime( data ))) ime->pImeSetActiveContext( himc, activate ); } - old = NtUserGetWindowInputContext(hwnd); - ret = NtUserAssociateInputContext(hwnd, imc, flags); - if (ret == AICR_FOCUS_CHANGED) + if (IsWindow(hwnd)) { - ImmSetActiveContext(hwnd, old, FALSE); - ImmSetActiveContext(hwnd, imc, TRUE); + SendMessageW(hwnd, WM_IME_SETCONTEXT, activate, ISC_SHOWUIALL); + /* TODO: send WM_IME_NOTIFY */ } - return ret != AICR_FAILED; + SetLastError(0); + return TRUE; } /*********************************************************************** * ImmConfigureIMEA (IMM32.@) */ -BOOL WINAPI ImmConfigureIMEA( - HKL hKL, HWND hWnd, DWORD dwMode, LPVOID lpData) +BOOL WINAPI ImmConfigureIMEA( HKL hkl, HWND hwnd, DWORD mode, void *data ) { - ImmHkl *immHkl = IMM_GetImmHkl(hKL); + struct ime *ime; + BOOL ret; - TRACE("(%p, %p, %ld, %p):\n", hKL, hWnd, dwMode, lpData); + TRACE( "hkl %p, hwnd %p, mode %lu, data %p.\n", hkl, hwnd, mode, data ); - if (dwMode == IME_CONFIG_REGISTERWORD && !lpData) - return FALSE; + if (mode == IME_CONFIG_REGISTERWORD && !data) return FALSE; + if (!(ime = ime_acquire( hkl ))) return FALSE; - if (immHkl->hIME && immHkl->pImeConfigure) + if (mode != IME_CONFIG_REGISTERWORD || !ime_is_unicode( ime )) + ret = ime->pImeConfigure( hkl, hwnd, mode, data ); + else { - if (dwMode != IME_CONFIG_REGISTERWORD || !is_kbd_ime_unicode(immHkl)) - return immHkl->pImeConfigure(hKL,hWnd,dwMode,lpData); - else - { - REGISTERWORDW rww; - REGISTERWORDA *rwa = lpData; - BOOL rc; - - rww.lpReading = strdupAtoW(rwa->lpReading); - rww.lpWord = strdupAtoW(rwa->lpWord); - rc = immHkl->pImeConfigure(hKL,hWnd,dwMode,&rww); - HeapFree(GetProcessHeap(),0,rww.lpReading); - HeapFree(GetProcessHeap(),0,rww.lpWord); - return rc; - } + REGISTERWORDA *wordA = data; + REGISTERWORDW wordW; + wordW.lpWord = strdupAtoW( wordA->lpWord ); + wordW.lpReading = strdupAtoW( wordA->lpReading ); + ret = ime->pImeConfigure( hkl, hwnd, mode, &wordW ); + free( wordW.lpReading ); + free( wordW.lpWord ); } - else - return FALSE; + + ime_release( ime ); + return ret; } /*********************************************************************** * ImmConfigureIMEW (IMM32.@) */ -BOOL WINAPI ImmConfigureIMEW( - HKL hKL, HWND hWnd, DWORD dwMode, LPVOID lpData) +BOOL WINAPI ImmConfigureIMEW( HKL hkl, HWND hwnd, DWORD mode, void *data ) { - ImmHkl *immHkl = IMM_GetImmHkl(hKL); + struct ime *ime; + BOOL ret; - TRACE("(%p, %p, %ld, %p):\n", hKL, hWnd, dwMode, lpData); + TRACE( "hkl %p, hwnd %p, mode %lu, data %p.\n", hkl, hwnd, mode, data ); - if (dwMode == IME_CONFIG_REGISTERWORD && !lpData) - return FALSE; + if (mode == IME_CONFIG_REGISTERWORD && !data) return FALSE; + if (!(ime = ime_acquire( hkl ))) return FALSE; - if (immHkl->hIME && immHkl->pImeConfigure) - { - if (dwMode != IME_CONFIG_REGISTERWORD || is_kbd_ime_unicode(immHkl)) - return immHkl->pImeConfigure(hKL,hWnd,dwMode,lpData); - else - { - REGISTERWORDW *rww = lpData; - REGISTERWORDA rwa; - BOOL rc; - - rwa.lpReading = strdupWtoA(rww->lpReading); - rwa.lpWord = strdupWtoA(rww->lpWord); - rc = immHkl->pImeConfigure(hKL,hWnd,dwMode,&rwa); - HeapFree(GetProcessHeap(),0,rwa.lpReading); - HeapFree(GetProcessHeap(),0,rwa.lpWord); - return rc; - } - } + if (mode != IME_CONFIG_REGISTERWORD || ime_is_unicode( ime )) + ret = ime->pImeConfigure( hkl, hwnd, mode, data ); else - return FALSE; -} - -static InputContextData *create_input_context(HIMC default_imc) -{ - InputContextData *new_context; - LPGUIDELINE gl; - LPCANDIDATEINFO ci; - int i; - - new_context = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(InputContextData)); - - /* Load the IME */ - new_context->threadDefault = !!default_imc; - new_context->immKbd = IMM_GetImmHkl(GetKeyboardLayout(0)); - - if (!new_context->immKbd->hIME) { - TRACE("IME dll could not be loaded\n"); - HeapFree(GetProcessHeap(),0,new_context); - return 0; + REGISTERWORDW *wordW = data; + REGISTERWORDA wordA; + wordA.lpWord = strdupWtoA( wordW->lpWord ); + wordA.lpReading = strdupWtoA( wordW->lpReading ); + ret = ime->pImeConfigure( hkl, hwnd, mode, &wordA ); + free( wordA.lpReading ); + free( wordA.lpWord ); } - /* the HIMCCs are never NULL */ - new_context->IMC.hCompStr = ImmCreateBlankCompStr(); - new_context->IMC.hMsgBuf = ImmCreateIMCC(0); - new_context->IMC.hCandInfo = ImmCreateIMCC(sizeof(CANDIDATEINFO)); - ci = ImmLockIMCC(new_context->IMC.hCandInfo); - memset(ci,0,sizeof(CANDIDATEINFO)); - ci->dwSize = sizeof(CANDIDATEINFO); - ImmUnlockIMCC(new_context->IMC.hCandInfo); - new_context->IMC.hGuideLine = ImmCreateIMCC(sizeof(GUIDELINE)); - gl = ImmLockIMCC(new_context->IMC.hGuideLine); - memset(gl,0,sizeof(GUIDELINE)); - gl->dwSize = sizeof(GUIDELINE); - ImmUnlockIMCC(new_context->IMC.hGuideLine); - - for (i = 0; i < ARRAY_SIZE(new_context->IMC.cfCandForm); i++) - new_context->IMC.cfCandForm[i].dwIndex = ~0u; + ime_release( ime ); + return ret; +} - /* Initialize the IME Private */ - new_context->IMC.hPrivate = ImmCreateIMCC(new_context->immKbd->imeInfo.dwPrivateDataSize); +static struct imc *create_input_context( HIMC default_imc ) +{ + struct imc *new_context; - new_context->IMC.fdwConversion = new_context->immKbd->imeInfo.fdwConversionCaps; - new_context->IMC.fdwSentence = new_context->immKbd->imeInfo.fdwSentenceCaps; + if (!(new_context = calloc( 1, sizeof(*new_context) ))) return NULL; + input_context_init( &new_context->IMC ); if (!default_imc) new_context->handle = NtUserCreateInputContext((UINT_PTR)new_context); @@ -867,34 +918,54 @@ static InputContextData *create_input_context(HIMC default_imc) return 0; } - if (!new_context->immKbd->pImeSelect(new_context->handle, TRUE)) - { - TRACE("Selection of IME failed\n"); - IMM_DestroyContext(new_context); - return 0; - } - new_context->threadID = GetCurrentThreadId(); - SendMessageW(GetFocus(), WM_IME_SELECT, TRUE, (LPARAM)new_context->immKbd); - - new_context->immKbd->uSelected++; TRACE("Created context %p\n", new_context); return new_context; } -static InputContextData* get_imc_data(HIMC handle) +static struct imc *get_imc_data( HIMC handle ) { - InputContextData *ret; + struct imc *ret; if ((ret = query_imc_data(handle)) || !handle) return ret; return create_input_context(handle); } +static struct imc *default_input_context(void) +{ + UINT *himc = &NtUserGetThreadInfo()->default_imc; + if (!*himc) *himc = (UINT_PTR)NtUserCreateInputContext( 0 ); + return get_imc_data( (HIMC)(UINT_PTR)*himc ); +} + +static HWND get_ime_ui_window(void) +{ + struct imc *imc = default_input_context(); + struct ime *ime; + + if (!(ime = imc_select_ime( imc ))) return 0; + + if (!imc->ui_hwnd) + { + imc->ui_hwnd = CreateWindowExW( WS_EX_TOOLWINDOW, ime->ui_class, NULL, WS_POPUP, 0, 0, 1, 1, + ImmGetDefaultIMEWnd( 0 ), 0, ime->module, 0 ); + SetWindowLongPtrW( imc->ui_hwnd, IMMGWL_IMC, (LONG_PTR)NtUserGetWindowInputContext( GetFocus() ) ); + } + return imc->ui_hwnd; +} + +static void set_ime_ui_window_himc( HIMC himc ) +{ + HWND hwnd; + if (!(hwnd = get_ime_ui_window())) return; + SetWindowLongPtrW( hwnd, IMMGWL_IMC, (LONG_PTR)himc ); +} + /*********************************************************************** * ImmCreateContext (IMM32.@) */ HIMC WINAPI ImmCreateContext(void) { - InputContextData *new_context; + struct imc *new_context; if (!(new_context = create_input_context(0))) return 0; return new_context->handle; @@ -912,79 +983,160 @@ static BOOL IMM_DestroyContext(HIMC hIMC) */ BOOL WINAPI ImmDestroyContext(HIMC hIMC) { - if (!IMM_IsDefaultContext(hIMC) && !IMM_IsCrossThreadAccess(NULL, hIMC)) - return IMM_DestroyContext(hIMC); - else - return FALSE; + if ((UINT_PTR)hIMC == NtUserGetThreadInfo()->default_imc) return FALSE; + if (NtUserQueryInputContext( hIMC, NtUserInputContextThreadId ) != GetCurrentThreadId()) return FALSE; + return IMM_DestroyContext(hIMC); } /*********************************************************************** - * ImmEnumRegisterWordA (IMM32.@) + * ImmAssociateContext (IMM32.@) */ -UINT WINAPI ImmEnumRegisterWordA( - HKL hKL, REGISTERWORDENUMPROCA lpfnEnumProc, - LPCSTR lpszReading, DWORD dwStyle, - LPCSTR lpszRegister, LPVOID lpData) +HIMC WINAPI ImmAssociateContext( HWND hwnd, HIMC new_himc ) { - ImmHkl *immHkl = IMM_GetImmHkl(hKL); - TRACE("(%p, %p, %s, %ld, %s, %p):\n", hKL, lpfnEnumProc, - debugstr_a(lpszReading), dwStyle, debugstr_a(lpszRegister), lpData); - if (immHkl->hIME && immHkl->pImeEnumRegisterWord) + HIMC old_himc; + UINT ret; + + TRACE( "hwnd %p, new_himc %p\n", hwnd, new_himc ); + + old_himc = NtUserGetWindowInputContext( hwnd ); + ret = NtUserAssociateInputContext( hwnd, new_himc, 0 ); + if (ret == AICR_FOCUS_CHANGED) { - if (!is_kbd_ime_unicode(immHkl)) - return immHkl->pImeEnumRegisterWord((REGISTERWORDENUMPROCW)lpfnEnumProc, - (LPCWSTR)lpszReading, dwStyle, (LPCWSTR)lpszRegister, lpData); - else - { - LPWSTR lpszwReading = strdupAtoW(lpszReading); - LPWSTR lpszwRegister = strdupAtoW(lpszRegister); - BOOL rc; + ImmSetActiveContext( hwnd, old_himc, FALSE ); + ImmSetActiveContext( hwnd, new_himc, TRUE ); + if (hwnd == GetFocus()) set_ime_ui_window_himc( new_himc ); + } - rc = immHkl->pImeEnumRegisterWord((REGISTERWORDENUMPROCW)lpfnEnumProc, - lpszwReading, dwStyle, lpszwRegister, - lpData); + return ret == AICR_FAILED ? 0 : old_himc; +} - HeapFree(GetProcessHeap(),0,lpszwReading); - HeapFree(GetProcessHeap(),0,lpszwRegister); - return rc; - } +static BOOL CALLBACK enum_associate_context( HWND hwnd, LPARAM lparam ) +{ + ImmAssociateContext( hwnd, (HIMC)lparam ); + return TRUE; +} + +/*********************************************************************** + * ImmAssociateContextEx (IMM32.@) + */ +BOOL WINAPI ImmAssociateContextEx( HWND hwnd, HIMC new_himc, DWORD flags ) +{ + HIMC old_himc; + UINT ret; + + TRACE( "hwnd %p, new_himc %p, flags %#lx\n", hwnd, new_himc, flags ); + + if (!hwnd) return FALSE; + + if (flags == IACE_CHILDREN) + { + EnumChildWindows( hwnd, enum_associate_context, (LPARAM)new_himc ); + return TRUE; } + + old_himc = NtUserGetWindowInputContext( hwnd ); + ret = NtUserAssociateInputContext( hwnd, new_himc, flags ); + if (ret == AICR_FOCUS_CHANGED) + { + if (flags == IACE_DEFAULT) new_himc = NtUserGetWindowInputContext( hwnd ); + ImmSetActiveContext( hwnd, old_himc, FALSE ); + ImmSetActiveContext( hwnd, new_himc, TRUE ); + if (hwnd == GetFocus()) set_ime_ui_window_himc( new_himc ); + } + + return ret != AICR_FAILED; +} + +struct enum_register_word_params_WtoA +{ + REGISTERWORDENUMPROCA proc; + void *user; +}; + +static int CALLBACK enum_register_word_WtoA( const WCHAR *readingW, DWORD style, + const WCHAR *stringW, void *user ) +{ + char *readingA = strdupWtoA( readingW ), *stringA = strdupWtoA( stringW ); + struct enum_register_word_params_WtoA *params = user; + int ret = params->proc( readingA, style, stringA, params->user ); + free( readingA ); + free( stringA ); + return ret; +} + +/*********************************************************************** + * ImmEnumRegisterWordA (IMM32.@) + */ +UINT WINAPI ImmEnumRegisterWordA( HKL hkl, REGISTERWORDENUMPROCA procA, const char *readingA, + DWORD style, const char *stringA, void *user ) +{ + struct ime *ime; + UINT ret; + + TRACE( "hkl %p, procA %p, readingA %s, style %lu, stringA %s, user %p.\n", hkl, procA, + debugstr_a(readingA), style, debugstr_a(stringA), user ); + + if (!(ime = ime_acquire( hkl ))) return 0; + + if (!ime_is_unicode( ime )) + ret = ime->pImeEnumRegisterWord( procA, readingA, style, stringA, user ); else - return 0; + { + struct enum_register_word_params_WtoA params = {.proc = procA, .user = user}; + WCHAR *readingW = strdupAtoW( readingA ), *stringW = strdupAtoW( stringA ); + ret = ime->pImeEnumRegisterWord( enum_register_word_WtoA, readingW, style, stringW, ¶ms ); + free( readingW ); + free( stringW ); + } + + ime_release( ime ); + return ret; +} + +struct enum_register_word_params_AtoW +{ + REGISTERWORDENUMPROCW proc; + void *user; +}; + +static int CALLBACK enum_register_word_AtoW( const char *readingA, DWORD style, + const char *stringA, void *user ) +{ + WCHAR *readingW = strdupAtoW( readingA ), *stringW = strdupAtoW( stringA ); + struct enum_register_word_params_AtoW *params = user; + int ret = params->proc( readingW, style, stringW, params->user ); + free( readingW ); + free( stringW ); + return ret; } /*********************************************************************** * ImmEnumRegisterWordW (IMM32.@) */ -UINT WINAPI ImmEnumRegisterWordW( - HKL hKL, REGISTERWORDENUMPROCW lpfnEnumProc, - LPCWSTR lpszReading, DWORD dwStyle, - LPCWSTR lpszRegister, LPVOID lpData) +UINT WINAPI ImmEnumRegisterWordW( HKL hkl, REGISTERWORDENUMPROCW procW, const WCHAR *readingW, + DWORD style, const WCHAR *stringW, void *user ) { - ImmHkl *immHkl = IMM_GetImmHkl(hKL); - TRACE("(%p, %p, %s, %ld, %s, %p):\n", hKL, lpfnEnumProc, - debugstr_w(lpszReading), dwStyle, debugstr_w(lpszRegister), lpData); - if (immHkl->hIME && immHkl->pImeEnumRegisterWord) - { - if (is_kbd_ime_unicode(immHkl)) - return immHkl->pImeEnumRegisterWord(lpfnEnumProc, lpszReading, dwStyle, - lpszRegister, lpData); - else - { - LPSTR lpszaReading = strdupWtoA(lpszReading); - LPSTR lpszaRegister = strdupWtoA(lpszRegister); - BOOL rc; + struct ime *ime; + UINT ret; - rc = immHkl->pImeEnumRegisterWord(lpfnEnumProc, (LPCWSTR)lpszaReading, - dwStyle, (LPCWSTR)lpszaRegister, lpData); + TRACE( "hkl %p, procW %p, readingW %s, style %lu, stringW %s, user %p.\n", hkl, procW, + debugstr_w(readingW), style, debugstr_w(stringW), user ); - HeapFree(GetProcessHeap(),0,lpszaReading); - HeapFree(GetProcessHeap(),0,lpszaRegister); - return rc; - } - } + if (!(ime = ime_acquire( hkl ))) return 0; + + if (ime_is_unicode( ime )) + ret = ime->pImeEnumRegisterWord( procW, readingW, style, stringW, user ); else - return 0; + { + struct enum_register_word_params_AtoW params = {.proc = procW, .user = user}; + char *readingA = strdupWtoA( readingW ), *stringA = strdupWtoA( stringW ); + ret = ime->pImeEnumRegisterWord( enum_register_word_AtoW, readingA, style, stringA, ¶ms ); + free( readingA ); + free( stringA ); + } + + ime_release( ime ); + return ret; } static inline BOOL EscapeRequiresWA(UINT uEscape) @@ -1000,71 +1152,67 @@ static inline BOOL EscapeRequiresWA(UINT uEscape) /*********************************************************************** * ImmEscapeA (IMM32.@) */ -LRESULT WINAPI ImmEscapeA( - HKL hKL, HIMC hIMC, - UINT uEscape, LPVOID lpData) +LRESULT WINAPI ImmEscapeA( HKL hkl, HIMC himc, UINT code, void *data ) { - ImmHkl *immHkl = IMM_GetImmHkl(hKL); - TRACE("(%p, %p, %d, %p):\n", hKL, hIMC, uEscape, lpData); + struct ime *ime; + LRESULT ret; + + TRACE( "hkl %p, himc %p, code %u, data %p.\n", hkl, himc, code, data ); + + if (!(ime = ime_acquire( hkl ))) return 0; - if (immHkl->hIME && immHkl->pImeEscape) + if (!EscapeRequiresWA( code ) || !ime_is_unicode( ime ) || !data) + ret = ime->pImeEscape( himc, code, data ); + else { - if (!EscapeRequiresWA(uEscape) || !is_kbd_ime_unicode(immHkl)) - return immHkl->pImeEscape(hIMC,uEscape,lpData); + WCHAR buffer[81]; /* largest required buffer should be 80 */ + if (code == IME_ESC_SET_EUDC_DICTIONARY) + { + MultiByteToWideChar( CP_ACP, 0, data, -1, buffer, 81 ); + ret = ime->pImeEscape( himc, code, buffer ); + } else { - WCHAR buffer[81]; /* largest required buffer should be 80 */ - LRESULT rc; - if (uEscape == IME_ESC_SET_EUDC_DICTIONARY) - { - MultiByteToWideChar(CP_ACP,0,lpData,-1,buffer,81); - rc = immHkl->pImeEscape(hIMC,uEscape,buffer); - } - else - { - rc = immHkl->pImeEscape(hIMC,uEscape,buffer); - WideCharToMultiByte(CP_ACP,0,buffer,-1,lpData,80, NULL, NULL); - } - return rc; + ret = ime->pImeEscape( himc, code, buffer ); + WideCharToMultiByte( CP_ACP, 0, buffer, -1, data, 80, NULL, NULL ); } } - else - return 0; + + ime_release( ime ); + return ret; } /*********************************************************************** * ImmEscapeW (IMM32.@) */ -LRESULT WINAPI ImmEscapeW( - HKL hKL, HIMC hIMC, - UINT uEscape, LPVOID lpData) +LRESULT WINAPI ImmEscapeW( HKL hkl, HIMC himc, UINT code, void *data ) { - ImmHkl *immHkl = IMM_GetImmHkl(hKL); - TRACE("(%p, %p, %d, %p):\n", hKL, hIMC, uEscape, lpData); + struct ime *ime; + LRESULT ret; + + TRACE( "hkl %p, himc %p, code %u, data %p.\n", hkl, himc, code, data ); + + if (!(ime = ime_acquire( hkl ))) return 0; - if (immHkl->hIME && immHkl->pImeEscape) + if (!EscapeRequiresWA( code ) || ime_is_unicode( ime ) || !data) + ret = ime->pImeEscape( himc, code, data ); + else { - if (!EscapeRequiresWA(uEscape) || is_kbd_ime_unicode(immHkl)) - return immHkl->pImeEscape(hIMC,uEscape,lpData); + char buffer[81]; /* largest required buffer should be 80 */ + if (code == IME_ESC_SET_EUDC_DICTIONARY) + { + WideCharToMultiByte( CP_ACP, 0, data, -1, buffer, 81, NULL, NULL ); + ret = ime->pImeEscape( himc, code, buffer ); + } else { - CHAR buffer[81]; /* largest required buffer should be 80 */ - LRESULT rc; - if (uEscape == IME_ESC_SET_EUDC_DICTIONARY) - { - WideCharToMultiByte(CP_ACP,0,lpData,-1,buffer,81, NULL, NULL); - rc = immHkl->pImeEscape(hIMC,uEscape,buffer); - } - else - { - rc = immHkl->pImeEscape(hIMC,uEscape,buffer); - MultiByteToWideChar(CP_ACP,0,buffer,-1,lpData,80); - } - return rc; + ret = ime->pImeEscape( himc, code, buffer ); + MultiByteToWideChar( CP_ACP, 0, buffer, -1, data, 80 ); } } - else - return 0; + + ime_release( ime ); + return ret; } /*********************************************************************** @@ -1074,9 +1222,10 @@ DWORD WINAPI ImmGetCandidateListA( HIMC hIMC, DWORD dwIndex, LPCANDIDATELIST lpCandList, DWORD dwBufLen) { - InputContextData *data = get_imc_data(hIMC); + struct imc *data = get_imc_data( hIMC ); LPCANDIDATEINFO candinfo; LPCANDIDATELIST candlist; + struct ime *ime; DWORD ret = 0; TRACE("%p, %ld, %p, %ld\n", hIMC, dwIndex, lpCandList, dwBufLen); @@ -1092,7 +1241,9 @@ DWORD WINAPI ImmGetCandidateListA( if ( !candlist->dwSize || !candlist->dwCount ) goto done; - if ( !is_himc_ime_unicode(data) ) + if (!(ime = imc_select_ime( data ))) + ret = 0; + else if (!ime_is_unicode( ime )) { ret = candlist->dwSize; if ( lpCandList && dwBufLen >= ret ) @@ -1112,9 +1263,10 @@ DWORD WINAPI ImmGetCandidateListA( DWORD WINAPI ImmGetCandidateListCountA( HIMC hIMC, LPDWORD lpdwListCount) { - InputContextData *data = get_imc_data(hIMC); + struct imc *data = get_imc_data( hIMC ); LPCANDIDATEINFO candinfo; DWORD ret, count; + struct ime *ime; TRACE("%p, %p\n", hIMC, lpdwListCount); @@ -1125,7 +1277,9 @@ DWORD WINAPI ImmGetCandidateListCountA( *lpdwListCount = count = candinfo->dwCount; - if ( !is_himc_ime_unicode(data) ) + if (!(ime = imc_select_ime( data ))) + ret = 0; + else if (!ime_is_unicode( ime )) ret = candinfo->dwSize; else { @@ -1144,9 +1298,10 @@ DWORD WINAPI ImmGetCandidateListCountA( DWORD WINAPI ImmGetCandidateListCountW( HIMC hIMC, LPDWORD lpdwListCount) { - InputContextData *data = get_imc_data(hIMC); + struct imc *data = get_imc_data( hIMC ); LPCANDIDATEINFO candinfo; DWORD ret, count; + struct ime *ime; TRACE("%p, %p\n", hIMC, lpdwListCount); @@ -1157,7 +1312,9 @@ DWORD WINAPI ImmGetCandidateListCountW( *lpdwListCount = count = candinfo->dwCount; - if ( is_himc_ime_unicode(data) ) + if (!(ime = imc_select_ime( data ))) + ret = 0; + else if (ime_is_unicode( ime )) ret = candinfo->dwSize; else { @@ -1177,9 +1334,10 @@ DWORD WINAPI ImmGetCandidateListW( HIMC hIMC, DWORD dwIndex, LPCANDIDATELIST lpCandList, DWORD dwBufLen) { - InputContextData *data = get_imc_data(hIMC); + struct imc *data = get_imc_data( hIMC ); LPCANDIDATEINFO candinfo; LPCANDIDATELIST candlist; + struct ime *ime; DWORD ret = 0; TRACE("%p, %ld, %p, %ld\n", hIMC, dwIndex, lpCandList, dwBufLen); @@ -1195,7 +1353,9 @@ DWORD WINAPI ImmGetCandidateListW( if ( !candlist->dwSize || !candlist->dwCount ) goto done; - if ( is_himc_ime_unicode(data) ) + if (!(ime = imc_select_ime( data ))) + ret = 0; + else if (ime_is_unicode( ime )) { ret = candlist->dwSize; if ( lpCandList && dwBufLen >= ret ) @@ -1212,62 +1372,73 @@ DWORD WINAPI ImmGetCandidateListW( /*********************************************************************** * ImmGetCandidateWindow (IMM32.@) */ -BOOL WINAPI ImmGetCandidateWindow( - HIMC hIMC, DWORD dwIndex, LPCANDIDATEFORM lpCandidate) +BOOL WINAPI ImmGetCandidateWindow( HIMC himc, DWORD index, CANDIDATEFORM *candidate ) { - InputContextData *data = get_imc_data(hIMC); + INPUTCONTEXT *ctx; + BOOL ret = TRUE; - TRACE("%p, %ld, %p\n", hIMC, dwIndex, lpCandidate); - - if (!data || !lpCandidate) - return FALSE; + TRACE( "himc %p, index %lu, candidate %p\n", himc, index, candidate ); - if (dwIndex >= ARRAY_SIZE(data->IMC.cfCandForm)) - return FALSE; - - if (data->IMC.cfCandForm[dwIndex].dwIndex != dwIndex) - return FALSE; + if (!candidate) return FALSE; - *lpCandidate = data->IMC.cfCandForm[dwIndex]; + if (!(ctx = ImmLockIMC( himc ))) return FALSE; + if (ctx->cfCandForm[index].dwIndex == -1) ret = FALSE; + else *candidate = ctx->cfCandForm[index]; + ImmUnlockIMC( himc ); - return TRUE; + return ret; } /*********************************************************************** * ImmGetCompositionFontA (IMM32.@) */ -BOOL WINAPI ImmGetCompositionFontA(HIMC hIMC, LPLOGFONTA lplf) +BOOL WINAPI ImmGetCompositionFontA( HIMC himc, LOGFONTA *fontA ) { - LOGFONTW lfW; - BOOL rc; + INPUTCONTEXT *ctx; + LOGFONTW fontW; + BOOL ret = TRUE; - TRACE("(%p, %p):\n", hIMC, lplf); + TRACE( "himc %p, fontA %p\n", himc, fontA ); - rc = ImmGetCompositionFontW(hIMC,&lfW); - if (!rc || !lplf) - return FALSE; + if (!fontA) return FALSE; - memcpy(lplf,&lfW,sizeof(LOGFONTA)); - WideCharToMultiByte(CP_ACP, 0, lfW.lfFaceName, -1, lplf->lfFaceName, - LF_FACESIZE, NULL, NULL); - return TRUE; + if (!(ctx = ImmLockIMC( himc ))) return FALSE; + if (!(ctx->fdwInit & INIT_LOGFONT)) ret = FALSE; + else if (!input_context_is_unicode( ctx )) *fontA = ctx->lfFont.A; + else if ((ret = ImmGetCompositionFontW( himc, &fontW ))) + { + memcpy( fontA, &fontW, offsetof(LOGFONTA, lfFaceName) ); + WideCharToMultiByte( CP_ACP, 0, fontW.lfFaceName, -1, fontA->lfFaceName, LF_FACESIZE, NULL, NULL ); + } + ImmUnlockIMC( himc ); + + return ret; } /*********************************************************************** * ImmGetCompositionFontW (IMM32.@) */ -BOOL WINAPI ImmGetCompositionFontW(HIMC hIMC, LPLOGFONTW lplf) +BOOL WINAPI ImmGetCompositionFontW( HIMC himc, LOGFONTW *fontW ) { - InputContextData *data = get_imc_data(hIMC); + INPUTCONTEXT *ctx; + LOGFONTA fontA; + BOOL ret = TRUE; - TRACE("(%p, %p):\n", hIMC, lplf); + TRACE( "himc %p, fontW %p\n", himc, fontW ); - if (!data || !lplf) - return FALSE; + if (!fontW) return FALSE; - *lplf = data->IMC.lfFont.W; + if (!(ctx = ImmLockIMC( himc ))) return FALSE; + if (!(ctx->fdwInit & INIT_LOGFONT)) ret = FALSE; + else if (input_context_is_unicode( ctx )) *fontW = ctx->lfFont.W; + else if ((ret = ImmGetCompositionFontA( himc, &fontA ))) + { + memcpy( fontW, &fontA, offsetof(LOGFONTW, lfFaceName) ); + MultiByteToWideChar( CP_ACP, 0, fontA.lfFaceName, -1, fontW->lfFaceName, LF_FACESIZE ); + } + ImmUnlockIMC( himc ); - return TRUE; + return ret; } @@ -1275,15 +1446,15 @@ BOOL WINAPI ImmGetCompositionFontW(HIMC hIMC, LPLOGFONTW lplf) /* Source encoding is defined by context, source length is always given in respective characters. Destination buffer length is always in bytes. */ -static INT CopyCompStringIMEtoClient(const InputContextData *data, const void *src, INT src_len, void *dst, - INT dst_len, BOOL unicode) +static INT CopyCompStringIMEtoClient( BOOL src_unicode, const void *src, INT src_len, + void *dst, INT dst_len, BOOL dst_unicode ) { - int char_size = unicode ? sizeof(WCHAR) : sizeof(char); + int char_size = dst_unicode ? sizeof(WCHAR) : sizeof(char); INT ret; - if (is_himc_ime_unicode(data) ^ unicode) + if (src_unicode ^ dst_unicode) { - if (unicode) + if (dst_unicode) ret = MultiByteToWideChar(CP_ACP, 0, src, src_len, dst, dst_len / sizeof(WCHAR)); else ret = WideCharToMultiByte(CP_ACP, 0, src, src_len, dst, dst_len, NULL, NULL); @@ -1305,8 +1476,8 @@ static INT CopyCompStringIMEtoClient(const InputContextData *data, const void *s /* Composition string encoding is defined by context, returned attributes correspond to string, converted according to passed mode. String length is in characters, attributes are in byte arrays. */ -static INT CopyCompAttrIMEtoClient(const InputContextData *data, const BYTE *src, INT src_len, const void *comp_string, - INT str_len, BYTE *dst, INT dst_len, BOOL unicode) +static INT CopyCompAttrIMEtoClient( BOOL src_unicode, const BYTE *src, INT src_len, const void *comp_string, INT str_len, + BYTE *dst, INT dst_len, BOOL unicode ) { union { @@ -1318,7 +1489,7 @@ static INT CopyCompAttrIMEtoClient(const InputContextData *data, const BYTE *src string.str = comp_string; - if (is_himc_ime_unicode(data) && !unicode) + if (src_unicode && !unicode) { rc = WideCharToMultiByte(CP_ACP, 0, string.strW, str_len, NULL, 0, NULL, NULL); if (dst_len) @@ -1345,7 +1516,7 @@ static INT CopyCompAttrIMEtoClient(const InputContextData *data, const BYTE *src rc = j; } } - else if (!is_himc_ime_unicode(data) && unicode) + else if (!src_unicode && unicode) { rc = MultiByteToWideChar(CP_ACP, 0, string.strA, str_len, NULL, 0); if (dst_len) @@ -1376,12 +1547,12 @@ static INT CopyCompAttrIMEtoClient(const InputContextData *data, const BYTE *src return rc; } -static INT CopyCompClauseIMEtoClient(InputContextData *data, LPBYTE source, INT slen, LPBYTE ssource, - LPBYTE target, INT tlen, BOOL unicode ) +static INT CopyCompClauseIMEtoClient( BOOL src_unicode, LPBYTE source, INT slen, LPBYTE ssource, + LPBYTE target, INT tlen, BOOL unicode ) { INT rc; - if (is_himc_ime_unicode(data) && !unicode) + if (src_unicode && !unicode) { if (tlen) { @@ -1402,7 +1573,7 @@ static INT CopyCompClauseIMEtoClient(InputContextData *data, LPBYTE source, INT else rc = slen; } - else if (!is_himc_ime_unicode(data) && unicode) + else if (!src_unicode && unicode) { if (tlen) { @@ -1431,15 +1602,15 @@ static INT CopyCompClauseIMEtoClient(InputContextData *data, LPBYTE source, INT return rc; } -static INT CopyCompOffsetIMEtoClient(InputContextData *data, DWORD offset, LPBYTE ssource, BOOL unicode) +static INT CopyCompOffsetIMEtoClient( BOOL src_unicode, DWORD offset, LPBYTE ssource, BOOL unicode ) { int rc; - if (is_himc_ime_unicode(data) && !unicode) + if (src_unicode && !unicode) { rc = WideCharToMultiByte(CP_ACP, 0, (LPWSTR)ssource, offset, NULL, 0, NULL, NULL); } - else if (!is_himc_ime_unicode(data) && unicode) + else if (!src_unicode && unicode) { rc = MultiByteToWideChar(CP_ACP, 0, (LPSTR)ssource, offset, NULL, 0); } @@ -1453,8 +1624,10 @@ static LONG ImmGetCompositionStringT( HIMC hIMC, DWORD dwIndex, LPVOID lpBuf, DWORD dwBufLen, BOOL unicode) { LONG rc = 0; - InputContextData *data = get_imc_data(hIMC); + struct imc *data = get_imc_data( hIMC ); LPCOMPOSITIONSTRING compstr; + BOOL src_unicode; + struct ime *ime; LPBYTE compdata; TRACE("(%p, 0x%lx, %p, %ld)\n", hIMC, dwIndex, lpBuf, dwBufLen); @@ -1465,6 +1638,10 @@ static LONG ImmGetCompositionStringT( HIMC hIMC, DWORD dwIndex, LPVOID lpBuf, if (!data->IMC.hCompStr) return FALSE; + if (!(ime = imc_select_ime( data ))) + return FALSE; + src_unicode = ime_is_unicode( ime ); + compdata = ImmLockIMCC(data->IMC.hCompStr); compstr = (LPCOMPOSITIONSTRING)compdata; @@ -1472,63 +1649,63 @@ static LONG ImmGetCompositionStringT( HIMC hIMC, DWORD dwIndex, LPVOID lpBuf, { case GCS_RESULTSTR: TRACE("GCS_RESULTSTR\n"); - rc = CopyCompStringIMEtoClient(data, compdata + compstr->dwResultStrOffset, compstr->dwResultStrLen, lpBuf, dwBufLen, unicode); + rc = CopyCompStringIMEtoClient(src_unicode, compdata + compstr->dwResultStrOffset, compstr->dwResultStrLen, lpBuf, dwBufLen, unicode); break; case GCS_COMPSTR: TRACE("GCS_COMPSTR\n"); - rc = CopyCompStringIMEtoClient(data, compdata + compstr->dwCompStrOffset, compstr->dwCompStrLen, lpBuf, dwBufLen, unicode); + rc = CopyCompStringIMEtoClient(src_unicode, compdata + compstr->dwCompStrOffset, compstr->dwCompStrLen, lpBuf, dwBufLen, unicode); break; case GCS_COMPATTR: TRACE("GCS_COMPATTR\n"); - rc = CopyCompAttrIMEtoClient(data, compdata + compstr->dwCompAttrOffset, compstr->dwCompAttrLen, + rc = CopyCompAttrIMEtoClient(src_unicode, compdata + compstr->dwCompAttrOffset, compstr->dwCompAttrLen, compdata + compstr->dwCompStrOffset, compstr->dwCompStrLen, lpBuf, dwBufLen, unicode); break; case GCS_COMPCLAUSE: TRACE("GCS_COMPCLAUSE\n"); - rc = CopyCompClauseIMEtoClient(data, compdata + compstr->dwCompClauseOffset,compstr->dwCompClauseLen, + rc = CopyCompClauseIMEtoClient(src_unicode, compdata + compstr->dwCompClauseOffset,compstr->dwCompClauseLen, compdata + compstr->dwCompStrOffset, lpBuf, dwBufLen, unicode); break; case GCS_RESULTCLAUSE: TRACE("GCS_RESULTCLAUSE\n"); - rc = CopyCompClauseIMEtoClient(data, compdata + compstr->dwResultClauseOffset,compstr->dwResultClauseLen, + rc = CopyCompClauseIMEtoClient(src_unicode, compdata + compstr->dwResultClauseOffset,compstr->dwResultClauseLen, compdata + compstr->dwResultStrOffset, lpBuf, dwBufLen, unicode); break; case GCS_RESULTREADSTR: TRACE("GCS_RESULTREADSTR\n"); - rc = CopyCompStringIMEtoClient(data, compdata + compstr->dwResultReadStrOffset, compstr->dwResultReadStrLen, lpBuf, dwBufLen, unicode); + rc = CopyCompStringIMEtoClient(src_unicode, compdata + compstr->dwResultReadStrOffset, compstr->dwResultReadStrLen, lpBuf, dwBufLen, unicode); break; case GCS_RESULTREADCLAUSE: TRACE("GCS_RESULTREADCLAUSE\n"); - rc = CopyCompClauseIMEtoClient(data, compdata + compstr->dwResultReadClauseOffset,compstr->dwResultReadClauseLen, + rc = CopyCompClauseIMEtoClient(src_unicode, compdata + compstr->dwResultReadClauseOffset,compstr->dwResultReadClauseLen, compdata + compstr->dwResultStrOffset, lpBuf, dwBufLen, unicode); break; case GCS_COMPREADSTR: TRACE("GCS_COMPREADSTR\n"); - rc = CopyCompStringIMEtoClient(data, compdata + compstr->dwCompReadStrOffset, compstr->dwCompReadStrLen, lpBuf, dwBufLen, unicode); + rc = CopyCompStringIMEtoClient(src_unicode, compdata + compstr->dwCompReadStrOffset, compstr->dwCompReadStrLen, lpBuf, dwBufLen, unicode); break; case GCS_COMPREADATTR: TRACE("GCS_COMPREADATTR\n"); - rc = CopyCompAttrIMEtoClient(data, compdata + compstr->dwCompReadAttrOffset, compstr->dwCompReadAttrLen, + rc = CopyCompAttrIMEtoClient(src_unicode, compdata + compstr->dwCompReadAttrOffset, compstr->dwCompReadAttrLen, compdata + compstr->dwCompReadStrOffset, compstr->dwCompReadStrLen, lpBuf, dwBufLen, unicode); break; case GCS_COMPREADCLAUSE: TRACE("GCS_COMPREADCLAUSE\n"); - rc = CopyCompClauseIMEtoClient(data, compdata + compstr->dwCompReadClauseOffset,compstr->dwCompReadClauseLen, + rc = CopyCompClauseIMEtoClient(src_unicode, compdata + compstr->dwCompReadClauseOffset,compstr->dwCompReadClauseLen, compdata + compstr->dwCompStrOffset, lpBuf, dwBufLen, unicode); break; case GCS_CURSORPOS: TRACE("GCS_CURSORPOS\n"); - rc = CopyCompOffsetIMEtoClient(data, compstr->dwCursorPos, compdata + compstr->dwCompStrOffset, unicode); + rc = CopyCompOffsetIMEtoClient(src_unicode, compstr->dwCursorPos, compdata + compstr->dwCompStrOffset, unicode); break; case GCS_DELTASTART: TRACE("GCS_DELTASTART\n"); - rc = CopyCompOffsetIMEtoClient(data, compstr->dwDeltaStart, compdata + compstr->dwCompStrOffset, unicode); + rc = CopyCompOffsetIMEtoClient(src_unicode, compstr->dwDeltaStart, compdata + compstr->dwCompStrOffset, unicode); break; default: FIXME("Unhandled index 0x%lx\n",dwIndex); @@ -1563,136 +1740,115 @@ LONG WINAPI ImmGetCompositionStringW( /*********************************************************************** * ImmGetCompositionWindow (IMM32.@) */ -BOOL WINAPI ImmGetCompositionWindow(HIMC hIMC, LPCOMPOSITIONFORM lpCompForm) +BOOL WINAPI ImmGetCompositionWindow( HIMC himc, COMPOSITIONFORM *composition ) { - InputContextData *data = get_imc_data(hIMC); + INPUTCONTEXT *ctx; + BOOL ret; - TRACE("(%p, %p)\n", hIMC, lpCompForm); + TRACE( "himc %p, composition %p\n", himc, composition ); - if (!data) - return FALSE; + if (!(ctx = ImmLockIMC( himc ))) return FALSE; + if ((ret = !!(ctx->fdwInit & INIT_COMPFORM))) *composition = ctx->cfCompForm; + ImmUnlockIMC( himc ); - *lpCompForm = data->IMC.cfCompForm; - return TRUE; + return ret; } /*********************************************************************** * ImmGetContext (IMM32.@) * */ -HIMC WINAPI ImmGetContext(HWND hWnd) +HIMC WINAPI ImmGetContext( HWND hwnd ) { - HIMC rc; - - TRACE("%p\n", hWnd); - - rc = NtUserGetWindowInputContext(hWnd); - - if (rc) - { - InputContextData *data = get_imc_data(rc); - if (data) data->IMC.hWnd = hWnd; - else rc = 0; - } - - TRACE("returning %p\n", rc); - - return rc; + TRACE( "hwnd %p\n", hwnd ); + return NtUserGetWindowInputContext( hwnd ); } /*********************************************************************** * ImmGetConversionListA (IMM32.@) */ -DWORD WINAPI ImmGetConversionListA( - HKL hKL, HIMC hIMC, - LPCSTR pSrc, LPCANDIDATELIST lpDst, - DWORD dwBufLen, UINT uFlag) +DWORD WINAPI ImmGetConversionListA( HKL hkl, HIMC himc, const char *srcA, CANDIDATELIST *listA, + DWORD lengthA, UINT flags ) { - ImmHkl *immHkl = IMM_GetImmHkl(hKL); - TRACE("(%p, %p, %s, %p, %ld, %d):\n", hKL, hIMC, debugstr_a(pSrc), lpDst, - dwBufLen, uFlag); - if (immHkl->hIME && immHkl->pImeConversionList) + struct ime *ime; + DWORD ret; + + TRACE( "hkl %p, himc %p, srcA %s, listA %p, lengthA %lu, flags %#x.\n", hkl, himc, + debugstr_a(srcA), listA, lengthA, flags ); + + if (!(ime = ime_acquire( hkl ))) return 0; + + if (!ime_is_unicode( ime )) + ret = ime->pImeConversionList( himc, srcA, listA, lengthA, flags ); + else { - if (!is_kbd_ime_unicode(immHkl)) - return immHkl->pImeConversionList(hIMC,(LPCWSTR)pSrc,lpDst,dwBufLen,uFlag); + CANDIDATELIST *listW; + WCHAR *srcW = strdupAtoW( srcA ); + DWORD lengthW = ime->pImeConversionList( himc, srcW, NULL, 0, flags ); + + if (!(listW = malloc( lengthW ))) ret = 0; else { - LPCANDIDATELIST lpwDst; - DWORD ret = 0, len; - LPWSTR pwSrc = strdupAtoW(pSrc); - - len = immHkl->pImeConversionList(hIMC, pwSrc, NULL, 0, uFlag); - lpwDst = HeapAlloc(GetProcessHeap(), 0, len); - if ( lpwDst ) - { - immHkl->pImeConversionList(hIMC, pwSrc, lpwDst, len, uFlag); - ret = convert_candidatelist_WtoA( lpwDst, lpDst, dwBufLen); - HeapFree(GetProcessHeap(), 0, lpwDst); - } - HeapFree(GetProcessHeap(), 0, pwSrc); - - return ret; + ime->pImeConversionList( himc, srcW, listW, lengthW, flags ); + ret = convert_candidatelist_WtoA( listW, listA, lengthA ); + free( listW ); } + free( srcW ); } - else - return 0; + + ime_release( ime ); + return ret; } /*********************************************************************** * ImmGetConversionListW (IMM32.@) */ -DWORD WINAPI ImmGetConversionListW( - HKL hKL, HIMC hIMC, - LPCWSTR pSrc, LPCANDIDATELIST lpDst, - DWORD dwBufLen, UINT uFlag) +DWORD WINAPI ImmGetConversionListW( HKL hkl, HIMC himc, const WCHAR *srcW, CANDIDATELIST *listW, + DWORD lengthW, UINT flags ) { - ImmHkl *immHkl = IMM_GetImmHkl(hKL); - TRACE("(%p, %p, %s, %p, %ld, %d):\n", hKL, hIMC, debugstr_w(pSrc), lpDst, - dwBufLen, uFlag); - if (immHkl->hIME && immHkl->pImeConversionList) + struct ime *ime; + DWORD ret; + + TRACE( "hkl %p, himc %p, srcW %s, listW %p, lengthW %lu, flags %#x.\n", hkl, himc, + debugstr_w(srcW), listW, lengthW, flags ); + + if (!(ime = ime_acquire( hkl ))) return 0; + + if (ime_is_unicode( ime )) + ret = ime->pImeConversionList( himc, srcW, listW, lengthW, flags ); + else { - if (is_kbd_ime_unicode(immHkl)) - return immHkl->pImeConversionList(hIMC,pSrc,lpDst,dwBufLen,uFlag); + CANDIDATELIST *listA; + char *srcA = strdupWtoA( srcW ); + DWORD lengthA = ime->pImeConversionList( himc, srcA, NULL, 0, flags ); + + if (!(listA = malloc( lengthA ))) ret = 0; else { - LPCANDIDATELIST lpaDst; - DWORD ret = 0, len; - LPSTR paSrc = strdupWtoA(pSrc); - - len = immHkl->pImeConversionList(hIMC, (LPCWSTR)paSrc, NULL, 0, uFlag); - lpaDst = HeapAlloc(GetProcessHeap(), 0, len); - if ( lpaDst ) - { - immHkl->pImeConversionList(hIMC, (LPCWSTR)paSrc, lpaDst, len, uFlag); - ret = convert_candidatelist_AtoW( lpaDst, lpDst, dwBufLen); - HeapFree(GetProcessHeap(), 0, lpaDst); - } - HeapFree(GetProcessHeap(), 0, paSrc); - - return ret; + ime->pImeConversionList( himc, srcA, listA, lengthA, flags ); + ret = convert_candidatelist_AtoW( listA, listW, lengthW ); + free( listA ); } + free( srcA ); } - else - return 0; + + ime_release( ime ); + return ret; } /*********************************************************************** * ImmGetConversionStatus (IMM32.@) */ -BOOL WINAPI ImmGetConversionStatus( - HIMC hIMC, LPDWORD lpfdwConversion, LPDWORD lpfdwSentence) +BOOL WINAPI ImmGetConversionStatus( HIMC himc, DWORD *conversion, DWORD *sentence ) { - InputContextData *data = get_imc_data(hIMC); + INPUTCONTEXT *ctx; - TRACE("%p %p %p\n", hIMC, lpfdwConversion, lpfdwSentence); + TRACE( "himc %p, conversion %p, sentence %p\n", himc, conversion, sentence ); - if (!data) - return FALSE; - - if (lpfdwConversion) - *lpfdwConversion = data->IMC.fdwConversion; - if (lpfdwSentence) - *lpfdwSentence = data->IMC.fdwSentence; + if (!(ctx = ImmLockIMC( himc ))) return FALSE; + if (conversion) *conversion = ctx->fdwConversion; + if (sentence) *sentence = ctx->fdwSentence; + ImmUnlockIMC( himc ); return TRUE; } @@ -1706,52 +1862,49 @@ HWND WINAPI ImmGetDefaultIMEWnd(HWND hWnd) } /*********************************************************************** - * ImmGetDescriptionA (IMM32.@) + * ImmGetDescriptionA (IMM32.@) */ -UINT WINAPI ImmGetDescriptionA( - HKL hKL, LPSTR lpszDescription, UINT uBufLen) +UINT WINAPI ImmGetDescriptionA( HKL hkl, LPSTR bufferA, UINT lengthA ) { - WCHAR *buf; - DWORD len; + WCHAR *bufferW; + DWORD lengthW; - TRACE("%p %p %d\n", hKL, lpszDescription, uBufLen); + TRACE( "hkl %p, bufferA %p, lengthA %d\n", hkl, bufferA, lengthA ); - /* find out how many characters in the unicode buffer */ - len = ImmGetDescriptionW( hKL, NULL, 0 ); - if (!len) - return 0; + if (!(lengthW = ImmGetDescriptionW( hkl, NULL, 0 ))) return 0; + if (!(bufferW = malloc( (lengthW + 1) * sizeof(WCHAR) ))) return 0; + lengthW = ImmGetDescriptionW( hkl, bufferW, lengthW + 1 ); + lengthA = WideCharToMultiByte( CP_ACP, 0, bufferW, lengthW, bufferA, + bufferA ? lengthA : 0, NULL, NULL ); + if (bufferA) bufferA[lengthA] = 0; + free( bufferW ); - /* allocate a buffer of that size */ - buf = HeapAlloc( GetProcessHeap(), 0, (len + 1) * sizeof (WCHAR) ); - if( !buf ) - return 0; - - /* fetch the unicode buffer */ - len = ImmGetDescriptionW( hKL, buf, len + 1 ); - - /* convert it back to ANSI */ - len = WideCharToMultiByte( CP_ACP, 0, buf, len + 1, - lpszDescription, uBufLen, NULL, NULL ); - - HeapFree( GetProcessHeap(), 0, buf ); - - if (len == 0) - return 0; - - return len - 1; + return lengthA; } /*********************************************************************** * ImmGetDescriptionW (IMM32.@) */ -UINT WINAPI ImmGetDescriptionW(HKL hKL, LPWSTR lpszDescription, UINT uBufLen) +UINT WINAPI ImmGetDescriptionW( HKL hkl, WCHAR *buffer, UINT length ) { - FIXME("(%p, %p, %d): semi stub\n", hKL, lpszDescription, uBufLen); + WCHAR path[MAX_PATH]; + HKEY hkey = 0; + DWORD size; + + TRACE( "hkl %p, buffer %p, length %u\n", hkl, buffer, length ); - if (!hKL) return 0; - if (!uBufLen) return lstrlenW(L"Wine XIM" ); - lstrcpynW( lpszDescription, L"Wine XIM", uBufLen ); - return lstrlenW( lpszDescription ); + swprintf( path, ARRAY_SIZE(path), layouts_formatW, (ULONG)(ULONG_PTR)hkl ); + if (RegOpenKeyW( HKEY_LOCAL_MACHINE, path, &hkey )) return 0; + + size = ARRAY_SIZE(path) * sizeof(WCHAR); + if (RegGetValueW( hkey, NULL, L"Layout Text", RRF_RT_REG_SZ, NULL, path, &size )) *path = 0; + RegCloseKey( hkey ); + + size = wcslen( path ); + if (!buffer) return size; + + lstrcpynW( buffer, path, length ); + return wcslen( buffer ); } /*********************************************************************** @@ -1780,284 +1933,247 @@ DWORD WINAPI ImmGetGuideLineW(HIMC hIMC, DWORD dwIndex, LPWSTR lpBuf, DWORD dwBu } /*********************************************************************** - * ImmGetIMEFileNameA (IMM32.@) + * ImmGetIMEFileNameA (IMM32.@) */ -UINT WINAPI ImmGetIMEFileNameA( HKL hKL, LPSTR lpszFileName, UINT uBufLen) +UINT WINAPI ImmGetIMEFileNameA( HKL hkl, char *bufferA, UINT lengthA ) { - LPWSTR bufW = NULL; - UINT wBufLen = uBufLen; - UINT rc; + WCHAR *bufferW; + DWORD lengthW; - if (uBufLen && lpszFileName) - bufW = HeapAlloc(GetProcessHeap(),0,uBufLen * sizeof(WCHAR)); - else /* We need this to get the number of byte required */ - { - bufW = HeapAlloc(GetProcessHeap(),0,MAX_PATH * sizeof(WCHAR)); - wBufLen = MAX_PATH; - } - - rc = ImmGetIMEFileNameW(hKL,bufW,wBufLen); + TRACE( "hkl %p, bufferA %p, lengthA %d\n", hkl, bufferA, lengthA ); - if (rc > 0) - { - if (uBufLen && lpszFileName) - rc = WideCharToMultiByte(CP_ACP, 0, bufW, -1, lpszFileName, - uBufLen, NULL, NULL); - else /* get the length */ - rc = WideCharToMultiByte(CP_ACP, 0, bufW, -1, NULL, 0, NULL, - NULL); - } + if (!(lengthW = ImmGetIMEFileNameW( hkl, NULL, 0 ))) return 0; + if (!(bufferW = malloc( (lengthW + 1) * sizeof(WCHAR) ))) return 0; + lengthW = ImmGetIMEFileNameW( hkl, bufferW, lengthW + 1 ); + lengthA = WideCharToMultiByte( CP_ACP, 0, bufferW, lengthW, bufferA, + bufferA ? lengthA : 0, NULL, NULL ); + if (bufferA) bufferA[lengthA] = 0; + free( bufferW ); - HeapFree(GetProcessHeap(),0,bufW); - return rc; + return lengthA; } /*********************************************************************** * ImmGetIMEFileNameW (IMM32.@) */ -UINT WINAPI ImmGetIMEFileNameW(HKL hKL, LPWSTR lpszFileName, UINT uBufLen) +UINT WINAPI ImmGetIMEFileNameW( HKL hkl, WCHAR *buffer, UINT length ) { - HKEY hkey; - DWORD length; - DWORD rc; - WCHAR regKey[ARRAY_SIZE(szImeRegFmt)+8]; + WCHAR path[MAX_PATH]; + HKEY hkey = 0; + DWORD size; - wsprintfW( regKey, szImeRegFmt, (ULONG_PTR)hKL ); - rc = RegOpenKeyW( HKEY_LOCAL_MACHINE, regKey, &hkey); - if (rc != ERROR_SUCCESS) - { - SetLastError(rc); - return 0; - } + TRACE( "hkl %p, buffer %p, length %u\n", hkl, buffer, length ); - length = 0; - rc = RegGetValueW(hkey, NULL, L"Ime File", RRF_RT_REG_SZ, NULL, NULL, &length); + swprintf( path, ARRAY_SIZE(path), layouts_formatW, (ULONG)(ULONG_PTR)hkl ); + if (RegOpenKeyW( HKEY_LOCAL_MACHINE, path, &hkey )) return 0; - if (rc != ERROR_SUCCESS) - { - RegCloseKey(hkey); - SetLastError(rc); - return 0; - } - if (length > uBufLen * sizeof(WCHAR) || !lpszFileName) - { - RegCloseKey(hkey); - if (lpszFileName) - { - SetLastError(ERROR_INSUFFICIENT_BUFFER); - return 0; - } - else - return length / sizeof(WCHAR); - } - - RegGetValueW(hkey, NULL, L"Ime File", RRF_RT_REG_SZ, NULL, lpszFileName, &length); + size = ARRAY_SIZE(path) * sizeof(WCHAR); + if (RegGetValueW( hkey, NULL, L"Ime File", RRF_RT_REG_SZ, NULL, path, &size )) *path = 0; + RegCloseKey( hkey ); - RegCloseKey(hkey); + size = wcslen( path ); + if (!buffer) return size; - return length / sizeof(WCHAR); + lstrcpynW( buffer, path, length ); + return wcslen( buffer ); } /*********************************************************************** * ImmGetOpenStatus (IMM32.@) */ -BOOL WINAPI ImmGetOpenStatus(HIMC hIMC) +BOOL WINAPI ImmGetOpenStatus( HIMC himc ) { - InputContextData *data = get_imc_data(hIMC); - static int i; - - if (!data) - return FALSE; + INPUTCONTEXT *ctx; + BOOL status; - TRACE("(%p): semi-stub\n", hIMC); + TRACE( "himc %p\n", himc ); - if (!i++) - FIXME("(%p): semi-stub\n", hIMC); + if (!(ctx = ImmLockIMC( himc ))) return FALSE; + status = ctx->fOpen; + ImmUnlockIMC( himc ); - return data->IMC.fOpen; + return status; } /*********************************************************************** * ImmGetProperty (IMM32.@) */ -DWORD WINAPI ImmGetProperty(HKL hKL, DWORD fdwIndex) +DWORD WINAPI ImmGetProperty( HKL hkl, DWORD index ) { - DWORD rc = 0; - ImmHkl *kbd; + struct ime *ime; + DWORD ret; - TRACE("(%p, %ld)\n", hKL, fdwIndex); - kbd = IMM_GetImmHkl(hKL); + TRACE( "hkl %p, index %lu.\n", hkl, index ); - if (kbd && kbd->hIME) + if (!(ime = ime_acquire( hkl ))) return 0; + + switch (index) { - switch (fdwIndex) - { - case IGP_PROPERTY: rc = kbd->imeInfo.fdwProperty; break; - case IGP_CONVERSION: rc = kbd->imeInfo.fdwConversionCaps; break; - case IGP_SENTENCE: rc = kbd->imeInfo.fdwSentenceCaps; break; - case IGP_SETCOMPSTR: rc = kbd->imeInfo.fdwSCSCaps; break; - case IGP_SELECT: rc = kbd->imeInfo.fdwSelectCaps; break; - case IGP_GETIMEVERSION: rc = IMEVER_0400; break; - case IGP_UI: rc = 0; break; - default: rc = 0; - } + case IGP_PROPERTY: ret = ime->info.fdwProperty; break; + case IGP_CONVERSION: ret = ime->info.fdwConversionCaps; break; + case IGP_SENTENCE: ret = ime->info.fdwSentenceCaps; break; + case IGP_SETCOMPSTR: ret = ime->info.fdwSCSCaps; break; + case IGP_SELECT: ret = ime->info.fdwSelectCaps; break; + case IGP_GETIMEVERSION: ret = IMEVER_0400; break; + case IGP_UI: ret = 0; break; + default: ret = 0; break; } - return rc; + + ime_release( ime ); + return ret; } /*********************************************************************** * ImmGetRegisterWordStyleA (IMM32.@) */ -UINT WINAPI ImmGetRegisterWordStyleA( - HKL hKL, UINT nItem, LPSTYLEBUFA lpStyleBuf) +UINT WINAPI ImmGetRegisterWordStyleA( HKL hkl, UINT count, STYLEBUFA *styleA ) { - ImmHkl *immHkl = IMM_GetImmHkl(hKL); - TRACE("(%p, %d, %p):\n", hKL, nItem, lpStyleBuf); - if (immHkl->hIME && immHkl->pImeGetRegisterWordStyle) - { - if (!is_kbd_ime_unicode(immHkl)) - return immHkl->pImeGetRegisterWordStyle(nItem,(LPSTYLEBUFW)lpStyleBuf); - else - { - STYLEBUFW sbw; - UINT rc; - - rc = immHkl->pImeGetRegisterWordStyle(nItem,&sbw); - WideCharToMultiByte(CP_ACP, 0, sbw.szDescription, -1, - lpStyleBuf->szDescription, 32, NULL, NULL); - lpStyleBuf->dwStyle = sbw.dwStyle; - return rc; - } - } + struct ime *ime; + UINT ret; + + TRACE( "hkl %p, count %u, styleA %p.\n", hkl, count, styleA ); + + if (!(ime = ime_acquire( hkl ))) return 0; + + if (!ime_is_unicode( ime )) + ret = ime->pImeGetRegisterWordStyle( count, styleA ); else - return 0; + { + STYLEBUFW styleW; + ret = ime->pImeGetRegisterWordStyle( count, &styleW ); + WideCharToMultiByte( CP_ACP, 0, styleW.szDescription, -1, styleA->szDescription, 32, NULL, NULL ); + styleA->dwStyle = styleW.dwStyle; + } + + ime_release( ime ); + return ret; } /*********************************************************************** * ImmGetRegisterWordStyleW (IMM32.@) */ -UINT WINAPI ImmGetRegisterWordStyleW( - HKL hKL, UINT nItem, LPSTYLEBUFW lpStyleBuf) +UINT WINAPI ImmGetRegisterWordStyleW( HKL hkl, UINT count, STYLEBUFW *styleW ) { - ImmHkl *immHkl = IMM_GetImmHkl(hKL); - TRACE("(%p, %d, %p):\n", hKL, nItem, lpStyleBuf); - if (immHkl->hIME && immHkl->pImeGetRegisterWordStyle) + struct ime *ime; + UINT ret; + + TRACE( "hkl %p, count %u, styleW %p.\n", hkl, count, styleW ); + + if (!(ime = ime_acquire( hkl ))) return 0; + + if (ime_is_unicode( ime )) + ret = ime->pImeGetRegisterWordStyle( count, styleW ); + else { - if (is_kbd_ime_unicode(immHkl)) - return immHkl->pImeGetRegisterWordStyle(nItem,lpStyleBuf); - else - { - STYLEBUFA sba; - UINT rc; - - rc = immHkl->pImeGetRegisterWordStyle(nItem,(LPSTYLEBUFW)&sba); - MultiByteToWideChar(CP_ACP, 0, sba.szDescription, -1, - lpStyleBuf->szDescription, 32); - lpStyleBuf->dwStyle = sba.dwStyle; - return rc; - } + STYLEBUFA styleA; + ret = ime->pImeGetRegisterWordStyle( count, &styleA ); + MultiByteToWideChar( CP_ACP, 0, styleA.szDescription, -1, styleW->szDescription, 32 ); + styleW->dwStyle = styleA.dwStyle; } - else - return 0; + + ime_release( ime ); + return ret; } /*********************************************************************** * ImmGetStatusWindowPos (IMM32.@) */ -BOOL WINAPI ImmGetStatusWindowPos(HIMC hIMC, LPPOINT lpptPos) +BOOL WINAPI ImmGetStatusWindowPos( HIMC himc, POINT *pos ) { - InputContextData *data = get_imc_data(hIMC); - - TRACE("(%p, %p)\n", hIMC, lpptPos); + INPUTCONTEXT *ctx; + BOOL ret; - if (!data || !lpptPos) - return FALSE; + TRACE( "himc %p, pos %p\n", himc, pos ); - *lpptPos = data->IMC.ptStatusWndPos; + if (!(ctx = ImmLockIMC( himc ))) return FALSE; + if ((ret = !!(ctx->fdwInit & INIT_STATUSWNDPOS))) *pos = ctx->ptStatusWndPos; + ImmUnlockIMC( himc ); - return TRUE; + return ret; } /*********************************************************************** * ImmGetVirtualKey (IMM32.@) */ -UINT WINAPI ImmGetVirtualKey(HWND hWnd) -{ - OSVERSIONINFOA version; - InputContextData *data = get_imc_data( ImmGetContext( hWnd )); - TRACE("%p\n", hWnd); - - if ( data ) - return data->lastVK; - - version.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA); - GetVersionExA( &version ); - switch(version.dwPlatformId) - { - case VER_PLATFORM_WIN32_WINDOWS: - return VK_PROCESSKEY; - case VER_PLATFORM_WIN32_NT: - return 0; - default: - FIXME("%ld not supported\n",version.dwPlatformId); - return VK_PROCESSKEY; - } +UINT WINAPI ImmGetVirtualKey( HWND hwnd ) +{ + HIMC himc = ImmGetContext( hwnd ); + struct imc *imc; + + TRACE( "%p\n", hwnd ); + + if ((imc = get_imc_data( himc ))) return imc->vkey; + return VK_PROCESSKEY; } /*********************************************************************** * ImmInstallIMEA (IMM32.@) */ -HKL WINAPI ImmInstallIMEA( - LPCSTR lpszIMEFileName, LPCSTR lpszLayoutText) +HKL WINAPI ImmInstallIMEA( const char *filenameA, const char *descriptionA ) { - LPWSTR lpszwIMEFileName; - LPWSTR lpszwLayoutText; + WCHAR *filenameW = strdupAtoW( filenameA ), *descriptionW = strdupAtoW( descriptionA ); HKL hkl; - TRACE ("(%s, %s)\n", debugstr_a(lpszIMEFileName), - debugstr_a(lpszLayoutText)); - - lpszwIMEFileName = strdupAtoW(lpszIMEFileName); - lpszwLayoutText = strdupAtoW(lpszLayoutText); + TRACE( "filenameA %s, descriptionA %s\n", debugstr_a(filenameA), debugstr_a(descriptionA) ); - hkl = ImmInstallIMEW(lpszwIMEFileName, lpszwLayoutText); + hkl = ImmInstallIMEW( filenameW, descriptionW ); + free( descriptionW ); + free( filenameW ); - HeapFree(GetProcessHeap(),0,lpszwIMEFileName); - HeapFree(GetProcessHeap(),0,lpszwLayoutText); return hkl; } +static LCID get_ime_file_lang( const WCHAR *filename ) +{ + DWORD *languages; + LCID lcid = 0; + void *info; + UINT len; + + if (!(len = GetFileVersionInfoSizeW( filename, NULL ))) return 0; + if (!(info = malloc( len ))) goto done; + if (!GetFileVersionInfoW( filename, 0, len, info )) goto done; + if (!VerQueryValueW( info, L"\\VarFileInfo\\Translation", (void **)&languages, &len ) || !len) goto done; + lcid = languages[0]; + +done: + free( info ); + return lcid; +} + /*********************************************************************** * ImmInstallIMEW (IMM32.@) */ -HKL WINAPI ImmInstallIMEW( - LPCWSTR lpszIMEFileName, LPCWSTR lpszLayoutText) +HKL WINAPI ImmInstallIMEW( const WCHAR *filename, const WCHAR *description ) { - INT lcid = GetUserDefaultLCID(); - INT count; - HKL hkl; - DWORD rc; + WCHAR path[ARRAY_SIZE(layouts_formatW)+8], buffer[MAX_PATH]; + LCID lcid; + WORD count = 0x20; + const WCHAR *tmp; + DWORD length; HKEY hkey; - WCHAR regKey[ARRAY_SIZE(szImeRegFmt)+8]; + HKL hkl; - TRACE ("(%s, %s):\n", debugstr_w(lpszIMEFileName), - debugstr_w(lpszLayoutText)); + TRACE( "filename %s, description %s\n", debugstr_w(filename), debugstr_w(description) ); - /* Start with 2. e001 will be blank and so default to the wine internal IME */ - count = 2; + if (!filename || !description || !(lcid = get_ime_file_lang( filename ))) + { + SetLastError( ERROR_INVALID_PARAMETER ); + return 0; + } while (count < 0xfff) { DWORD disposition = 0; - hkl = (HKL)MAKELPARAM( lcid, 0xe000 | count ); - wsprintfW( regKey, szImeRegFmt, (ULONG_PTR)hkl); - - rc = RegCreateKeyExW(HKEY_LOCAL_MACHINE, regKey, 0, NULL, 0, KEY_WRITE, NULL, &hkey, &disposition); - if (rc == ERROR_SUCCESS && disposition == REG_CREATED_NEW_KEY) - break; - else if (rc == ERROR_SUCCESS) - RegCloseKey(hkey); + hkl = (HKL)(UINT_PTR)MAKELONG( lcid, 0xe000 | count ); + swprintf( path, ARRAY_SIZE(path), layouts_formatW, (ULONG)(ULONG_PTR)hkl); + if (!RegCreateKeyExW( HKEY_LOCAL_MACHINE, path, 0, NULL, 0, + KEY_WRITE, NULL, &hkey, &disposition )) + { + if (disposition == REG_CREATED_NEW_KEY) break; + RegCloseKey( hkey ); + } count++; } @@ -2068,32 +2184,32 @@ HKL WINAPI ImmInstallIMEW( return 0; } - if (rc == ERROR_SUCCESS) - { - rc = RegSetValueExW(hkey, L"Ime File", 0, REG_SZ, (const BYTE*)lpszIMEFileName, - (lstrlenW(lpszIMEFileName) + 1) * sizeof(WCHAR)); - if (rc == ERROR_SUCCESS) - rc = RegSetValueExW(hkey, L"Layout Text", 0, REG_SZ, (const BYTE*)lpszLayoutText, - (lstrlenW(lpszLayoutText) + 1) * sizeof(WCHAR)); - RegCloseKey(hkey); - return hkl; - } - else + if ((tmp = wcsrchr( filename, '\\' ))) tmp++; + else tmp = filename; + + length = LCMapStringW( LOCALE_USER_DEFAULT, LCMAP_UPPERCASE, tmp, -1, buffer, ARRAY_SIZE(buffer) ); + + if (RegSetValueExW( hkey, L"Ime File", 0, REG_SZ, (const BYTE *)buffer, length * sizeof(WCHAR) ) || + RegSetValueExW( hkey, L"Layout Text", 0, REG_SZ, (const BYTE *)description, + (wcslen(description) + 1) * sizeof(WCHAR) )) { - WARN("Unable to set IME registry values\n"); - return 0; + WARN( "Unable to write registry to install IME\n"); + hkl = 0; } + RegCloseKey( hkey ); + + if (!hkl) RegDeleteKeyW( HKEY_LOCAL_MACHINE, path ); + return hkl; } /*********************************************************************** * ImmIsIME (IMM32.@) */ -BOOL WINAPI ImmIsIME(HKL hKL) +BOOL WINAPI ImmIsIME( HKL hkl ) { - ImmHkl *ptr; - TRACE("(%p):\n", hKL); - ptr = IMM_GetImmHkl(hKL); - return (ptr && ptr->hIME); + TRACE( "hkl %p\n", hkl ); + if (!hkl) return FALSE; + return TRUE; } /*********************************************************************** @@ -2146,7 +2262,8 @@ BOOL WINAPI ImmIsUIMessageW( BOOL WINAPI ImmNotifyIME( HIMC hIMC, DWORD dwAction, DWORD dwIndex, DWORD dwValue) { - InputContextData *data = get_imc_data(hIMC); + struct imc *data = get_imc_data( hIMC ); + struct ime *ime; TRACE("(%p, %ld, %ld, %ld)\n", hIMC, dwAction, dwIndex, dwValue); @@ -2157,72 +2274,65 @@ BOOL WINAPI ImmNotifyIME( return FALSE; } - if (!data || ! data->immKbd->pNotifyIME) + if (!data) { return FALSE; } - return data->immKbd->pNotifyIME(hIMC,dwAction,dwIndex,dwValue); + if (!(ime = imc_select_ime( data ))) return FALSE; + return ime->pNotifyIME( hIMC, dwAction, dwIndex, dwValue ); } /*********************************************************************** * ImmRegisterWordA (IMM32.@) */ -BOOL WINAPI ImmRegisterWordA( - HKL hKL, LPCSTR lpszReading, DWORD dwStyle, LPCSTR lpszRegister) +BOOL WINAPI ImmRegisterWordA( HKL hkl, const char *readingA, DWORD style, const char *stringA ) { - ImmHkl *immHkl = IMM_GetImmHkl(hKL); - TRACE("(%p, %s, %ld, %s):\n", hKL, debugstr_a(lpszReading), dwStyle, - debugstr_a(lpszRegister)); - if (immHkl->hIME && immHkl->pImeRegisterWord) + struct ime *ime; + BOOL ret; + + TRACE( "hkl %p, readingA %s, style %lu, stringA %s.\n", hkl, debugstr_a(readingA), style, debugstr_a(stringA) ); + + if (!(ime = ime_acquire( hkl ))) return FALSE; + + if (!ime_is_unicode( ime )) + ret = ime->pImeRegisterWord( readingA, style, stringA ); + else { - if (!is_kbd_ime_unicode(immHkl)) - return immHkl->pImeRegisterWord((LPCWSTR)lpszReading,dwStyle, - (LPCWSTR)lpszRegister); - else - { - LPWSTR lpszwReading = strdupAtoW(lpszReading); - LPWSTR lpszwRegister = strdupAtoW(lpszRegister); - BOOL rc; - - rc = immHkl->pImeRegisterWord(lpszwReading,dwStyle,lpszwRegister); - HeapFree(GetProcessHeap(),0,lpszwReading); - HeapFree(GetProcessHeap(),0,lpszwRegister); - return rc; - } + WCHAR *readingW = strdupAtoW( readingA ), *stringW = strdupAtoW( stringA ); + ret = ime->pImeRegisterWord( readingW, style, stringW ); + free( readingW ); + free( stringW ); } - else - return FALSE; + + ime_release( ime ); + return ret; } /*********************************************************************** * ImmRegisterWordW (IMM32.@) */ -BOOL WINAPI ImmRegisterWordW( - HKL hKL, LPCWSTR lpszReading, DWORD dwStyle, LPCWSTR lpszRegister) +BOOL WINAPI ImmRegisterWordW( HKL hkl, const WCHAR *readingW, DWORD style, const WCHAR *stringW ) { - ImmHkl *immHkl = IMM_GetImmHkl(hKL); - TRACE("(%p, %s, %ld, %s):\n", hKL, debugstr_w(lpszReading), dwStyle, - debugstr_w(lpszRegister)); - if (immHkl->hIME && immHkl->pImeRegisterWord) + struct ime *ime; + BOOL ret; + + TRACE( "hkl %p, readingW %s, style %lu, stringW %s.\n", hkl, debugstr_w(readingW), style, debugstr_w(stringW) ); + + if (!(ime = ime_acquire( hkl ))) return FALSE; + + if (ime_is_unicode( ime )) + ret = ime->pImeRegisterWord( readingW, style, stringW ); + else { - if (is_kbd_ime_unicode(immHkl)) - return immHkl->pImeRegisterWord(lpszReading,dwStyle,lpszRegister); - else - { - LPSTR lpszaReading = strdupWtoA(lpszReading); - LPSTR lpszaRegister = strdupWtoA(lpszRegister); - BOOL rc; - - rc = immHkl->pImeRegisterWord((LPCWSTR)lpszaReading,dwStyle, - (LPCWSTR)lpszaRegister); - HeapFree(GetProcessHeap(),0,lpszaReading); - HeapFree(GetProcessHeap(),0,lpszaRegister); - return rc; - } + char *readingA = strdupWtoA( readingW ), *stringA = strdupWtoA( stringW ); + ret = ime->pImeRegisterWord( readingA, style, stringA ); + free( readingA ); + free( stringA ); } - else - return FALSE; + + ime_release( ime ); + return ret; } /*********************************************************************** @@ -2242,60 +2352,90 @@ BOOL WINAPI ImmReleaseContext(HWND hWnd, HIMC hIMC) /*********************************************************************** * ImmRequestMessageA(IMM32.@) */ -LRESULT WINAPI ImmRequestMessageA(HIMC hIMC, WPARAM wParam, LPARAM lParam) +LRESULT WINAPI ImmRequestMessageA( HIMC himc, WPARAM wparam, LPARAM lparam ) { - InputContextData *data = get_imc_data(hIMC); + INPUTCONTEXT *ctx; + LRESULT res; - TRACE("%p %Id %Id\n", hIMC, wParam, wParam); + TRACE( "himc %p, wparam %#Ix, lparam %#Ix\n", himc, wparam, lparam ); - if (data) return SendMessageA(data->IMC.hWnd, WM_IME_REQUEST, wParam, lParam); + if (NtUserQueryInputContext( himc, NtUserInputContextThreadId ) != GetCurrentThreadId()) return FALSE; - SetLastError(ERROR_INVALID_HANDLE); - return 0; + switch (wparam) + { + case IMR_CANDIDATEWINDOW: + case IMR_COMPOSITIONFONT: + case IMR_COMPOSITIONWINDOW: + case IMR_CONFIRMRECONVERTSTRING: + case IMR_DOCUMENTFEED: + case IMR_QUERYCHARPOSITION: + case IMR_RECONVERTSTRING: + break; + default: + return FALSE; + } + + if (!(ctx = ImmLockIMC( himc ))) return FALSE; + res = SendMessageA( ctx->hWnd, WM_IME_REQUEST, wparam, lparam ); + ImmUnlockIMC( himc ); + + return res; } /*********************************************************************** * ImmRequestMessageW(IMM32.@) */ -LRESULT WINAPI ImmRequestMessageW(HIMC hIMC, WPARAM wParam, LPARAM lParam) +LRESULT WINAPI ImmRequestMessageW( HIMC himc, WPARAM wparam, LPARAM lparam ) { - InputContextData *data = get_imc_data(hIMC); + INPUTCONTEXT *ctx; + LRESULT res; - TRACE("%p %Id %Id\n", hIMC, wParam, wParam); + TRACE( "himc %p, wparam %#Ix, lparam %#Ix\n", himc, wparam, lparam ); - if (data) return SendMessageW(data->IMC.hWnd, WM_IME_REQUEST, wParam, lParam); + if (NtUserQueryInputContext( himc, NtUserInputContextThreadId ) != GetCurrentThreadId()) return FALSE; - SetLastError(ERROR_INVALID_HANDLE); - return 0; + switch (wparam) + { + case IMR_CANDIDATEWINDOW: + case IMR_COMPOSITIONFONT: + case IMR_COMPOSITIONWINDOW: + case IMR_CONFIRMRECONVERTSTRING: + case IMR_DOCUMENTFEED: + case IMR_QUERYCHARPOSITION: + case IMR_RECONVERTSTRING: + break; + default: + return FALSE; + } + + if (!(ctx = ImmLockIMC( himc ))) return FALSE; + res = SendMessageW( ctx->hWnd, WM_IME_REQUEST, wparam, lparam ); + ImmUnlockIMC( himc ); + + return res; } /*********************************************************************** * ImmSetCandidateWindow (IMM32.@) */ -BOOL WINAPI ImmSetCandidateWindow( - HIMC hIMC, LPCANDIDATEFORM lpCandidate) +BOOL WINAPI ImmSetCandidateWindow( HIMC himc, CANDIDATEFORM *candidate ) { - InputContextData *data = get_imc_data(hIMC); + INPUTCONTEXT *ctx; - TRACE("(%p, %p)\n", hIMC, lpCandidate); + TRACE( "hwnd %p, candidate %s\n", himc, debugstr_candidate( candidate ) ); - if (!data || !lpCandidate) - return FALSE; + if (!candidate) return FALSE; + if (candidate->dwIndex >= ARRAY_SIZE(ctx->cfCandForm)) return FALSE; - if (IMM_IsCrossThreadAccess(NULL, hIMC)) - return FALSE; + if (NtUserQueryInputContext( himc, NtUserInputContextThreadId ) != GetCurrentThreadId()) return FALSE; + if (!(ctx = ImmLockIMC( himc ))) return FALSE; - TRACE("\t%lx, %lx, %s, %s\n", - lpCandidate->dwIndex, lpCandidate->dwStyle, - wine_dbgstr_point(&lpCandidate->ptCurrentPos), - wine_dbgstr_rect(&lpCandidate->rcArea)); + ctx->cfCandForm[candidate->dwIndex] = *candidate; - if (lpCandidate->dwIndex >= ARRAY_SIZE(data->IMC.cfCandForm)) - return FALSE; + ImmNotifyIME( himc, NI_CONTEXTUPDATED, 0, IMC_SETCANDIDATEPOS ); + SendMessageW( ctx->hWnd, WM_IME_NOTIFY, IMN_SETCANDIDATEPOS, 1 << candidate->dwIndex ); - data->IMC.cfCandForm[lpCandidate->dwIndex] = *lpCandidate; - ImmNotifyIME(hIMC, NI_CONTEXTUPDATED, 0, IMC_SETCANDIDATEPOS); - ImmInternalSendIMENotify(data, IMN_SETCANDIDATEPOS, 1 << lpCandidate->dwIndex); + ImmUnlockIMC( himc ); return TRUE; } @@ -2303,51 +2443,73 @@ BOOL WINAPI ImmSetCandidateWindow( /*********************************************************************** * ImmSetCompositionFontA (IMM32.@) */ -BOOL WINAPI ImmSetCompositionFontA(HIMC hIMC, LPLOGFONTA lplf) +BOOL WINAPI ImmSetCompositionFontA( HIMC himc, LOGFONTA *fontA ) { - InputContextData *data = get_imc_data(hIMC); - TRACE("(%p, %p)\n", hIMC, lplf); + INPUTCONTEXT *ctx; + BOOL ret = TRUE; + + TRACE( "hwnd %p, fontA %p\n", himc, fontA ); + + if (!fontA) return FALSE; + + if (NtUserQueryInputContext( himc, NtUserInputContextThreadId ) != GetCurrentThreadId()) return FALSE; + if (!(ctx = ImmLockIMC( himc ))) return FALSE; - if (!data || !lplf) + if (input_context_is_unicode( ctx )) { - SetLastError(ERROR_INVALID_HANDLE); - return FALSE; + LOGFONTW fontW; + memcpy( &fontW, fontA, offsetof(LOGFONTW, lfFaceName) ); + MultiByteToWideChar( CP_ACP, 0, fontA->lfFaceName, -1, fontW.lfFaceName, LF_FACESIZE ); + ret = ImmSetCompositionFontW( himc, &fontW ); } + else + { + ctx->lfFont.A = *fontA; + ctx->fdwInit |= INIT_LOGFONT; - if (IMM_IsCrossThreadAccess(NULL, hIMC)) - return FALSE; + ImmNotifyIME( himc, NI_CONTEXTUPDATED, 0, IMC_SETCOMPOSITIONFONT ); + SendMessageW( ctx->hWnd, WM_IME_NOTIFY, IMN_SETCOMPOSITIONFONT, 0 ); + } - memcpy(&data->IMC.lfFont.W,lplf,sizeof(LOGFONTA)); - MultiByteToWideChar(CP_ACP, 0, lplf->lfFaceName, -1, data->IMC.lfFont.W.lfFaceName, - LF_FACESIZE); - ImmNotifyIME(hIMC, NI_CONTEXTUPDATED, 0, IMC_SETCOMPOSITIONFONT); - ImmInternalSendIMENotify(data, IMN_SETCOMPOSITIONFONT, 0); + ImmUnlockIMC( himc ); - return TRUE; + return ret; } /*********************************************************************** * ImmSetCompositionFontW (IMM32.@) */ -BOOL WINAPI ImmSetCompositionFontW(HIMC hIMC, LPLOGFONTW lplf) +BOOL WINAPI ImmSetCompositionFontW( HIMC himc, LOGFONTW *fontW ) { - InputContextData *data = get_imc_data(hIMC); - TRACE("(%p, %p)\n", hIMC, lplf); + INPUTCONTEXT *ctx; + BOOL ret = TRUE; + + TRACE( "hwnd %p, fontW %p\n", himc, fontW ); - if (!data || !lplf) + if (!fontW) return FALSE; + + if (NtUserQueryInputContext( himc, NtUserInputContextThreadId ) != GetCurrentThreadId()) return FALSE; + if (!(ctx = ImmLockIMC( himc ))) return FALSE; + + if (!input_context_is_unicode( ctx )) { - SetLastError(ERROR_INVALID_HANDLE); - return FALSE; + LOGFONTA fontA; + memcpy( &fontA, fontW, offsetof(LOGFONTA, lfFaceName) ); + WideCharToMultiByte( CP_ACP, 0, fontW->lfFaceName, -1, fontA.lfFaceName, LF_FACESIZE, NULL, NULL ); + ret = ImmSetCompositionFontA( himc, &fontA ); } + else + { + ctx->lfFont.W = *fontW; + ctx->fdwInit |= INIT_LOGFONT; - if (IMM_IsCrossThreadAccess(NULL, hIMC)) - return FALSE; + ImmNotifyIME( himc, NI_CONTEXTUPDATED, 0, IMC_SETCOMPOSITIONFONT ); + SendMessageW( ctx->hWnd, WM_IME_NOTIFY, IMN_SETCOMPOSITIONFONT, 0 ); + } - data->IMC.lfFont.W = *lplf; - ImmNotifyIME(hIMC, NI_CONTEXTUPDATED, 0, IMC_SETCOMPOSITIONFONT); - ImmInternalSendIMENotify(data, IMN_SETCOMPOSITIONFONT, 0); + ImmUnlockIMC( himc ); - return TRUE; + return ret; } /*********************************************************************** @@ -2363,7 +2525,8 @@ BOOL WINAPI ImmSetCompositionStringA( WCHAR *CompBuffer = NULL; WCHAR *ReadBuffer = NULL; BOOL rc; - InputContextData *data = get_imc_data(hIMC); + struct imc *data = get_imc_data( hIMC ); + struct ime *ime; TRACE("(%p, %ld, %p, %ld, %p, %ld):\n", hIMC, dwIndex, lpComp, dwCompLen, lpRead, dwReadLen); @@ -2371,8 +2534,7 @@ BOOL WINAPI ImmSetCompositionStringA( if (!data) return FALSE; - if (IMM_IsCrossThreadAccess(NULL, hIMC)) - return FALSE; + if (NtUserQueryInputContext( hIMC, NtUserInputContextThreadId ) != GetCurrentThreadId()) return FALSE; if (!(dwIndex == SCS_SETSTR || dwIndex == SCS_CHANGEATTR || @@ -2381,29 +2543,28 @@ BOOL WINAPI ImmSetCompositionStringA( dwIndex == SCS_QUERYRECONVERTSTRING)) return FALSE; - if (!is_himc_ime_unicode(data)) - return data->immKbd->pImeSetCompositionString(hIMC, dwIndex, lpComp, - dwCompLen, lpRead, dwReadLen); + if (!(ime = imc_select_ime( data ))) return FALSE; + if (!ime_is_unicode( ime )) return ime->pImeSetCompositionString( hIMC, dwIndex, lpComp, dwCompLen, lpRead, dwReadLen ); comp_len = MultiByteToWideChar(CP_ACP, 0, lpComp, dwCompLen, NULL, 0); if (comp_len) { - CompBuffer = HeapAlloc(GetProcessHeap(),0,comp_len * sizeof(WCHAR)); + CompBuffer = malloc( comp_len * sizeof(WCHAR) ); MultiByteToWideChar(CP_ACP, 0, lpComp, dwCompLen, CompBuffer, comp_len); } read_len = MultiByteToWideChar(CP_ACP, 0, lpRead, dwReadLen, NULL, 0); if (read_len) { - ReadBuffer = HeapAlloc(GetProcessHeap(),0,read_len * sizeof(WCHAR)); + ReadBuffer = malloc( read_len * sizeof(WCHAR) ); MultiByteToWideChar(CP_ACP, 0, lpRead, dwReadLen, ReadBuffer, read_len); } rc = ImmSetCompositionStringW(hIMC, dwIndex, CompBuffer, comp_len, ReadBuffer, read_len); - HeapFree(GetProcessHeap(), 0, CompBuffer); - HeapFree(GetProcessHeap(), 0, ReadBuffer); + free( CompBuffer ); + free( ReadBuffer ); return rc; } @@ -2421,7 +2582,8 @@ BOOL WINAPI ImmSetCompositionStringW( CHAR *CompBuffer = NULL; CHAR *ReadBuffer = NULL; BOOL rc; - InputContextData *data = get_imc_data(hIMC); + struct imc *data = get_imc_data( hIMC ); + struct ime *ime; TRACE("(%p, %ld, %p, %ld, %p, %ld):\n", hIMC, dwIndex, lpComp, dwCompLen, lpRead, dwReadLen); @@ -2429,8 +2591,7 @@ BOOL WINAPI ImmSetCompositionStringW( if (!data) return FALSE; - if (IMM_IsCrossThreadAccess(NULL, hIMC)) - return FALSE; + if (NtUserQueryInputContext( hIMC, NtUserInputContextThreadId ) != GetCurrentThreadId()) return FALSE; if (!(dwIndex == SCS_SETSTR || dwIndex == SCS_CHANGEATTR || @@ -2439,15 +2600,14 @@ BOOL WINAPI ImmSetCompositionStringW( dwIndex == SCS_QUERYRECONVERTSTRING)) return FALSE; - if (is_himc_ime_unicode(data)) - return data->immKbd->pImeSetCompositionString(hIMC, dwIndex, lpComp, - dwCompLen, lpRead, dwReadLen); + if (!(ime = imc_select_ime( data ))) return FALSE; + if (ime_is_unicode( ime )) return ime->pImeSetCompositionString( hIMC, dwIndex, lpComp, dwCompLen, lpRead, dwReadLen ); comp_len = WideCharToMultiByte(CP_ACP, 0, lpComp, dwCompLen, NULL, 0, NULL, NULL); if (comp_len) { - CompBuffer = HeapAlloc(GetProcessHeap(),0,comp_len); + CompBuffer = malloc( comp_len ); WideCharToMultiByte(CP_ACP, 0, lpComp, dwCompLen, CompBuffer, comp_len, NULL, NULL); } @@ -2456,7 +2616,7 @@ BOOL WINAPI ImmSetCompositionStringW( NULL); if (read_len) { - ReadBuffer = HeapAlloc(GetProcessHeap(),0,read_len); + ReadBuffer = malloc( read_len ); WideCharToMultiByte(CP_ACP, 0, lpRead, dwReadLen, ReadBuffer, read_len, NULL, NULL); } @@ -2464,8 +2624,8 @@ BOOL WINAPI ImmSetCompositionStringW( rc = ImmSetCompositionStringA(hIMC, dwIndex, CompBuffer, comp_len, ReadBuffer, read_len); - HeapFree(GetProcessHeap(), 0, CompBuffer); - HeapFree(GetProcessHeap(), 0, ReadBuffer); + free( CompBuffer ); + free( ReadBuffer ); return rc; } @@ -2473,117 +2633,80 @@ BOOL WINAPI ImmSetCompositionStringW( /*********************************************************************** * ImmSetCompositionWindow (IMM32.@) */ -BOOL WINAPI ImmSetCompositionWindow( - HIMC hIMC, LPCOMPOSITIONFORM lpCompForm) +BOOL WINAPI ImmSetCompositionWindow( HIMC himc, COMPOSITIONFORM *composition ) { - BOOL reshow = FALSE; - InputContextData *data = get_imc_data(hIMC); - - TRACE("(%p, %p)\n", hIMC, lpCompForm); - if (lpCompForm) - TRACE("\t%lx, %s, %s\n", lpCompForm->dwStyle, - wine_dbgstr_point(&lpCompForm->ptCurrentPos), - wine_dbgstr_rect(&lpCompForm->rcArea)); - - if (!data) - { - SetLastError(ERROR_INVALID_HANDLE); - return FALSE; - } + INPUTCONTEXT *ctx; - if (IMM_IsCrossThreadAccess(NULL, hIMC)) - return FALSE; + TRACE( "himc %p, composition %s\n", himc, debugstr_composition( composition ) ); - data->IMC.cfCompForm = *lpCompForm; + if (NtUserQueryInputContext( himc, NtUserInputContextThreadId ) != GetCurrentThreadId()) return FALSE; + if (!(ctx = ImmLockIMC( himc ))) return FALSE; - if (IsWindowVisible(data->immKbd->UIWnd)) - { - reshow = TRUE; - ShowWindow(data->immKbd->UIWnd,SW_HIDE); - } + ctx->cfCompForm = *composition; + ctx->fdwInit |= INIT_COMPFORM; - /* FIXME: this is a partial stub */ + ImmNotifyIME( himc, NI_CONTEXTUPDATED, 0, IMC_SETCOMPOSITIONWINDOW ); + SendMessageW( ctx->hWnd, WM_IME_NOTIFY, IMN_SETCOMPOSITIONWINDOW, 0 ); - if (reshow) - ShowWindow(data->immKbd->UIWnd,SW_SHOWNOACTIVATE); + ImmUnlockIMC( himc ); - ImmInternalSendIMENotify(data, IMN_SETCOMPOSITIONWINDOW, 0); return TRUE; } /*********************************************************************** * ImmSetConversionStatus (IMM32.@) */ -BOOL WINAPI ImmSetConversionStatus( - HIMC hIMC, DWORD fdwConversion, DWORD fdwSentence) +BOOL WINAPI ImmSetConversionStatus( HIMC himc, DWORD conversion, DWORD sentence ) { - DWORD oldConversion, oldSentence; - InputContextData *data = get_imc_data(hIMC); + DWORD old_conversion, old_sentence; + INPUTCONTEXT *ctx; - TRACE("%p %ld %ld\n", hIMC, fdwConversion, fdwSentence); - - if (!data) - { - SetLastError(ERROR_INVALID_HANDLE); - return FALSE; - } + TRACE( "himc %p, conversion %#lx, sentence %#lx\n", himc, conversion, sentence ); - if (IMM_IsCrossThreadAccess(NULL, hIMC)) - return FALSE; + if (NtUserQueryInputContext( himc, NtUserInputContextThreadId ) != GetCurrentThreadId()) return FALSE; + if (!(ctx = ImmLockIMC( himc ))) return FALSE; - if ( fdwConversion != data->IMC.fdwConversion ) + if (conversion != ctx->fdwConversion) { - oldConversion = data->IMC.fdwConversion; - data->IMC.fdwConversion = fdwConversion; - ImmNotifyIME(hIMC, NI_CONTEXTUPDATED, oldConversion, IMC_SETCONVERSIONMODE); - ImmInternalSendIMENotify(data, IMN_SETCONVERSIONMODE, 0); + old_conversion = ctx->fdwConversion; + ctx->fdwConversion = conversion; + ImmNotifyIME( himc, NI_CONTEXTUPDATED, old_conversion, IMC_SETCONVERSIONMODE ); + SendMessageW( ctx->hWnd, WM_IME_NOTIFY, IMN_SETCONVERSIONMODE, 0 ); } - if ( fdwSentence != data->IMC.fdwSentence ) + + if (sentence != ctx->fdwSentence) { - oldSentence = data->IMC.fdwSentence; - data->IMC.fdwSentence = fdwSentence; - ImmNotifyIME(hIMC, NI_CONTEXTUPDATED, oldSentence, IMC_SETSENTENCEMODE); - ImmInternalSendIMENotify(data, IMN_SETSENTENCEMODE, 0); + old_sentence = ctx->fdwSentence; + ctx->fdwSentence = sentence; + ImmNotifyIME( himc, NI_CONTEXTUPDATED, old_sentence, IMC_SETSENTENCEMODE ); + SendMessageW( ctx->hWnd, WM_IME_NOTIFY, IMN_SETSENTENCEMODE, 0 ); } + ImmUnlockIMC( himc ); + return TRUE; } /*********************************************************************** * ImmSetOpenStatus (IMM32.@) */ -BOOL WINAPI ImmSetOpenStatus(HIMC hIMC, BOOL fOpen) +BOOL WINAPI ImmSetOpenStatus( HIMC himc, BOOL status ) { - InputContextData *data = get_imc_data(hIMC); + INPUTCONTEXT *ctx; - TRACE("%p %d\n", hIMC, fOpen); - - if (!data) - { - SetLastError(ERROR_INVALID_HANDLE); - return FALSE; - } + TRACE( "himc %p, status %u\n", himc, status ); - if (IMM_IsCrossThreadAccess(NULL, hIMC)) - return FALSE; + if (NtUserQueryInputContext( himc, NtUserInputContextThreadId ) != GetCurrentThreadId()) return FALSE; + if (!(ctx = ImmLockIMC( himc ))) return FALSE; - if (data->immKbd->UIWnd == NULL) + if (status != ctx->fOpen) { - /* create the ime window */ - data->immKbd->UIWnd = CreateWindowExW( WS_EX_TOOLWINDOW, - data->immKbd->imeClassName, NULL, WS_POPUP, 0, 0, 1, 1, 0, - 0, data->immKbd->hIME, 0); - SetWindowLongPtrW(data->immKbd->UIWnd, IMMGWL_IMC, (LONG_PTR)data); + ctx->fOpen = status; + ImmNotifyIME( himc, NI_CONTEXTUPDATED, 0, IMC_SETOPENSTATUS ); + SendMessageW( ctx->hWnd, WM_IME_NOTIFY, IMN_SETOPENSTATUS, 0 ); } - else if (fOpen) - SetWindowLongPtrW(data->immKbd->UIWnd, IMMGWL_IMC, (LONG_PTR)data); - if (!fOpen != !data->IMC.fOpen) - { - data->IMC.fOpen = fOpen; - ImmNotifyIME( hIMC, NI_CONTEXTUPDATED, 0, IMC_SETOPENSTATUS); - ImmInternalSendIMENotify(data, IMN_SETOPENSTATUS, 0); - } + ImmUnlockIMC( himc ); return TRUE; } @@ -2591,26 +2714,28 @@ BOOL WINAPI ImmSetOpenStatus(HIMC hIMC, BOOL fOpen) /*********************************************************************** * ImmSetStatusWindowPos (IMM32.@) */ -BOOL WINAPI ImmSetStatusWindowPos(HIMC hIMC, LPPOINT lpptPos) +BOOL WINAPI ImmSetStatusWindowPos( HIMC himc, POINT *pos ) { - InputContextData *data = get_imc_data(hIMC); + INPUTCONTEXT *ctx; - TRACE("(%p, %p)\n", hIMC, lpptPos); + TRACE( "himc %p, pos %s\n", himc, wine_dbgstr_point( pos ) ); - if (!data || !lpptPos) + if (!pos) { - SetLastError(ERROR_INVALID_HANDLE); + SetLastError( ERROR_INVALID_HANDLE ); return FALSE; } - if (IMM_IsCrossThreadAccess(NULL, hIMC)) - return FALSE; + if (NtUserQueryInputContext( himc, NtUserInputContextThreadId ) != GetCurrentThreadId()) return FALSE; + if (!(ctx = ImmLockIMC( himc ))) return FALSE; + + ctx->ptStatusWndPos = *pos; + ctx->fdwInit |= INIT_STATUSWNDPOS; - TRACE("\t%s\n", wine_dbgstr_point(lpptPos)); + ImmNotifyIME( himc, NI_CONTEXTUPDATED, 0, IMC_SETSTATUSWINDOWPOS ); + SendMessageW( ctx->hWnd, WM_IME_NOTIFY, IMN_SETSTATUSWINDOWPOS, 0 ); - data->IMC.ptStatusWndPos = *lpptPos; - ImmNotifyIME( hIMC, NI_CONTEXTUPDATED, 0, IMC_SETSTATUSWINDOWPOS); - ImmInternalSendIMENotify(data, IMN_SETSTATUSWINDOWPOS, 0); + ImmUnlockIMC( himc ); return TRUE; } @@ -2658,214 +2783,187 @@ BOOL WINAPI ImmSimulateHotKey(HWND hWnd, DWORD dwHotKeyID) /*********************************************************************** * ImmUnregisterWordA (IMM32.@) */ -BOOL WINAPI ImmUnregisterWordA( - HKL hKL, LPCSTR lpszReading, DWORD dwStyle, LPCSTR lpszUnregister) +BOOL WINAPI ImmUnregisterWordA( HKL hkl, const char *readingA, DWORD style, const char *stringA ) { - ImmHkl *immHkl = IMM_GetImmHkl(hKL); - TRACE("(%p, %s, %ld, %s):\n", hKL, debugstr_a(lpszReading), dwStyle, - debugstr_a(lpszUnregister)); - if (immHkl->hIME && immHkl->pImeUnregisterWord) + struct ime *ime; + BOOL ret; + + TRACE( "hkl %p, readingA %s, style %lu, stringA %s.\n", hkl, debugstr_a(readingA), style, debugstr_a(stringA) ); + + if (!(ime = ime_acquire( hkl ))) return FALSE; + + if (!ime_is_unicode( ime )) + ret = ime->pImeUnregisterWord( readingA, style, stringA ); + else { - if (!is_kbd_ime_unicode(immHkl)) - return immHkl->pImeUnregisterWord((LPCWSTR)lpszReading,dwStyle, - (LPCWSTR)lpszUnregister); - else - { - LPWSTR lpszwReading = strdupAtoW(lpszReading); - LPWSTR lpszwUnregister = strdupAtoW(lpszUnregister); - BOOL rc; - - rc = immHkl->pImeUnregisterWord(lpszwReading,dwStyle,lpszwUnregister); - HeapFree(GetProcessHeap(),0,lpszwReading); - HeapFree(GetProcessHeap(),0,lpszwUnregister); - return rc; - } + WCHAR *readingW = strdupAtoW( readingA ), *stringW = strdupAtoW( stringA ); + ret = ime->pImeUnregisterWord( readingW, style, stringW ); + free( readingW ); + free( stringW ); } - else - return FALSE; + + ime_release( ime ); + return ret; } /*********************************************************************** * ImmUnregisterWordW (IMM32.@) */ -BOOL WINAPI ImmUnregisterWordW( - HKL hKL, LPCWSTR lpszReading, DWORD dwStyle, LPCWSTR lpszUnregister) +BOOL WINAPI ImmUnregisterWordW( HKL hkl, const WCHAR *readingW, DWORD style, const WCHAR *stringW ) { - ImmHkl *immHkl = IMM_GetImmHkl(hKL); - TRACE("(%p, %s, %ld, %s):\n", hKL, debugstr_w(lpszReading), dwStyle, - debugstr_w(lpszUnregister)); - if (immHkl->hIME && immHkl->pImeUnregisterWord) + struct ime *ime; + BOOL ret; + + TRACE( "hkl %p, readingW %s, style %lu, stringW %s.\n", hkl, debugstr_w(readingW), style, debugstr_w(stringW) ); + + if (!(ime = ime_acquire( hkl ))) return FALSE; + + if (ime_is_unicode( ime )) + ret = ime->pImeUnregisterWord( readingW, style, stringW ); + else { - if (is_kbd_ime_unicode(immHkl)) - return immHkl->pImeUnregisterWord(lpszReading,dwStyle,lpszUnregister); - else - { - LPSTR lpszaReading = strdupWtoA(lpszReading); - LPSTR lpszaUnregister = strdupWtoA(lpszUnregister); - BOOL rc; - - rc = immHkl->pImeUnregisterWord((LPCWSTR)lpszaReading,dwStyle, - (LPCWSTR)lpszaUnregister); - HeapFree(GetProcessHeap(),0,lpszaReading); - HeapFree(GetProcessHeap(),0,lpszaUnregister); - return rc; - } + char *readingA = strdupWtoA( readingW ), *stringA = strdupWtoA( stringW ); + ret = ime->pImeUnregisterWord( readingA, style, stringA ); + free( readingA ); + free( stringA ); } - else - return FALSE; + + ime_release( ime ); + return ret; } /*********************************************************************** * ImmGetImeMenuItemsA (IMM32.@) */ -DWORD WINAPI ImmGetImeMenuItemsA( HIMC hIMC, DWORD dwFlags, DWORD dwType, - LPIMEMENUITEMINFOA lpImeParentMenu, LPIMEMENUITEMINFOA lpImeMenu, - DWORD dwSize) +DWORD WINAPI ImmGetImeMenuItemsA( HIMC himc, DWORD flags, DWORD type, IMEMENUITEMINFOA *parentA, + IMEMENUITEMINFOA *menuA, DWORD size ) { - InputContextData *data = get_imc_data(hIMC); - TRACE("(%p, %li, %li, %p, %p, %li):\n", hIMC, dwFlags, dwType, - lpImeParentMenu, lpImeMenu, dwSize); + struct imc *data = get_imc_data( himc ); + struct ime *ime; + DWORD ret; + + TRACE( "himc %p, flags %#lx, type %lu, parentA %p, menuA %p, size %lu.\n", + himc, flags, type, parentA, menuA, size ); if (!data) { - SetLastError(ERROR_INVALID_HANDLE); + SetLastError( ERROR_INVALID_HANDLE ); return 0; } - if (data->immKbd->hIME && data->immKbd->pImeGetImeMenuItems) + if (!(ime = imc_select_ime( data ))) return 0; + if (!ime_is_unicode( ime ) || (!parentA && !menuA)) + ret = ime->pImeGetImeMenuItems( himc, flags, type, parentA, menuA, size ); + else { - if (!is_himc_ime_unicode(data) || (!lpImeParentMenu && !lpImeMenu)) - return data->immKbd->pImeGetImeMenuItems(hIMC, dwFlags, dwType, - (IMEMENUITEMINFOW*)lpImeParentMenu, - (IMEMENUITEMINFOW*)lpImeMenu, dwSize); + IMEMENUITEMINFOW tmpW, *menuW, *parentW = parentA ? &tmpW : NULL; + + if (!menuA) menuW = NULL; else { - IMEMENUITEMINFOW lpImeParentMenuW; - IMEMENUITEMINFOW *lpImeMenuW, *parent = NULL; - DWORD rc; - - if (lpImeParentMenu) - parent = &lpImeParentMenuW; - if (lpImeMenu) - { - int count = dwSize / sizeof(LPIMEMENUITEMINFOA); - dwSize = count * sizeof(IMEMENUITEMINFOW); - lpImeMenuW = HeapAlloc(GetProcessHeap(), 0, dwSize); - } - else - lpImeMenuW = NULL; + int count = size / sizeof(LPIMEMENUITEMINFOA); + size = count * sizeof(IMEMENUITEMINFOW); + menuW = malloc( size ); + } - rc = data->immKbd->pImeGetImeMenuItems(hIMC, dwFlags, dwType, - parent, lpImeMenuW, dwSize); + ret = ime->pImeGetImeMenuItems( himc, flags, type, parentW, menuW, size ); - if (lpImeParentMenu) - { - memcpy(lpImeParentMenu,&lpImeParentMenuW,sizeof(IMEMENUITEMINFOA)); - lpImeParentMenu->hbmpItem = lpImeParentMenuW.hbmpItem; - WideCharToMultiByte(CP_ACP, 0, lpImeParentMenuW.szString, - -1, lpImeParentMenu->szString, IMEMENUITEM_STRING_SIZE, - NULL, NULL); - } - if (lpImeMenu && rc) + if (parentA) + { + memcpy( parentA, parentW, sizeof(IMEMENUITEMINFOA) ); + parentA->hbmpItem = parentW->hbmpItem; + WideCharToMultiByte( CP_ACP, 0, parentW->szString, -1, parentA->szString, + IMEMENUITEM_STRING_SIZE, NULL, NULL ); + } + if (menuA && ret) + { + unsigned int i; + for (i = 0; i < ret; i++) { - unsigned int i; - for (i = 0; i < rc; i++) - { - memcpy(&lpImeMenu[i],&lpImeMenuW[1],sizeof(IMEMENUITEMINFOA)); - lpImeMenu[i].hbmpItem = lpImeMenuW[i].hbmpItem; - WideCharToMultiByte(CP_ACP, 0, lpImeMenuW[i].szString, - -1, lpImeMenu[i].szString, IMEMENUITEM_STRING_SIZE, - NULL, NULL); - } + memcpy( &menuA[i], &menuW[1], sizeof(IMEMENUITEMINFOA) ); + menuA[i].hbmpItem = menuW[i].hbmpItem; + WideCharToMultiByte( CP_ACP, 0, menuW[i].szString, -1, menuA[i].szString, + IMEMENUITEM_STRING_SIZE, NULL, NULL ); } - HeapFree(GetProcessHeap(),0,lpImeMenuW); - return rc; } + free( menuW ); } - else - return 0; + + return ret; } /*********************************************************************** * ImmGetImeMenuItemsW (IMM32.@) */ -DWORD WINAPI ImmGetImeMenuItemsW( HIMC hIMC, DWORD dwFlags, DWORD dwType, - LPIMEMENUITEMINFOW lpImeParentMenu, LPIMEMENUITEMINFOW lpImeMenu, - DWORD dwSize) +DWORD WINAPI ImmGetImeMenuItemsW( HIMC himc, DWORD flags, DWORD type, IMEMENUITEMINFOW *parentW, + IMEMENUITEMINFOW *menuW, DWORD size ) { - InputContextData *data = get_imc_data(hIMC); - TRACE("(%p, %li, %li, %p, %p, %li):\n", hIMC, dwFlags, dwType, - lpImeParentMenu, lpImeMenu, dwSize); + struct imc *data = get_imc_data( himc ); + struct ime *ime; + DWORD ret; + + TRACE( "himc %p, flags %#lx, type %lu, parentW %p, menuW %p, size %lu.\n", + himc, flags, type, parentW, menuW, size ); if (!data) { - SetLastError(ERROR_INVALID_HANDLE); + SetLastError( ERROR_INVALID_HANDLE ); return 0; } - if (data->immKbd->hIME && data->immKbd->pImeGetImeMenuItems) + if (!(ime = imc_select_ime( data ))) return 0; + if (ime_is_unicode( ime ) || (!parentW && !menuW)) + ret = ime->pImeGetImeMenuItems( himc, flags, type, parentW, menuW, size ); + else { - if (is_himc_ime_unicode(data) || (!lpImeParentMenu && !lpImeMenu)) - return data->immKbd->pImeGetImeMenuItems(hIMC, dwFlags, dwType, - lpImeParentMenu, lpImeMenu, dwSize); + IMEMENUITEMINFOA tmpA, *menuA, *parentA = parentW ? &tmpA : NULL; + + if (!menuW) menuA = NULL; else { - IMEMENUITEMINFOA lpImeParentMenuA; - IMEMENUITEMINFOA *lpImeMenuA, *parent = NULL; - DWORD rc; - - if (lpImeParentMenu) - parent = &lpImeParentMenuA; - if (lpImeMenu) - { - int count = dwSize / sizeof(LPIMEMENUITEMINFOW); - dwSize = count * sizeof(IMEMENUITEMINFOA); - lpImeMenuA = HeapAlloc(GetProcessHeap(), 0, dwSize); - } - else - lpImeMenuA = NULL; + int count = size / sizeof(LPIMEMENUITEMINFOW); + size = count * sizeof(IMEMENUITEMINFOA); + menuA = malloc( size ); + } - rc = data->immKbd->pImeGetImeMenuItems(hIMC, dwFlags, dwType, - (IMEMENUITEMINFOW*)parent, - (IMEMENUITEMINFOW*)lpImeMenuA, dwSize); + ret = ime->pImeGetImeMenuItems( himc, flags, type, parentA, menuA, size ); - if (lpImeParentMenu) - { - memcpy(lpImeParentMenu,&lpImeParentMenuA,sizeof(IMEMENUITEMINFOA)); - lpImeParentMenu->hbmpItem = lpImeParentMenuA.hbmpItem; - MultiByteToWideChar(CP_ACP, 0, lpImeParentMenuA.szString, - -1, lpImeParentMenu->szString, IMEMENUITEM_STRING_SIZE); - } - if (lpImeMenu && rc) + if (parentW) + { + memcpy( parentW, parentA, sizeof(IMEMENUITEMINFOA) ); + parentW->hbmpItem = parentA->hbmpItem; + MultiByteToWideChar( CP_ACP, 0, parentA->szString, -1, parentW->szString, IMEMENUITEM_STRING_SIZE ); + } + if (menuW && ret) + { + unsigned int i; + for (i = 0; i < ret; i++) { - unsigned int i; - for (i = 0; i < rc; i++) - { - memcpy(&lpImeMenu[i],&lpImeMenuA[1],sizeof(IMEMENUITEMINFOA)); - lpImeMenu[i].hbmpItem = lpImeMenuA[i].hbmpItem; - MultiByteToWideChar(CP_ACP, 0, lpImeMenuA[i].szString, - -1, lpImeMenu[i].szString, IMEMENUITEM_STRING_SIZE); - } + memcpy( &menuW[i], &menuA[1], sizeof(IMEMENUITEMINFOA) ); + menuW[i].hbmpItem = menuA[i].hbmpItem; + MultiByteToWideChar( CP_ACP, 0, menuA[i].szString, -1, menuW[i].szString, IMEMENUITEM_STRING_SIZE ); } - HeapFree(GetProcessHeap(),0,lpImeMenuA); - return rc; } + free( menuA ); } - else - return 0; + + return ret; } /*********************************************************************** * ImmLockIMC(IMM32.@) */ -LPINPUTCONTEXT WINAPI ImmLockIMC(HIMC hIMC) +INPUTCONTEXT *WINAPI ImmLockIMC( HIMC himc ) { - InputContextData *data = get_imc_data(hIMC); + struct imc *imc = get_imc_data( himc ); - if (!data) - return NULL; - data->dwLock++; - return &data->IMC; + TRACE( "himc %p\n", himc ); + + if (!imc) return NULL; + imc->dwLock++; + + imc_select_ime( imc ); + return &imc->IMC; } /*********************************************************************** @@ -2873,7 +2971,7 @@ LPINPUTCONTEXT WINAPI ImmLockIMC(HIMC hIMC) */ BOOL WINAPI ImmUnlockIMC(HIMC hIMC) { - InputContextData *data = get_imc_data(hIMC); + struct imc *data = get_imc_data( hIMC ); if (!data) return FALSE; @@ -2887,7 +2985,7 @@ BOOL WINAPI ImmUnlockIMC(HIMC hIMC) */ DWORD WINAPI ImmGetIMCLockCount(HIMC hIMC) { - InputContextData *data = get_imc_data(hIMC); + struct imc *data = get_imc_data( hIMC ); if (!data) return 0; return data->dwLock; @@ -2952,38 +3050,25 @@ DWORD WINAPI ImmGetIMCCSize(HIMCC imcc) /*********************************************************************** * ImmGenerateMessage(IMM32.@) */ -BOOL WINAPI ImmGenerateMessage(HIMC hIMC) +BOOL WINAPI ImmGenerateMessage( HIMC himc ) { - InputContextData *data = get_imc_data(hIMC); - - if (!data) - { - SetLastError(ERROR_INVALID_HANDLE); - return FALSE; - } - - TRACE("%li messages queued\n",data->IMC.dwNumMsgBuf); - if (data->IMC.dwNumMsgBuf > 0) - { - LPTRANSMSG lpTransMsg; - HIMCC hMsgBuf; - DWORD i, dwNumMsgBuf; + INPUTCONTEXT *ctx; - /* We are going to detach our hMsgBuff so that if processing messages - generates new messages they go into a new buffer */ - hMsgBuf = data->IMC.hMsgBuf; - dwNumMsgBuf = data->IMC.dwNumMsgBuf; + TRACE( "himc %p\n", himc ); - data->IMC.hMsgBuf = ImmCreateIMCC(0); - data->IMC.dwNumMsgBuf = 0; + if (NtUserQueryInputContext( himc, NtUserInputContextThreadId ) != GetCurrentThreadId()) return FALSE; + if (!(ctx = ImmLockIMC( himc ))) return FALSE; - lpTransMsg = ImmLockIMCC(hMsgBuf); - for (i = 0; i < dwNumMsgBuf; i++) - ImmInternalSendIMEMessage(data, lpTransMsg[i].message, lpTransMsg[i].wParam, lpTransMsg[i].lParam); - - ImmUnlockIMCC(hMsgBuf); - ImmDestroyIMCC(hMsgBuf); + while (ctx->dwNumMsgBuf--) + { + TRANSMSG *msgs, msg; + if (!(msgs = ImmLockIMCC( ctx->hMsgBuf ))) return FALSE; + msg = msgs[0]; + memmove( msgs, msgs + 1, ctx->dwNumMsgBuf * sizeof(*msgs) ); + ImmUnlockIMCC( ctx->hMsgBuf ); + SendMessageW( ctx->hWnd, msg.message, msg.wParam, msg.lParam ); } + ctx->dwNumMsgBuf++; return TRUE; } @@ -2992,105 +3077,74 @@ BOOL WINAPI ImmGenerateMessage(HIMC hIMC) * ImmTranslateMessage(IMM32.@) * ( Undocumented, call internally and from user32.dll ) */ -BOOL WINAPI ImmTranslateMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lKeyData) +BOOL WINAPI ImmTranslateMessage( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) { - InputContextData *data; - HIMC imc = ImmGetContext(hwnd); + union + { + struct + { + UINT uMsgCount; + TRANSMSG TransMsg[10]; + }; + TRANSMSGLIST list; + } buffer = {.uMsgCount = ARRAY_SIZE(buffer.TransMsg)}; + TRANSMSG *msgs = buffer.TransMsg; + UINT scan, vkey, count, i; + struct imc *data; + struct ime *ime; BYTE state[256]; - UINT scancode; - TRANSMSGLIST *list = NULL; - UINT msg_count; - UINT uVirtKey; - static const DWORD list_count = 10; - - TRACE("%p %x %x %x\n",hwnd, msg, (UINT)wParam, (UINT)lKeyData); - - if (!(data = get_imc_data( imc ))) return FALSE; - - if (!data->immKbd->hIME || !data->immKbd->pImeToAsciiEx || data->lastVK == VK_PROCESSKEY) - return FALSE; - - GetKeyboardState(state); - scancode = lKeyData >> 0x10 & 0xff; + WCHAR chr; - list = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, list_count * sizeof(TRANSMSG) + sizeof(DWORD)); - list->uMsgCount = list_count; + TRACE( "hwnd %p, msg %#x, wparam %#Ix, lparam %#Ix\n", hwnd, msg, wparam, lparam ); - if (data->immKbd->imeInfo.fdwProperty & IME_PROP_KBD_CHAR_FIRST) - { - WCHAR chr; + if (msg < WM_KEYDOWN || msg > WM_KEYUP) return FALSE; + if (!(data = get_imc_data( ImmGetContext( hwnd ) ))) return FALSE; + if (!(ime = imc_select_ime( data ))) return FALSE; - if (!is_himc_ime_unicode(data)) - ToAscii(data->lastVK, scancode, state, &chr, 0); - else - ToUnicodeEx(data->lastVK, scancode, state, &chr, 1, 0, GetKeyboardLayout(0)); - uVirtKey = MAKELONG(data->lastVK,chr); - } - else - uVirtKey = data->lastVK; + if ((vkey = data->vkey) == VK_PROCESSKEY) return FALSE; + data->vkey = VK_PROCESSKEY; + GetKeyboardState( state ); + scan = lparam >> 0x10; - msg_count = data->immKbd->pImeToAsciiEx(uVirtKey, scancode, state, list, 0, imc); - TRACE("%i messages generated\n",msg_count); - if (msg_count && msg_count <= list_count) + if (ime->info.fdwProperty & IME_PROP_KBD_CHAR_FIRST) { - UINT i; - LPTRANSMSG msgs = list->TransMsg; - - for (i = 0; i < msg_count; i++) - ImmInternalPostIMEMessage(data, msgs[i].message, msgs[i].wParam, msgs[i].lParam); + if (!ime_is_unicode( ime )) ToAscii( vkey, scan, state, &chr, 0 ); + else ToUnicodeEx( vkey, scan, state, &chr, 1, 0, GetKeyboardLayout( 0 ) ); + vkey = MAKELONG( vkey, chr ); } - else if (msg_count > list_count) - ImmGenerateMessage(imc); - HeapFree(GetProcessHeap(),0,list); + count = ime->pImeToAsciiEx( vkey, scan, state, &buffer.list, 0, data->handle ); + if (count >= ARRAY_SIZE(buffer.TransMsg)) return 0; - data->lastVK = VK_PROCESSKEY; + for (i = 0; i < count; i++) PostMessageW( hwnd, msgs[i].message, msgs[i].wParam, msgs[i].lParam ); + TRACE( "%u messages generated\n", count ); - return (msg_count > 0); + return count > 0; } /*********************************************************************** * ImmProcessKey(IMM32.@) * ( Undocumented, called from user32.dll ) */ -BOOL WINAPI ImmProcessKey(HWND hwnd, HKL hKL, UINT vKey, LPARAM lKeyData, DWORD unknown) +BOOL WINAPI ImmProcessKey( HWND hwnd, HKL hkl, UINT vkey, LPARAM lparam, DWORD unknown ) { - InputContextData *data; - HIMC imc = ImmGetContext(hwnd); + struct imc *imc; + struct ime *ime; BYTE state[256]; + BOOL ret; - TRACE("%p %p %x %x %lx\n",hwnd, hKL, vKey, (UINT)lKeyData, unknown); - - if (!(data = get_imc_data( imc ))) return FALSE; + TRACE( "hwnd %p, hkl %p, vkey %#x, lparam %#Ix, unknown %#lx\n", hwnd, hkl, vkey, lparam, unknown ); - /* Make sure we are inputting to the correct keyboard */ - if (data->immKbd->hkl != hKL) - { - ImmHkl *new_hkl = IMM_GetImmHkl(hKL); - if (new_hkl) - { - data->immKbd->pImeSelect(imc, FALSE); - data->immKbd->uSelected--; - data->immKbd = new_hkl; - data->immKbd->pImeSelect(imc, TRUE); - data->immKbd->uSelected++; - } - else - return FALSE; - } + if (hkl != GetKeyboardLayout( 0 )) return FALSE; + if (!(imc = get_imc_data( ImmGetContext( hwnd ) ))) return FALSE; + if (!(ime = imc_select_ime( imc ))) return FALSE; - if (!data->immKbd->hIME || !data->immKbd->pImeProcessKey) - return FALSE; + GetKeyboardState( state ); - GetKeyboardState(state); - if (data->immKbd->pImeProcessKey(imc, vKey, lKeyData, state)) - { - data->lastVK = vKey; - return TRUE; - } + ret = ime->pImeProcessKey( imc->handle, vkey, lparam, state ); + imc->vkey = ret ? vkey : VK_PROCESSKEY; - data->lastVK = VK_PROCESSKEY; - return FALSE; + return ret; } /*********************************************************************** @@ -3106,10 +3160,25 @@ BOOL WINAPI ImmDisableTextFrameService(DWORD idThread) * ImmEnumInputContext(IMM32.@) */ -BOOL WINAPI ImmEnumInputContext(DWORD idThread, IMCENUMPROC lpfn, LPARAM lParam) +BOOL WINAPI ImmEnumInputContext( DWORD thread, IMCENUMPROC callback, LPARAM lparam ) { - FIXME("Stub\n"); - return FALSE; + HIMC buffer[256]; + NTSTATUS status; + UINT i, size; + + TRACE( "thread %lu, callback %p, lparam %#Ix\n", thread, callback, lparam ); + + if ((status = NtUserBuildHimcList( thread, ARRAY_SIZE(buffer), buffer, &size ))) + { + RtlSetLastWin32Error( RtlNtStatusToDosError( status ) ); + WARN( "NtUserBuildHimcList returned %#lx\n", status ); + return FALSE; + } + + if (size == ARRAY_SIZE(buffer)) FIXME( "NtUserBuildHimcList returned %u handles\n", size ); + for (i = 0; i < size; i++) if (!callback( buffer[i], lparam )) return FALSE; + + return TRUE; } /*********************************************************************** @@ -3131,12 +3200,6 @@ BOOL WINAPI ImmDisableLegacyIME(void) return TRUE; } -static HWND get_ui_window(HKL hkl) -{ - ImmHkl *immHkl = IMM_GetImmHkl(hkl); - return immHkl->UIWnd; -} - static BOOL is_ime_ui_msg(UINT msg) { switch (msg) @@ -3167,16 +3230,30 @@ static BOOL is_ime_ui_msg(UINT msg) static LRESULT ime_internal_msg( WPARAM wparam, LPARAM lparam) { - HWND hwnd = (HWND)lparam; + HWND hwnd; HIMC himc; switch (wparam) { case IME_INTERNAL_ACTIVATE: + hwnd = (HWND)lparam; + himc = NtUserGetWindowInputContext( hwnd ); + ImmSetActiveContext( hwnd, himc, TRUE ); + set_ime_ui_window_himc( himc ); + break; case IME_INTERNAL_DEACTIVATE: - himc = ImmGetContext(hwnd); - ImmSetActiveContext(hwnd, himc, wparam == IME_INTERNAL_ACTIVATE); - ImmReleaseContext(hwnd, himc); + hwnd = (HWND)lparam; + himc = NtUserGetWindowInputContext( hwnd ); + ImmSetActiveContext( hwnd, himc, FALSE ); + break; + case IME_INTERNAL_HKL_ACTIVATE: + ImmEnumInputContext( 0, enum_activate_layout, 0 ); + if (!(hwnd = get_ime_ui_window())) break; + SendMessageW( hwnd, WM_IME_SELECT, TRUE, lparam ); + break; + case IME_INTERNAL_HKL_DEACTIVATE: + if (!(hwnd = get_ime_ui_window())) break; + SendMessageW( hwnd, WM_IME_SELECT, FALSE, lparam ); break; default: FIXME("wparam = %Ix\n", wparam); @@ -3204,7 +3281,10 @@ static void init_messages(void) LRESULT WINAPI __wine_ime_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, BOOL ansi) { - HWND uiwnd; + HWND ui_hwnd; + + TRACE( "hwnd %p, msg %s, wparam %#Ix, lparam %#Ix, ansi %u\n", + hwnd, debugstr_wm_ime(msg), wparam, lparam, ansi ); switch (msg) { @@ -3226,12 +3306,12 @@ LRESULT WINAPI __wine_ime_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lp if (is_ime_ui_msg(msg)) { - if ((uiwnd = get_ui_window(NtUserGetKeyboardLayout(0)))) + if ((ui_hwnd = get_ime_ui_window())) { if (ansi) - return SendMessageA(uiwnd, msg, wparam, lparam); + return SendMessageA(ui_hwnd, msg, wparam, lparam); else - return SendMessageW(uiwnd, msg, wparam, lparam); + return SendMessageW(ui_hwnd, msg, wparam, lparam); } return FALSE; } diff --git a/dlls/imm32/imm32.spec b/dlls/imm32/imm32.spec index 70b8aef3a95..47b3916c822 100644 --- a/dlls/imm32/imm32.spec +++ b/dlls/imm32/imm32.spec @@ -1,4 +1,4 @@ -@ stub ImmActivateLayout +@ stdcall ImmActivateLayout(long) @ stdcall ImmAssociateContext(long long) @ stdcall ImmAssociateContextEx(long long long) @ stdcall ImmConfigureIMEA(long long long ptr) @@ -18,7 +18,7 @@ @ stdcall ImmEnumRegisterWordW(long ptr wstr long wstr ptr) @ stdcall ImmEscapeA(long long long ptr) @ stdcall ImmEscapeW(long long long ptr) -@ stub ImmFreeLayout +@ stdcall ImmFreeLayout(long) @ stdcall ImmGenerateMessage(ptr) @ stdcall ImmGetCandidateListA(long long ptr long) @ stdcall ImmGetCandidateListCountA(long ptr) @@ -66,7 +66,7 @@ @ stdcall ImmIsIME(long) @ stdcall ImmIsUIMessageA(long long long long) @ stdcall ImmIsUIMessageW(long long long long) -@ stub ImmLoadIME +@ stdcall ImmLoadIME(long) @ stub ImmLoadLayout @ stub ImmLockClientImc @ stdcall ImmLockIMC(long) diff --git a/dlls/imm32/imm_private.h b/dlls/imm32/imm_private.h new file mode 100644 index 00000000000..4cc4acda6c1 --- /dev/null +++ b/dlls/imm32/imm_private.h @@ -0,0 +1,76 @@ +/* + * Copyright 2023 Rémi Bernon for CodeWeavers + * + * 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 + */ + +#include +#include + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "wingdi.h" +#include "winuser.h" +#include "winnls.h" +#include "winreg.h" + +#include "imm.h" +#include "immdev.h" +#include "ntuser.h" +#include "objbase.h" + +#include "wine/debug.h" +#include "wine/list.h" + +extern HMODULE imm32_module; + +/* MSIME messages */ +extern UINT WM_MSIME_SERVICE; +extern UINT WM_MSIME_RECONVERTOPTIONS; +extern UINT WM_MSIME_MOUSE; +extern UINT WM_MSIME_RECONVERTREQUEST; +extern UINT WM_MSIME_RECONVERT; +extern UINT WM_MSIME_QUERYPOSITION; +extern UINT WM_MSIME_DOCUMENTFEED; + +static const char *debugstr_wm_ime( UINT msg ) +{ + switch (msg) + { + case WM_IME_STARTCOMPOSITION: return "WM_IME_STARTCOMPOSITION"; + case WM_IME_ENDCOMPOSITION: return "WM_IME_ENDCOMPOSITION"; + case WM_IME_COMPOSITION: return "WM_IME_COMPOSITION"; + case WM_IME_SETCONTEXT: return "WM_IME_SETCONTEXT"; + case WM_IME_NOTIFY: return "WM_IME_NOTIFY"; + case WM_IME_CONTROL: return "WM_IME_CONTROL"; + case WM_IME_COMPOSITIONFULL: return "WM_IME_COMPOSITIONFULL"; + case WM_IME_SELECT: return "WM_IME_SELECT"; + case WM_IME_CHAR: return "WM_IME_CHAR"; + case WM_IME_REQUEST: return "WM_IME_REQUEST"; + case WM_IME_KEYDOWN: return "WM_IME_KEYDOWN"; + case WM_IME_KEYUP: return "WM_IME_KEYUP"; + default: + if (msg == WM_MSIME_SERVICE) return "WM_MSIME_SERVICE"; + else if (msg == WM_MSIME_RECONVERTOPTIONS) return "WM_MSIME_RECONVERTOPTIONS"; + else if (msg == WM_MSIME_MOUSE) return "WM_MSIME_MOUSE"; + else if (msg == WM_MSIME_RECONVERTREQUEST) return "WM_MSIME_RECONVERTREQUEST"; + else if (msg == WM_MSIME_RECONVERT) return "WM_MSIME_RECONVERT"; + else if (msg == WM_MSIME_QUERYPOSITION) return "WM_MSIME_QUERYPOSITION"; + else if (msg == WM_MSIME_DOCUMENTFEED) return "WM_MSIME_DOCUMENTFEED"; + return wine_dbg_sprintf( "%#x", msg ); + } +} diff --git a/dlls/imm32/tests/Makefile.in b/dlls/imm32/tests/Makefile.in index d0881429e34..ee4999f2855 100644 --- a/dlls/imm32/tests/Makefile.in +++ b/dlls/imm32/tests/Makefile.in @@ -1,5 +1,8 @@ TESTDLL = imm32.dll -IMPORTS = imm32 ole32 user32 +IMPORTS = imm32 ole32 user32 advapi32 -C_SRCS = \ +SOURCES = \ + ime_wrapper.c \ + ime_wrapper.rc \ + ime_wrapper.spec \ imm32.c diff --git a/dlls/imm32/tests/ime_test.h b/dlls/imm32/tests/ime_test.h new file mode 100644 index 00000000000..fda8065276d --- /dev/null +++ b/dlls/imm32/tests/ime_test.h @@ -0,0 +1,56 @@ +/* + * Copyright 2023 Rémi Bernon for CodeWeavers + * + * 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 + */ + +#ifndef __WINE_IME_TEST_H +#define __WINE_IME_TEST_H + +#include +#include + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "winuser.h" +#include "wingdi.h" + +#include "imm.h" +#include "immdev.h" + +struct ime_functions +{ + BOOL (*WINAPI pImeConfigure)(HKL,HWND,DWORD,void *); + DWORD (*WINAPI pImeConversionList)(HIMC,const WCHAR *,CANDIDATELIST *,DWORD,UINT); + BOOL (*WINAPI pImeDestroy)(UINT); + UINT (*WINAPI pImeEnumRegisterWord)(REGISTERWORDENUMPROCW,const WCHAR *,DWORD,const WCHAR *,void *); + LRESULT (*WINAPI pImeEscape)(HIMC,UINT,void *); + DWORD (*WINAPI pImeGetImeMenuItems)(HIMC,DWORD,DWORD,IMEMENUITEMINFOW *,IMEMENUITEMINFOW *,DWORD); + UINT (*WINAPI pImeGetRegisterWordStyle)(UINT,STYLEBUFW *); + BOOL (*WINAPI pImeInquire)(IMEINFO *,WCHAR *,DWORD); + BOOL (*WINAPI pImeProcessKey)(HIMC,UINT,LPARAM,BYTE *); + BOOL (*WINAPI pImeRegisterWord)(const WCHAR *,DWORD,const WCHAR *); + BOOL (*WINAPI pImeSelect)(HIMC,BOOL); + BOOL (*WINAPI pImeSetActiveContext)(HIMC,BOOL); + BOOL (*WINAPI pImeSetCompositionString)(HIMC,DWORD,const void *,DWORD,const void *,DWORD); + UINT (*WINAPI pImeToAsciiEx)(UINT,UINT,BYTE *,TRANSMSGLIST *,UINT,HIMC); + BOOL (*WINAPI pImeUnregisterWord)(const WCHAR *,DWORD,const WCHAR *); + BOOL (*WINAPI pNotifyIME)(HIMC,DWORD,DWORD,DWORD); + BOOL (*WINAPI pDllMain)(HINSTANCE,DWORD,void *); +}; + +#endif /* __WINE_IME_TEST_H */ diff --git a/dlls/imm32/tests/ime_wrapper.c b/dlls/imm32/tests/ime_wrapper.c new file mode 100644 index 00000000000..d8a03499549 --- /dev/null +++ b/dlls/imm32/tests/ime_wrapper.c @@ -0,0 +1,142 @@ +/* + * Copyright 2023 Rémi Bernon for CodeWeavers + * + * 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 + */ + +#include "ime_test.h" + +struct ime_functions ime_functions = {0}; + +BOOL WINAPI ImeConfigure( HKL hkl, HWND hwnd, DWORD mode, void *data ) +{ + if (!ime_functions.pImeConfigure) return FALSE; + return ime_functions.pImeConfigure( hkl, hwnd, mode, data ); +} + +DWORD WINAPI ImeConversionList( HIMC himc, const WCHAR *source, CANDIDATELIST *dest, DWORD dest_len, UINT flag ) +{ + if (!ime_functions.pImeConversionList) return 0; + return ime_functions.pImeConversionList( himc, source, dest, dest_len, flag ); +} + +BOOL WINAPI ImeDestroy( UINT force ) +{ + if (!ime_functions.pImeDestroy) return FALSE; + return ime_functions.pImeDestroy( force ); +} + +UINT WINAPI ImeEnumRegisterWord( REGISTERWORDENUMPROCW proc, const WCHAR *reading, DWORD style, + const WCHAR *string, void *data ) +{ + if (!ime_functions.pImeEnumRegisterWord) return 0; + return ime_functions.pImeEnumRegisterWord( proc, reading, style, string, data ); +} + +LRESULT WINAPI ImeEscape( HIMC himc, UINT escape, void *data ) +{ + if (!ime_functions.pImeEscape) return 0; + return ime_functions.pImeEscape( himc, escape, data ); +} + +DWORD WINAPI ImeGetImeMenuItems( HIMC himc, DWORD flags, DWORD type, IMEMENUITEMINFOW *parent, + IMEMENUITEMINFOW *menu, DWORD size ) +{ + if (!ime_functions.pImeGetImeMenuItems) return 0; + return ime_functions.pImeGetImeMenuItems( himc, flags, type, parent, menu, size ); +} + +UINT WINAPI ImeGetRegisterWordStyle( UINT item, STYLEBUFW *style_buf ) +{ + if (!ime_functions.pImeGetRegisterWordStyle) return 0; + return ime_functions.pImeGetRegisterWordStyle( item, style_buf ); +} + +BOOL WINAPI ImeInquire( IMEINFO *info, WCHAR *ui_class, DWORD flags ) +{ + if (!ime_functions.pImeInquire) return FALSE; + return ime_functions.pImeInquire( info, ui_class, flags ); +} + +BOOL WINAPI ImeProcessKey( HIMC himc, UINT vkey, LPARAM key_data, BYTE *key_state ) +{ + if (!ime_functions.pImeProcessKey) return FALSE; + return ime_functions.pImeProcessKey( himc, vkey, key_data, key_state ); +} + +BOOL WINAPI ImeRegisterWord( const WCHAR *reading, DWORD style, const WCHAR *string ) +{ + if (!ime_functions.pImeRegisterWord) return FALSE; + return ime_functions.pImeRegisterWord( reading, style, string ); +} + +BOOL WINAPI ImeSelect( HIMC himc, BOOL select ) +{ + if (!ime_functions.pImeSelect) return FALSE; + return ime_functions.pImeSelect( himc, select ); +} + +BOOL WINAPI ImeSetActiveContext( HIMC himc, BOOL flag ) +{ + if (!ime_functions.pImeSetActiveContext) return FALSE; + return ime_functions.pImeSetActiveContext( himc, flag ); +} + +BOOL WINAPI ImeSetCompositionString( HIMC himc, DWORD index, const void *comp, DWORD comp_len, + const void *read, DWORD read_len ) +{ + if (!ime_functions.pImeSetCompositionString) return FALSE; + return ime_functions.pImeSetCompositionString( himc, index, comp, comp_len, read, read_len ); +} + +UINT WINAPI ImeToAsciiEx( UINT vkey, UINT scan_code, BYTE *key_state, TRANSMSGLIST *msgs, UINT state, HIMC himc ) +{ + if (!ime_functions.pImeToAsciiEx) return 0; + return ime_functions.pImeToAsciiEx( vkey, scan_code, key_state, msgs, state, himc ); +} + +BOOL WINAPI ImeUnregisterWord( const WCHAR *reading, DWORD style, const WCHAR *string ) +{ + if (!ime_functions.pImeUnregisterWord) return FALSE; + return ime_functions.pImeUnregisterWord( reading, style, string ); +} + +BOOL WINAPI NotifyIME( HIMC himc, DWORD action, DWORD index, DWORD value ) +{ + if (!ime_functions.pNotifyIME) return FALSE; + return ime_functions.pNotifyIME( himc, action, index, value ); +} + +BOOL WINAPI DllMain( HINSTANCE instance, DWORD reason, LPVOID reserved ) +{ + static HMODULE module; + + switch (reason) + { + case DLL_PROCESS_ATTACH: + if (!(module = GetModuleHandleW( L"winetest_ime.dll" ))) return TRUE; + ime_functions = *(struct ime_functions *)GetProcAddress( module, "ime_functions" ); + if (!ime_functions.pDllMain) return TRUE; + return ime_functions.pDllMain( instance, reason, reserved ); + + case DLL_PROCESS_DETACH: + if (module == instance) return TRUE; + if (!ime_functions.pDllMain) return TRUE; + ime_functions.pDllMain( instance, reason, reserved ); + memset( &ime_functions, 0, sizeof(ime_functions) ); + } + + return TRUE; +} diff --git a/dlls/msmpeg2vdec/main.c b/dlls/imm32/tests/ime_wrapper.rc similarity index 55% rename from dlls/msmpeg2vdec/main.c rename to dlls/imm32/tests/ime_wrapper.rc index 348d3d405b4..e71d81f4fc9 100644 --- a/dlls/msmpeg2vdec/main.c +++ b/dlls/imm32/tests/ime_wrapper.rc @@ -16,33 +16,13 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -#include -#include -#include +#pragma makedep testdll -#include "windef.h" -#include "winbase.h" +#define WINE_LANGID 047f /* LANG_INVARIANT/SUBLANG_DEFAULT */ +#define WINE_FILETYPE VFT_DRV +#define WINE_FILESUBTYPE VFT2_DRV_INPUTMETHOD +#define WINE_FILENAME "ime_wrapper" +#define WINE_FILENAME_STR "ime_wrapper.dll" +#define WINE_FILEDESCRIPTION_STR "WineTest IME" -#include "wine/debug.h" - -WINE_DEFAULT_DEBUG_CHANNEL(mfplat); - -BOOL WINAPI DllMain( HINSTANCE instance, DWORD reason, void *reserved ) -{ - TRACE( "instance %p, reason %#lx, reserved %p\n", instance, reason, reserved ); - - switch (reason) - { - case DLL_PROCESS_ATTACH: - { - const char *sgi; - if ((sgi = getenv( "SteamGameId" )) && !strcmp( sgi, "1498570" )) return FALSE; - DisableThreadLibraryCalls( instance ); - break; - } - case DLL_PROCESS_DETACH: - break; - } - - return TRUE; -} +#include "wine/wine_common_ver.rc" diff --git a/dlls/imm32/tests/ime_wrapper.spec b/dlls/imm32/tests/ime_wrapper.spec new file mode 100644 index 00000000000..05a60e84a5d --- /dev/null +++ b/dlls/imm32/tests/ime_wrapper.spec @@ -0,0 +1,17 @@ +@ stdcall ImeConfigure(long long long ptr) +@ stdcall ImeConversionList(long wstr ptr long long) +@ stdcall ImeDestroy(long) +@ stdcall ImeEnumRegisterWord(ptr wstr long wstr ptr) +@ stdcall ImeEscape(long long ptr) +@ stdcall ImeGetImeMenuItems(long long long ptr ptr long) +@ stdcall ImeGetRegisterWordStyle(long ptr) +@ stdcall ImeInquire(ptr wstr wstr) +@ stdcall ImeProcessKey(long long long ptr) +@ stdcall ImeRegisterWord(wstr long wstr) +@ stdcall ImeSelect(long long) +@ stdcall ImeSetActiveContext(long long) +@ stdcall ImeSetCompositionString(long long ptr long ptr long) +@ stdcall ImeToAsciiEx(long long ptr ptr long long) +@ stdcall ImeUnregisterWord(wstr long wstr) +@ stdcall NotifyIME(long long long long) +@ extern -private ime_functions diff --git a/dlls/imm32/tests/imm32.c b/dlls/imm32/tests/imm32.c index 98af84383ba..081a54fd878 100644 --- a/dlls/imm32/tests/imm32.c +++ b/dlls/imm32/tests/imm32.c @@ -18,7 +18,13 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -#include +#include +#include + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" #include "wine/test.h" #include "objbase.h" @@ -27,6 +33,68 @@ #include "imm.h" #include "immdev.h" +#include "ime_test.h" + +static const char *debugstr_wm_ime( UINT msg ) +{ + switch (msg) + { + case WM_IME_STARTCOMPOSITION: return "WM_IME_STARTCOMPOSITION"; + case WM_IME_ENDCOMPOSITION: return "WM_IME_ENDCOMPOSITION"; + case WM_IME_COMPOSITION: return "WM_IME_COMPOSITION"; + case WM_IME_SETCONTEXT: return "WM_IME_SETCONTEXT"; + case WM_IME_NOTIFY: return "WM_IME_NOTIFY"; + case WM_IME_CONTROL: return "WM_IME_CONTROL"; + case WM_IME_COMPOSITIONFULL: return "WM_IME_COMPOSITIONFULL"; + case WM_IME_SELECT: return "WM_IME_SELECT"; + case WM_IME_CHAR: return "WM_IME_CHAR"; + case WM_IME_REQUEST: return "WM_IME_REQUEST"; + case WM_IME_KEYDOWN: return "WM_IME_KEYDOWN"; + case WM_IME_KEYUP: return "WM_IME_KEYUP"; + default: return wine_dbg_sprintf( "%#x", msg ); + } +} + +static const char *debugstr_ok( const char *cond ) +{ + int c, n = 0; + /* skip possible casts */ + while ((c = *cond++)) + { + if (c == '(') n++; + if (!n) break; + if (c == ')') n--; + } + if (!strchr( cond - 1, '(' )) return wine_dbg_sprintf( "got %s", cond - 1 ); + return wine_dbg_sprintf( "%.*s returned", (int)strcspn( cond - 1, "( " ), cond - 1 ); +} + +#define ok_eq( e, r, t, f, ... ) \ + do \ + { \ + t v = (r); \ + ok( v == (e), "%s " f "\n", debugstr_ok( #r ), v, ##__VA_ARGS__ ); \ + } while (0) +#define ok_ne( e, r, t, f, ... ) \ + do \ + { \ + t v = (r); \ + ok( v != (e), "%s " f "\n", debugstr_ok( #r ), v, ##__VA_ARGS__ ); \ + } while (0) +#define ok_wcs( e, r ) \ + do \ + { \ + const WCHAR *v = (r); \ + ok( !wcscmp( v, (e) ), "%s %s\n", debugstr_ok(#r), debugstr_w(v) ); \ + } while (0) +#define ok_str( e, r ) \ + do \ + { \ + const char *v = (r); \ + ok( !strcmp( v, (e) ), "%s %s\n", debugstr_ok(#r), debugstr_a(v) ); \ + } while (0) +#define ok_ret( e, r ) ok_eq( e, r, UINT_PTR, "%Iu, error %ld", GetLastError() ) + BOOL WINAPI ImmSetActiveContext(HWND, HIMC, BOOL); static BOOL (WINAPI *pImmAssociateContextEx)(HWND,HIMC,DWORD); @@ -34,6 +102,144 @@ static UINT (WINAPI *pNtUserAssociateInputContext)(HWND,HIMC,ULONG); static BOOL (WINAPI *pImmIsUIMessageA)(HWND,UINT,WPARAM,LPARAM); static UINT (WINAPI *pSendInput) (UINT, INPUT*, size_t); +extern BOOL WINAPI ImmFreeLayout(HKL); +extern BOOL WINAPI ImmLoadIME(HKL); +extern BOOL WINAPI ImmActivateLayout(HKL); + +#define check_member_( file, line, val, exp, fmt, member ) \ + ok_(file, line)( (val).member == (exp).member, "got " #member " " fmt "\n", (val).member ) +#define check_member( val, exp, fmt, member ) \ + check_member_( __FILE__, __LINE__, val, exp, fmt, member ) + +#define check_member_wstr_( file, line, val, exp, member ) \ + ok_(file, line)( !wcscmp( (val).member, (exp).member ), "got " #member " %s\n", \ + debugstr_w((val).member) ) +#define check_member_wstr( val, exp, member ) \ + check_member_wstr_( __FILE__, __LINE__, val, exp, member ) + +#define check_member_str_( file, line, val, exp, member ) \ + ok_(file, line)( !strcmp( (val).member, (exp).member ), "got " #member " %s\n", \ + debugstr_a((val).member) ) +#define check_member_str( val, exp, member ) \ + check_member_str_( __FILE__, __LINE__, val, exp, member ) + +#define check_member_point_( file, line, val, exp, member ) \ + ok_(file, line)( !memcmp( &(val).member, &(exp).member, sizeof(POINT) ), \ + "got " #member " %s\n", wine_dbgstr_point( &(val).member ) ) +#define check_member_point( val, exp, member ) \ + check_member_point_( __FILE__, __LINE__, val, exp, member ) + +#define check_member_rect_( file, line, val, exp, member ) \ + ok_(file, line)( !memcmp( &(val).member, &(exp).member, sizeof(RECT) ), \ + "got " #member " %s\n", wine_dbgstr_rect( &(val).member ) ) +#define check_member_rect( val, exp, member ) \ + check_member_rect_( __FILE__, __LINE__, val, exp, member ) + +#define check_composition_string( a, b ) check_composition_string_( __LINE__, a, b ) +static void check_composition_string_( int line, COMPOSITIONSTRING *string, const COMPOSITIONSTRING *expect ) +{ + check_member_( __FILE__, line, *string, *expect, "%lu", dwSize ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwCompReadAttrLen ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwCompReadAttrOffset ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwCompReadClauseLen ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwCompReadClauseOffset ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwCompReadStrLen ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwCompReadStrOffset ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwCompAttrLen ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwCompAttrOffset ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwCompClauseLen ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwCompClauseOffset ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwCompStrLen ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwCompStrOffset ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwCursorPos ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwDeltaStart ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwResultReadClauseLen ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwResultReadClauseOffset ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwResultReadStrLen ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwResultReadStrOffset ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwResultClauseLen ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwResultClauseOffset ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwResultStrLen ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwResultStrOffset ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwPrivateSize ); + check_member_( __FILE__, line, *string, *expect, "%lu", dwPrivateOffset ); +} + +#define check_candidate_list( a, b ) check_candidate_list_( __LINE__, a, b, TRUE ) +static void check_candidate_list_( int line, CANDIDATELIST *list, const CANDIDATELIST *expect, BOOL unicode ) +{ + UINT i; + + check_member_( __FILE__, line, *list, *expect, "%lu", dwSize ); + check_member_( __FILE__, line, *list, *expect, "%lu", dwStyle ); + check_member_( __FILE__, line, *list, *expect, "%lu", dwCount ); + check_member_( __FILE__, line, *list, *expect, "%lu", dwSelection ); + check_member_( __FILE__, line, *list, *expect, "%lu", dwPageStart ); + check_member_( __FILE__, line, *list, *expect, "%lu", dwPageSize ); + for (i = 0; i < list->dwCount && i < expect->dwCount; ++i) + { + void *list_str = (BYTE *)list + list->dwOffset[i], *expect_str = (BYTE *)expect + expect->dwOffset[i]; + check_member_( __FILE__, line, *list, *expect, "%lu", dwOffset[i] ); + if (unicode) ok_( __FILE__, line )( !wcscmp( list_str, expect_str ), "got %s\n", debugstr_w(list_str) ); + else ok_( __FILE__, line )( !strcmp( list_str, expect_str ), "got %s\n", debugstr_a(list_str) ); + } +} + +#define check_candidate_form( a, b ) check_candidate_form_( __LINE__, a, b ) +static void check_candidate_form_( int line, CANDIDATEFORM *form, const CANDIDATEFORM *expect ) +{ + check_member_( __FILE__, line, *form, *expect, "%#lx", dwIndex ); + check_member_( __FILE__, line, *form, *expect, "%#lx", dwStyle ); + check_member_point_( __FILE__, line, *form, *expect, ptCurrentPos ); + check_member_rect_( __FILE__, line, *form, *expect, rcArea ); +} + +#define check_composition_form( a, b ) check_composition_form_( __LINE__, a, b ) +static void check_composition_form_( int line, COMPOSITIONFORM *form, const COMPOSITIONFORM *expect ) +{ + check_member_( __FILE__, line, *form, *expect, "%#lx", dwStyle ); + check_member_point_( __FILE__, line, *form, *expect, ptCurrentPos ); + check_member_rect_( __FILE__, line, *form, *expect, rcArea ); +} + +#define check_logfont_w( a, b ) check_logfont_w_( __LINE__, a, b ) +static void check_logfont_w_( int line, LOGFONTW *font, const LOGFONTW *expect ) +{ + check_member_( __FILE__, line, *font, *expect, "%lu", lfHeight ); + check_member_( __FILE__, line, *font, *expect, "%lu", lfWidth ); + check_member_( __FILE__, line, *font, *expect, "%lu", lfEscapement ); + check_member_( __FILE__, line, *font, *expect, "%lu", lfOrientation ); + check_member_( __FILE__, line, *font, *expect, "%lu", lfWeight ); + check_member_( __FILE__, line, *font, *expect, "%u", lfItalic ); + check_member_( __FILE__, line, *font, *expect, "%u", lfUnderline ); + check_member_( __FILE__, line, *font, *expect, "%u", lfStrikeOut ); + check_member_( __FILE__, line, *font, *expect, "%u", lfCharSet ); + check_member_( __FILE__, line, *font, *expect, "%u", lfOutPrecision ); + check_member_( __FILE__, line, *font, *expect, "%u", lfClipPrecision ); + check_member_( __FILE__, line, *font, *expect, "%u", lfQuality ); + check_member_( __FILE__, line, *font, *expect, "%u", lfPitchAndFamily ); + check_member_wstr_( __FILE__, line, *font, *expect, lfFaceName ); +} + +#define check_logfont_a( a, b ) check_logfont_a_( __LINE__, a, b ) +static void check_logfont_a_( int line, LOGFONTA *font, const LOGFONTA *expect ) +{ + check_member_( __FILE__, line, *font, *expect, "%lu", lfHeight ); + check_member_( __FILE__, line, *font, *expect, "%lu", lfWidth ); + check_member_( __FILE__, line, *font, *expect, "%lu", lfEscapement ); + check_member_( __FILE__, line, *font, *expect, "%lu", lfOrientation ); + check_member_( __FILE__, line, *font, *expect, "%lu", lfWeight ); + check_member_( __FILE__, line, *font, *expect, "%u", lfItalic ); + check_member_( __FILE__, line, *font, *expect, "%u", lfUnderline ); + check_member_( __FILE__, line, *font, *expect, "%u", lfStrikeOut ); + check_member_( __FILE__, line, *font, *expect, "%u", lfCharSet ); + check_member_( __FILE__, line, *font, *expect, "%u", lfOutPrecision ); + check_member_( __FILE__, line, *font, *expect, "%u", lfClipPrecision ); + check_member_( __FILE__, line, *font, *expect, "%u", lfQuality ); + check_member_( __FILE__, line, *font, *expect, "%u", lfPitchAndFamily ); + check_member_str_( __FILE__, line, *font, *expect, lfFaceName ); +} + #define DEFINE_EXPECT(func) \ static BOOL expect_ ## func = FALSE, called_ ## func = FALSE, enabled_ ## func = FALSE @@ -66,6 +272,374 @@ static UINT (WINAPI *pSendInput) (UINT, INPUT*, size_t); DEFINE_EXPECT(WM_IME_SETCONTEXT_DEACTIVATE); DEFINE_EXPECT(WM_IME_SETCONTEXT_ACTIVATE); +#define process_messages() process_messages_(0) +static void process_messages_(HWND hwnd) +{ + MSG msg; + + while (PeekMessageA( &msg, hwnd, 0, 0, PM_REMOVE )) + { + TranslateMessage( &msg ); + DispatchMessageA( &msg ); + } +} + +/* try to make sure pending X events have been processed before continuing */ +#define flush_events() flush_events_( 100, 200 ) +static void flush_events_( int min_timeout, int max_timeout ) +{ + DWORD time = GetTickCount() + max_timeout; + MSG msg; + + while (max_timeout > 0) + { + if (MsgWaitForMultipleObjects( 0, NULL, FALSE, min_timeout, QS_ALLINPUT ) == WAIT_TIMEOUT) break; + while (PeekMessageA( &msg, 0, 0, 0, PM_REMOVE )) + { + TranslateMessage( &msg ); + DispatchMessageA( &msg ); + } + max_timeout = time - GetTickCount(); + } +} + +#define ime_trace( msg, ... ) if (winetest_debug > 1) trace( "%04lx:%s " msg, GetCurrentThreadId(), __func__, ## __VA_ARGS__ ) + +static BOOL ImeSelect_init_status; +static BOOL todo_ImeInquire; +DEFINE_EXPECT( ImeInquire ); +static BOOL todo_ImeDestroy; +DEFINE_EXPECT( ImeDestroy ); +DEFINE_EXPECT( ImeEscape ); +DEFINE_EXPECT( ImeEnumRegisterWord ); +DEFINE_EXPECT( ImeRegisterWord ); +DEFINE_EXPECT( ImeGetRegisterWordStyle ); +DEFINE_EXPECT( ImeUnregisterWord ); +static BOOL todo_ImeSetCompositionString; +DEFINE_EXPECT( ImeSetCompositionString ); +static BOOL todo_IME_DLL_PROCESS_ATTACH; +DEFINE_EXPECT( IME_DLL_PROCESS_ATTACH ); +static BOOL todo_IME_DLL_PROCESS_DETACH; +DEFINE_EXPECT( IME_DLL_PROCESS_DETACH ); + +static IMEINFO ime_info; +static UINT ime_count; +static WCHAR ime_path[MAX_PATH]; +static HIMC default_himc; +static HKL default_hkl, wineime_hkl; +static HKL expect_ime = (HKL)(int)0xe020047f; + +enum ime_function +{ + IME_SELECT = 1, + IME_NOTIFY, + IME_PROCESS_KEY, + IME_TO_ASCII_EX, + IME_SET_ACTIVE_CONTEXT, + MSG_IME_UI, + MSG_TEST_WIN, +}; + +struct ime_call +{ + HKL hkl; + HIMC himc; + enum ime_function func; + + WCHAR comp[16]; + WCHAR result[16]; + + union + { + int select; + struct + { + int action; + int index; + int value; + } notify; + struct + { + WORD vkey; + LPARAM lparam; + } process_key; + struct + { + UINT vkey; + UINT vsc; + UINT flags; + } to_ascii_ex; + struct + { + int flag; + } set_active_context; + struct + { + UINT msg; + WPARAM wparam; + LPARAM lparam; + } message; + }; + + BOOL todo; + BOOL broken; + BOOL flaky_himc; + BOOL todo_value; +}; + +struct ime_call empty_sequence[] = {{0}}; +static struct ime_call ime_calls[1024]; +static ULONG ime_call_count; + +#define ok_call( a, b ) ok_call_( __FILE__, __LINE__, a, b ) +static int ok_call_( const char *file, int line, const struct ime_call *expected, const struct ime_call *received ) +{ + int ret; + + if ((ret = expected->func - received->func)) goto done; + /* Wine doesn't allocate HIMC in a deterministic order, ignore them when they are enumerated */ + if (expected->flaky_himc && (ret = !!(UINT_PTR)expected->himc - !!(UINT_PTR)received->himc)) goto done; + if (!expected->flaky_himc && (ret = (UINT_PTR)expected->himc - (UINT_PTR)received->himc)) goto done; + if ((ret = (UINT)(UINT_PTR)expected->hkl - (UINT)(UINT_PTR)received->hkl)) goto done; + switch (expected->func) + { + case IME_SELECT: + if ((ret = expected->select - received->select)) goto done; + break; + case IME_NOTIFY: + if ((ret = expected->notify.action - received->notify.action)) goto done; + if ((ret = expected->notify.index - received->notify.index)) goto done; + if ((ret = expected->notify.value - received->notify.value)) goto done; + break; + case IME_PROCESS_KEY: + if ((ret = expected->process_key.vkey - received->process_key.vkey)) goto done; + if ((ret = expected->process_key.lparam - received->process_key.lparam)) goto done; + break; + case IME_TO_ASCII_EX: + if ((ret = expected->to_ascii_ex.vkey - received->to_ascii_ex.vkey)) goto done; + if ((ret = expected->to_ascii_ex.vsc - received->to_ascii_ex.vsc)) goto done; + if ((ret = expected->to_ascii_ex.flags - received->to_ascii_ex.flags)) goto done; + break; + case IME_SET_ACTIVE_CONTEXT: + if ((ret = expected->set_active_context.flag - received->set_active_context.flag)) goto done; + break; + case MSG_IME_UI: + case MSG_TEST_WIN: + if ((ret = expected->message.msg - received->message.msg)) goto done; + if ((ret = (expected->message.wparam - received->message.wparam))) goto done; + if ((ret = (expected->message.lparam - received->message.lparam))) goto done; + if ((ret = wcscmp( expected->comp, received->comp ))) goto done; + if ((ret = wcscmp( expected->result, received->result ))) goto done; + break; + } + +done: + if (ret && broken( expected->broken )) return ret; + + switch (received->func) + { + case IME_SELECT: + todo_wine_if( expected->todo || expected->todo_value ) + ok_(file, line)( !ret, "got hkl %p, himc %p, IME_SELECT select %u\n", received->hkl, received->himc, received->select ); + return ret; + case IME_NOTIFY: + todo_wine_if( expected->todo || expected->todo_value ) + ok_(file, line)( !ret, "got hkl %p, himc %p, IME_NOTIFY action %#x, index %#x, value %#x\n", + received->hkl, received->himc, received->notify.action, received->notify.index, + received->notify.value ); + return ret; + case IME_PROCESS_KEY: + todo_wine_if( expected->todo || expected->todo_value ) + ok_(file, line)( !ret, "got hkl %p, himc %p, IME_PROCESS_KEY vkey %#x, lparam %#Ix\n", + received->hkl, received->himc, received->process_key.vkey, received->process_key.lparam ); + return ret; + case IME_TO_ASCII_EX: + todo_wine_if( expected->todo || expected->todo_value ) + ok_(file, line)( !ret, "got hkl %p, himc %p, IME_TO_ASCII_EX vkey %#x, vsc %#x, flags %#x\n", + received->hkl, received->himc, received->to_ascii_ex.vkey, received->to_ascii_ex.vsc, + received->to_ascii_ex.flags ); + return ret; + case IME_SET_ACTIVE_CONTEXT: + todo_wine_if( expected->todo || expected->todo_value ) + ok_(file, line)( !ret, "got hkl %p, himc %p, IME_SET_ACTIVE_CONTEXT flag %u\n", received->hkl, received->himc, + received->set_active_context.flag ); + return ret; + case MSG_IME_UI: + todo_wine_if( expected->todo || expected->todo_value ) + ok_(file, line)( !ret, "got hkl %p, himc %p, MSG_IME_UI msg %s, wparam %#Ix, lparam %#Ix\n", received->hkl, + received->himc, debugstr_wm_ime(received->message.msg), received->message.wparam, received->message.lparam ); + return ret; + case MSG_TEST_WIN: + todo_wine_if( expected->todo || expected->todo_value ) + ok_(file, line)( !ret, "got hkl %p, himc %p, MSG_TEST_WIN msg %s, wparam %#Ix, lparam %#Ix, comp %s, result %s\n", received->hkl, + received->himc, debugstr_wm_ime(received->message.msg), received->message.wparam, received->message.lparam, + debugstr_w(received->comp), debugstr_w(received->result) ); + return ret; + } + + switch (expected->func) + { + case IME_SELECT: + todo_wine_if( expected->todo || expected->todo_value ) + ok_(file, line)( !ret, "hkl %p, himc %p, IME_SELECT select %u\n", expected->hkl, expected->himc, expected->select ); + break; + case IME_NOTIFY: + todo_wine_if( expected->todo || expected->todo_value ) + ok_(file, line)( !ret, "hkl %p, himc %p, IME_NOTIFY action %#x, index %#x, value %#x\n", + expected->hkl, expected->himc, expected->notify.action, expected->notify.index, + expected->notify.value ); + break; + case IME_PROCESS_KEY: + todo_wine_if( expected->todo || expected->todo_value ) + ok_(file, line)( !ret, "hkl %p, himc %p, IME_PROCESS_KEY vkey %#x, lparam %#Ix\n", + expected->hkl, expected->himc, expected->process_key.vkey, expected->process_key.lparam ); + break; + case IME_TO_ASCII_EX: + todo_wine_if( expected->todo || expected->todo_value ) + ok_(file, line)( !ret, "hkl %p, himc %p, IME_TO_ASCII_EX vkey %#x, vsc %#x, flags %#x\n", + expected->hkl, expected->himc, expected->to_ascii_ex.vkey, expected->to_ascii_ex.vsc, + expected->to_ascii_ex.flags ); + break; + case IME_SET_ACTIVE_CONTEXT: + todo_wine_if( expected->todo || expected->todo_value ) + ok_(file, line)( !ret, "hkl %p, himc %p, IME_SET_ACTIVE_CONTEXT flag %u\n", expected->hkl, expected->himc, + expected->set_active_context.flag ); + break; + case MSG_IME_UI: + todo_wine_if( expected->todo || expected->todo_value ) + ok_(file, line)( !ret, "hkl %p, himc %p, MSG_IME_UI msg %s, wparam %#Ix, lparam %#Ix\n", expected->hkl, + expected->himc, debugstr_wm_ime(expected->message.msg), expected->message.wparam, expected->message.lparam ); + break; + case MSG_TEST_WIN: + todo_wine_if( expected->todo || expected->todo_value ) + ok_(file, line)( !ret, "hkl %p, himc %p, MSG_TEST_WIN msg %s, wparam %#Ix, lparam %#Ix, comp %s, result %s\n", expected->hkl, + expected->himc, debugstr_wm_ime(expected->message.msg), expected->message.wparam, expected->message.lparam, + debugstr_w(expected->comp), debugstr_w(expected->result) ); + break; + } + + return 0; +} + +#define ok_seq( a ) ok_seq_( __FILE__, __LINE__, a, #a ) +static void ok_seq_( const char *file, int line, const struct ime_call *expected, const char *context ) +{ + const struct ime_call *received = ime_calls; + UINT i = 0, ret; + + while (expected->func || received->func) + { + winetest_push_context( "%u%s%s", i++, !expected->func ? " (spurious)" : "", + !received->func ? " (missing)" : "" ); + ret = ok_call_( file, line, expected, received ); + if (ret && expected->todo && expected->func && + !strcmp( winetest_platform, "wine" )) + expected++; + else if (ret && broken(expected->broken)) + expected++; + else + { + if (expected->func) expected++; + if (received->func) received++; + } + winetest_pop_context(); + } + + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; +} + +static BOOL check_WM_SHOWWINDOW; +static BOOL ignore_WM_IME_NOTIFY; +static BOOL ignore_WM_IME_REQUEST; + +static BOOL ignore_message( UINT msg, WPARAM wparam ) +{ + switch (msg) + { + case WM_IME_NOTIFY: + if (ignore_WM_IME_NOTIFY) return TRUE; + return wparam > IMN_PRIVATE; + case WM_IME_REQUEST: + if (ignore_WM_IME_REQUEST) return TRUE; + return FALSE; + case WM_IME_STARTCOMPOSITION: + case WM_IME_ENDCOMPOSITION: + case WM_IME_COMPOSITION: + case WM_IME_SETCONTEXT: + case WM_IME_CONTROL: + case WM_IME_COMPOSITIONFULL: + case WM_IME_SELECT: + case WM_IME_CHAR: + case 0x287: + case WM_IME_KEYDOWN: + case WM_IME_KEYUP: + return FALSE; + case WM_SHOWWINDOW: + return !check_WM_SHOWWINDOW; + default: + return TRUE; + } +} + +static LRESULT CALLBACK ime_ui_window_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) +{ + struct ime_call call = + { + .hkl = GetKeyboardLayout( 0 ), .himc = (HIMC)GetWindowLongPtrW( hwnd, IMMGWL_IMC ), + .func = MSG_IME_UI, .message = {.msg = msg, .wparam = wparam, .lparam = lparam} + }; + LONG_PTR ptr; + + ime_trace( "hwnd %p, msg %#x, wparam %#Ix, lparam %#Ix\n", hwnd, msg, wparam, lparam ); + + if (ignore_message( msg, wparam )) return DefWindowProcW( hwnd, msg, wparam, lparam ); + + ptr = GetWindowLongPtrW( hwnd, IMMGWL_PRIVATE ); + ok( !ptr, "got IMMGWL_PRIVATE %#Ix\n", ptr ); + + ime_calls[ime_call_count++] = call; + return DefWindowProcW( hwnd, msg, wparam, lparam ); +} + +static LRESULT CALLBACK test_window_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) +{ + struct ime_call call = + { + .hkl = GetKeyboardLayout( 0 ), .himc = ImmGetContext( hwnd ), + .func = MSG_TEST_WIN, .message = {.msg = msg, .wparam = wparam, .lparam = lparam} + }; + + ime_trace( "hwnd %p, msg %#x, wparam %#Ix, lparam %#Ix\n", hwnd, msg, wparam, lparam ); + + if (ignore_message( msg, wparam )) return DefWindowProcW( hwnd, msg, wparam, lparam ); + + if (msg == WM_IME_COMPOSITION) + { + ImmGetCompositionStringW( call.himc, GCS_COMPSTR, call.comp, sizeof(call.comp) ); + ImmGetCompositionStringW( call.himc, GCS_RESULTSTR, call.result, sizeof(call.result) ); + } + + ime_calls[ime_call_count++] = call; + return DefWindowProcW( hwnd, msg, wparam, lparam ); +} + +static WNDCLASSEXW ime_ui_class = +{ + .cbSize = sizeof(WNDCLASSEXW), + .style = CS_IME, + .lpfnWndProc = ime_ui_window_proc, + .cbWndExtra = 2 * sizeof(LONG_PTR), + .lpszClassName = L"WineTestIME", +}; + +static WNDCLASSEXW test_class = +{ + .cbSize = sizeof(WNDCLASSEXW), + .lpfnWndProc = test_window_proc, + .lpszClassName = L"WineTest", +}; + /* * msgspy - record and analyse message traces sent to a certain window */ @@ -206,6 +780,28 @@ static HWND hwnd, child; static HWND get_ime_window(void); +static void load_resource( const WCHAR *name, WCHAR *filename ) +{ + static WCHAR path[MAX_PATH]; + DWORD written; + HANDLE file; + HRSRC res; + void *ptr; + + GetTempPathW( ARRAY_SIZE(path), path ); + GetTempFileNameW( path, name, 0, filename ); + + file = CreateFileW( filename, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0 ); + ok( file != INVALID_HANDLE_VALUE, "failed to create %s, error %lu\n", debugstr_w(filename), GetLastError() ); + + res = FindResourceW( NULL, name, L"TESTDLL" ); + ok( res != 0, "couldn't find resource\n" ); + ptr = LockResource( LoadResource( GetModuleHandleW( NULL ), res ) ); + WriteFile( file, ptr, SizeofResource( GetModuleHandleW( NULL ), res ), &written, NULL ); + ok( written == SizeofResource( GetModuleHandleW( NULL ), res ), "couldn't write resource\n" ); + CloseHandle( file ); +} + static LRESULT WINAPI wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { HWND default_ime_wnd; @@ -469,7 +1065,7 @@ static LRESULT WINAPI test_ime_wnd_proc(HWND hWnd, UINT msg, WPARAM wParam, LPAR hWnd, msg, wParam, lParam); } -static void test_ImmGetCompositionString(void) +static void test_SCS_SETSTR(void) { HIMC imc; static const WCHAR string[] = {'w','i','n','e',0x65e5,0x672c,0x8a9e}; @@ -611,12 +1207,6 @@ static void test_ImmGetCompositionString(void) skip("WM_IME_COMPOSITION(GCS_RESULTSTR) isn't tested\n"); msg_spy_flush_msgs(); } -} - -static void test_ImmSetCompositionString(void) -{ - HIMC imc; - BOOL ret; SetLastError(0xdeadbeef); imc = ImmGetContext(hwnd); @@ -825,248 +1415,438 @@ static void test_NtUserAssociateInputContext(void) ImmReleaseContext(hwnd,imc); } -typedef struct _igc_threadinfo { +struct test_cross_thread_himc_params +{ HWND hwnd; HANDLE event; - HIMC himc; - HIMC u_himc; -} igc_threadinfo; - + HIMC himc[2]; + INPUTCONTEXT *contexts[2]; +}; -static DWORD WINAPI ImmGetContextThreadFunc( LPVOID lpParam) +static DWORD WINAPI test_cross_thread_himc_thread( void *arg ) { - HIMC h1,h2; - HWND hwnd2; - COMPOSITIONFORM cf; - CANDIDATEFORM cdf; - POINT pt; + CANDIDATEFORM candidate = {.dwIndex = 1, .dwStyle = CFS_CANDIDATEPOS}; + struct test_cross_thread_himc_params *params = arg; + COMPOSITIONFORM composition = {0}; + INPUTCONTEXT *contexts[2]; + HIMC himc[2], tmp_himc; + LOGFONTW fontW = {0}; + HWND hwnd, tmp_hwnd; + POINT pos = {0}; MSG msg; - igc_threadinfo *info= (igc_threadinfo*)lpParam; - info->hwnd = CreateWindowExA(WS_EX_CLIENTEDGE, wndcls, "Wine imm32.dll test", - WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, - 240, 120, NULL, NULL, GetModuleHandleW(NULL), NULL); - - h1 = ImmGetContext(hwnd); - ok(info->himc == h1, "hwnd context changed in new thread\n"); - h2 = ImmGetContext(info->hwnd); - ok(h2 != h1, "new hwnd in new thread should have different context\n"); - info->himc = h2; - ImmReleaseContext(hwnd,h1); - - hwnd2 = CreateWindowExA(WS_EX_CLIENTEDGE, wndcls, "Wine imm32.dll test", - WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, - 240, 120, NULL, NULL, GetModuleHandleW(NULL), NULL); - h1 = ImmGetContext(hwnd2); - - ok(h1 == h2, "Windows in same thread should have same default context\n"); - ImmReleaseContext(hwnd2,h1); - ImmReleaseContext(info->hwnd,h2); - DestroyWindow(hwnd2); + hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok_ne( NULL, hwnd, HWND, "%p" ); + himc[0] = ImmGetContext( hwnd ); + ok_ne( NULL, himc[0], HIMC, "%p" ); + contexts[0] = ImmLockIMC( himc[0] ); + ok_ne( NULL, contexts[0], INPUTCONTEXT *, "%p" ); + contexts[0]->hWnd = hwnd; + + tmp_hwnd = CreateWindowW( L"static", NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + tmp_himc = ImmGetContext( tmp_hwnd ); + ok_eq( himc[0], tmp_himc, HIMC, "%p" ); + ok_ret( 1, ImmReleaseContext( tmp_hwnd, tmp_himc ) ); + ok_ret( 1, DestroyWindow( tmp_hwnd ) ); + + himc[1] = ImmCreateContext(); + ok_ne( NULL, himc[1], HIMC, "%p" ); + contexts[1] = ImmLockIMC( himc[1] ); + ok_ne( NULL, contexts[1], INPUTCONTEXT *, "%p" ); + contexts[1]->hWnd = hwnd; + + ok_ret( 1, ImmSetOpenStatus( himc[0], 0xdeadbeef ) ); + ok_ret( 1, ImmSetOpenStatus( himc[1], 0xfeedcafe ) ); + ok_ret( 1, ImmSetCompositionWindow( himc[0], &composition ) ); + ok_ret( 1, ImmSetCompositionWindow( himc[1], &composition ) ); + ok_ret( 1, ImmSetCandidateWindow( himc[0], &candidate ) ); + ok_ret( 1, ImmSetCandidateWindow( himc[1], &candidate ) ); + ok_ret( 1, ImmSetStatusWindowPos( himc[0], &pos ) ); + ok_ret( 1, ImmSetStatusWindowPos( himc[1], &pos ) ); + ok_ret( 1, ImmSetCompositionFontW( himc[0], &fontW ) ); + ok_ret( 1, ImmSetCompositionFontW( himc[1], &fontW ) ); + + params->hwnd = hwnd; + params->himc[0] = himc[0]; + params->himc[1] = himc[1]; + params->contexts[0] = contexts[0]; + params->contexts[1] = contexts[1]; + SetEvent( params->event ); + + while (GetMessageW( &msg, 0, 0, 0 )) + { + TranslateMessage( &msg ); + DispatchMessageW( &msg ); + } - /* priming for later tests */ - ImmSetCompositionWindow(h1, &cf); - ImmSetStatusWindowPos(h1, &pt); - info->u_himc = ImmCreateContext(); - ImmSetOpenStatus(info->u_himc, TRUE); - cdf.dwIndex = 0; - cdf.dwStyle = CFS_CANDIDATEPOS; - cdf.ptCurrentPos.x = 0; - cdf.ptCurrentPos.y = 0; - ImmSetCandidateWindow(info->u_himc, &cdf); + ok_ret( 1, ImmUnlockIMC( himc[0] ) ); + ok_ret( 1, ImmUnlockIMC( himc[1] ) ); - SetEvent(info->event); + ok_ret( 1, ImmDestroyContext( himc[1] ) ); + ok_ret( 1, ImmReleaseContext( hwnd, himc[0] ) ); + ok_ret( 0, DestroyWindow( hwnd ) ); - while(GetMessageW(&msg, 0, 0, 0)) - { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } return 1; } -static void test_ImmThreads(void) +static void test_cross_thread_himc(void) { - HIMC himc, otherHimc, h1; - igc_threadinfo threadinfo; - HANDLE hThread; - DWORD dwThreadId; - BOOL rc; - LOGFONTA lf; - COMPOSITIONFORM cf; - CANDIDATEFORM cdf; - DWORD status, sentence; - POINT pt; + static const WCHAR comp_string[] = L"CompString"; + RECONVERTSTRING reconv = {.dwSize = sizeof(RECONVERTSTRING)}; + struct test_cross_thread_himc_params params; + COMPOSITIONFORM composition = {0}; + DWORD tid, conversion, sentence; + IMECHARPOSITION char_pos = {0}; + CANDIDATEFORM candidate = {0}; + COMPOSITIONSTRING *string; + HIMC himc[2], tmp_himc; + INPUTCONTEXT *tmp_ctx; + LOGFONTW fontW = {0}; + LOGFONTA fontA = {0}; + char buffer[512]; + POINT pos = {0}; + HANDLE thread; + BYTE *dst; + UINT ret; + + himc[0] = ImmGetContext( hwnd ); + ok_ne( NULL, himc[0], HIMC, "%p" ); + ok_ne( NULL, ImmLockIMC( himc[0] ), INPUTCONTEXT *, "%p" ); + ok_ret( 1, ImmUnlockIMC( himc[0] ) ); + + params.event = CreateEventW(NULL, TRUE, FALSE, NULL); + ok_ne( NULL, params.event, HANDLE, "%p" ); + thread = CreateThread( NULL, 0, test_cross_thread_himc_thread, ¶ms, 0, &tid ); + ok_ne( NULL, thread, HANDLE, "%p" ); + WaitForSingleObject( params.event, INFINITE ); + + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + tmp_himc = ImmGetContext( params.hwnd ); + ok_ne( himc[0], tmp_himc, HIMC, "%p" ); + ok_eq( params.himc[0], tmp_himc, HIMC, "%p" ); + ok_ret( 1, ImmReleaseContext( params.hwnd, tmp_himc ) ); + + himc[1] = ImmCreateContext(); + ok_ne( NULL, himc[1], HIMC, "%p" ); + tmp_ctx = ImmLockIMC( himc[1] ); + ok_ne( NULL, tmp_ctx, INPUTCONTEXT *, "%p" ); + + tmp_ctx->hCompStr = ImmReSizeIMCC( tmp_ctx->hCompStr, 512 ); + ok_ne( NULL, tmp_ctx->hCompStr, HIMCC, "%p" ); + string = ImmLockIMCC( tmp_ctx->hCompStr ); + ok_ne( NULL, string, COMPOSITIONSTRING *, "%p" ); + string->dwSize = sizeof(COMPOSITIONSTRING); + string->dwCompStrLen = wcslen( comp_string ); + string->dwCompStrOffset = string->dwSize; + dst = (BYTE *)string + string->dwCompStrOffset; + memcpy( dst, comp_string, string->dwCompStrLen * sizeof(WCHAR) ); + string->dwSize += string->dwCompStrLen * sizeof(WCHAR); + + string->dwCompClauseLen = 2 * sizeof(DWORD); + string->dwCompClauseOffset = string->dwSize; + dst = (BYTE *)string + string->dwCompClauseOffset; + *(DWORD *)(dst + 0 * sizeof(DWORD)) = 0; + *(DWORD *)(dst + 1 * sizeof(DWORD)) = string->dwCompStrLen; + string->dwSize += 2 * sizeof(DWORD); + + string->dwCompAttrLen = string->dwCompStrLen; + string->dwCompAttrOffset = string->dwSize; + dst = (BYTE *)string + string->dwCompAttrOffset; + memset( dst, ATTR_INPUT, string->dwCompStrLen ); + string->dwSize += string->dwCompStrLen; + ok_ret( 0, ImmUnlockIMCC( tmp_ctx->hCompStr ) ); + + ok_ret( 1, ImmUnlockIMC( himc[1] ) ); + + /* ImmLockIMC should succeed with cross thread HIMC */ + + tmp_ctx = ImmLockIMC( params.himc[0] ); + ok_eq( params.contexts[0], tmp_ctx, INPUTCONTEXT *, "%p" ); + ret = ImmGetIMCLockCount( params.himc[0] ); + ok( ret >= 2, "got ret %u\n", ret ); + + tmp_ctx->hCompStr = ImmReSizeIMCC( tmp_ctx->hCompStr, 512 ); + ok_ne( NULL, tmp_ctx->hCompStr, HIMCC, "%p" ); + string = ImmLockIMCC( tmp_ctx->hCompStr ); + ok_ne( NULL, string, COMPOSITIONSTRING *, "%p" ); + string->dwSize = sizeof(COMPOSITIONSTRING); + string->dwCompStrLen = wcslen( comp_string ); + string->dwCompStrOffset = string->dwSize; + dst = (BYTE *)string + string->dwCompStrOffset; + memcpy( dst, comp_string, string->dwCompStrLen * sizeof(WCHAR) ); + string->dwSize += string->dwCompStrLen * sizeof(WCHAR); + + string->dwCompClauseLen = 2 * sizeof(DWORD); + string->dwCompClauseOffset = string->dwSize; + dst = (BYTE *)string + string->dwCompClauseOffset; + *(DWORD *)(dst + 0 * sizeof(DWORD)) = 0; + *(DWORD *)(dst + 1 * sizeof(DWORD)) = string->dwCompStrLen; + string->dwSize += 2 * sizeof(DWORD); + + string->dwCompAttrLen = string->dwCompStrLen; + string->dwCompAttrOffset = string->dwSize; + dst = (BYTE *)string + string->dwCompAttrOffset; + memset( dst, ATTR_INPUT, string->dwCompStrLen ); + string->dwSize += string->dwCompStrLen; + ok_ret( 0, ImmUnlockIMCC( tmp_ctx->hCompStr ) ); + + ok_ret( 1, ImmUnlockIMC( params.himc[0] ) ); + + tmp_ctx = ImmLockIMC( params.himc[1] ); + ok_eq( params.contexts[1], tmp_ctx, INPUTCONTEXT *, "%p" ); + ret = ImmGetIMCLockCount( params.himc[1] ); + ok( ret >= 2, "got ret %u\n", ret ); + ok_ret( 1, ImmUnlockIMC( params.himc[1] ) ); + + /* ImmSetActiveContext should succeed with cross thread HIMC */ + + SET_ENABLE( WM_IME_SETCONTEXT_DEACTIVATE, TRUE ); + SET_ENABLE( WM_IME_SETCONTEXT_ACTIVATE, TRUE ); + + SET_EXPECT( WM_IME_SETCONTEXT_ACTIVATE ); + ok_ret( 1, ImmSetActiveContext( hwnd, params.himc[0], TRUE ) ); + CHECK_CALLED( WM_IME_SETCONTEXT_ACTIVATE ); + + SET_EXPECT( WM_IME_SETCONTEXT_DEACTIVATE ); + ok_ret( 1, ImmSetActiveContext( hwnd, params.himc[0], FALSE ) ); + CHECK_CALLED( WM_IME_SETCONTEXT_DEACTIVATE ); + + SET_ENABLE( WM_IME_SETCONTEXT_DEACTIVATE, FALSE ); + SET_ENABLE( WM_IME_SETCONTEXT_ACTIVATE, FALSE ); + + /* ImmSetOpenStatus should fail with cross thread HIMC */ + + ok_ret( 1, ImmSetOpenStatus( himc[1], 0xdeadbeef ) ); + ok_ret( (int)0xdeadbeef, ImmGetOpenStatus( himc[1] ) ); + + ok_ret( 0, ImmSetOpenStatus( params.himc[0], TRUE ) ); + ok_ret( 0, ImmSetOpenStatus( params.himc[1], TRUE ) ); + ok_ret( (int)0xdeadbeef, ImmGetOpenStatus( params.himc[0] ) ); + ok_ret( (int)0xfeedcafe, ImmGetOpenStatus( params.himc[1] ) ); + ok_ret( 0, ImmSetOpenStatus( params.himc[0], FALSE ) ); + ok_ret( (int)0xdeadbeef, ImmGetOpenStatus( params.himc[0] ) ); + + /* ImmSetConversionStatus should fail with cross thread HIMC */ + + ok_ret( 1, ImmGetConversionStatus( himc[1], &conversion, &sentence ) ); + ok_ret( 1, ImmSetConversionStatus( himc[1], conversion, sentence ) ); + + ok_ret( 1, ImmGetConversionStatus( params.himc[0], &conversion, &sentence ) ); + ok_ret( 1, ImmGetConversionStatus( params.himc[1], &conversion, &sentence ) ); + ok_ret( 0, ImmSetConversionStatus( params.himc[0], conversion, sentence ) ); + ok_ret( 0, ImmSetConversionStatus( params.himc[1], conversion, sentence ) ); + + /* ImmSetCompositionFont(W|A) should fail with cross thread HIMC */ + + ok_ret( 1, ImmSetCompositionFontA( himc[1], &fontA ) ); + ok_ret( 1, ImmGetCompositionFontA( himc[1], &fontA ) ); + ok_ret( 1, ImmSetCompositionFontW( himc[1], &fontW ) ); + ok_ret( 1, ImmGetCompositionFontW( himc[1], &fontW ) ); + + ok_ret( 0, ImmSetCompositionFontA( params.himc[0], &fontA ) ); + ok_ret( 0, ImmSetCompositionFontA( params.himc[1], &fontA ) ); + ok_ret( 1, ImmGetCompositionFontA( params.himc[0], &fontA ) ); + ok_ret( 1, ImmGetCompositionFontA( params.himc[1], &fontA ) ); + ok_ret( 0, ImmSetCompositionFontW( params.himc[0], &fontW ) ); + ok_ret( 0, ImmSetCompositionFontW( params.himc[1], &fontW ) ); + ok_ret( 1, ImmGetCompositionFontW( params.himc[0], &fontW ) ); + ok_ret( 1, ImmGetCompositionFontW( params.himc[1], &fontW ) ); + + /* ImmRequestMessage(W|A) should fail with cross thread HIMC */ + + ok_ret( 0, ImmRequestMessageW( himc[1], IMR_COMPOSITIONFONT, (LPARAM)&fontW ) ); + ok_ret( 0, ImmRequestMessageA( himc[1], IMR_COMPOSITIONFONT, (LPARAM)&fontA ) ); + + ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_COMPOSITIONFONT, (LPARAM)&fontW ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_COMPOSITIONFONT, (LPARAM)&fontA ) ); + ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_COMPOSITIONFONT, (LPARAM)&fontW ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_COMPOSITIONFONT, (LPARAM)&fontA ) ); + + ok_seq( empty_sequence ); + + /* ImmSetCompositionString(W|A) should fail with cross thread HIMC */ + + ok_ret( 10, ImmGetCompositionStringA( himc[1], GCS_COMPSTR, buffer, sizeof(buffer) ) ); + ok_ret( 20, ImmGetCompositionStringW( himc[1], GCS_COMPSTR, buffer, sizeof(buffer) ) ); + ok_ret( 1, ImmSetCompositionStringA( himc[1], SCS_SETSTR, "a", 2, NULL, 0 ) ); + ok_ret( 1, ImmSetCompositionStringW( himc[1], SCS_SETSTR, L"a", 4, NULL, 0 ) ); - himc = ImmGetContext(hwnd); - threadinfo.event = CreateEventA(NULL, TRUE, FALSE, NULL); - threadinfo.himc = himc; - hThread = CreateThread(NULL, 0, ImmGetContextThreadFunc, &threadinfo, 0, &dwThreadId ); - WaitForSingleObject(threadinfo.event, INFINITE); + ok_ret( 0, ImmSetCompositionStringA( params.himc[0], SCS_SETSTR, "a", 2, NULL, 0 ) ); + ok_ret( 0, ImmSetCompositionStringA( params.himc[1], SCS_SETSTR, "a", 2, NULL, 0 ) ); + ok_ret( 0, ImmSetCompositionStringW( params.himc[0], SCS_SETSTR, L"a", 4, NULL, 0 ) ); + ok_ret( 0, ImmSetCompositionStringW( params.himc[1], SCS_SETSTR, L"a", 4, NULL, 0 ) ); + ok_ret( 10, ImmGetCompositionStringA( params.himc[0], GCS_COMPSTR, buffer, sizeof(buffer) ) ); + ok_ret( 0, ImmGetCompositionStringA( params.himc[1], GCS_COMPSTR, buffer, sizeof(buffer) ) ); + ok_ret( 20, ImmGetCompositionStringW( params.himc[0], GCS_COMPSTR, buffer, sizeof(buffer) ) ); + ok_ret( 0, ImmGetCompositionStringW( params.himc[1], GCS_COMPSTR, buffer, sizeof(buffer) ) ); - otherHimc = ImmGetContext(threadinfo.hwnd); + /* ImmRequestMessage(W|A) should fail with cross thread HIMC */ - ok(himc != otherHimc, "Windows from other threads should have different himc\n"); - ok(otherHimc == threadinfo.himc, "Context from other thread should not change in main thread\n"); + ok_ret( 0, ImmRequestMessageW( himc[1], IMR_RECONVERTSTRING, 0 ) ); + ok_ret( 0, ImmRequestMessageW( himc[1], IMR_RECONVERTSTRING, (LPARAM)&reconv ) ); + ok_ret( 0, ImmRequestMessageA( himc[1], IMR_RECONVERTSTRING, 0 ) ); + ok_ret( 0, ImmRequestMessageA( himc[1], IMR_RECONVERTSTRING, (LPARAM)&reconv ) ); - SET_ENABLE(WM_IME_SETCONTEXT_DEACTIVATE, TRUE); - SET_ENABLE(WM_IME_SETCONTEXT_ACTIVATE, TRUE); - SET_EXPECT(WM_IME_SETCONTEXT_ACTIVATE); - rc = ImmSetActiveContext(hwnd, otherHimc, TRUE); - ok(rc, "ImmSetActiveContext failed\n"); - CHECK_CALLED(WM_IME_SETCONTEXT_ACTIVATE); - SET_EXPECT(WM_IME_SETCONTEXT_DEACTIVATE); - rc = ImmSetActiveContext(hwnd, otherHimc, FALSE); - ok(rc, "ImmSetActiveContext failed\n"); - CHECK_CALLED(WM_IME_SETCONTEXT_DEACTIVATE); - SET_ENABLE(WM_IME_SETCONTEXT_DEACTIVATE, FALSE); - SET_ENABLE(WM_IME_SETCONTEXT_ACTIVATE, FALSE); + ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_RECONVERTSTRING, 0 ) ); + ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_RECONVERTSTRING, (LPARAM)&reconv ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_RECONVERTSTRING, 0 ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_RECONVERTSTRING, (LPARAM)&reconv ) ); + ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_RECONVERTSTRING, 0 ) ); + ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_RECONVERTSTRING, (LPARAM)&reconv ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_RECONVERTSTRING, 0 ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_RECONVERTSTRING, (LPARAM)&reconv ) ); + + ok_ret( 0, ImmRequestMessageW( himc[1], IMR_DOCUMENTFEED, 0 ) ); + ok_ret( 0, ImmRequestMessageW( himc[1], IMR_DOCUMENTFEED, (LPARAM)&reconv ) ); + ok_ret( 0, ImmRequestMessageA( himc[1], IMR_DOCUMENTFEED, 0 ) ); + ok_ret( 0, ImmRequestMessageA( himc[1], IMR_DOCUMENTFEED, (LPARAM)&reconv ) ); + + ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_DOCUMENTFEED, 0 ) ); + ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_DOCUMENTFEED, (LPARAM)&reconv ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_DOCUMENTFEED, 0 ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_DOCUMENTFEED, (LPARAM)&reconv ) ); + ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_DOCUMENTFEED, 0 ) ); + ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_DOCUMENTFEED, (LPARAM)&reconv ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_DOCUMENTFEED, 0 ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_DOCUMENTFEED, (LPARAM)&reconv ) ); + + ok_ret( 0, ImmRequestMessageW( himc[1], IMR_CONFIRMRECONVERTSTRING, (LPARAM)&reconv ) ); + ok_ret( 0, ImmRequestMessageA( himc[1], IMR_CONFIRMRECONVERTSTRING, (LPARAM)&reconv ) ); + + ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_CONFIRMRECONVERTSTRING, (LPARAM)&reconv ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_CONFIRMRECONVERTSTRING, (LPARAM)&reconv ) ); + ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_CONFIRMRECONVERTSTRING, (LPARAM)&reconv ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_CONFIRMRECONVERTSTRING, (LPARAM)&reconv ) ); + + ok_seq( empty_sequence ); + + /* ImmSetCompositionWindow should fail with cross thread HIMC */ + + ok_ret( 1, ImmSetCompositionWindow( himc[1], &composition ) ); + ok_ret( 1, ImmGetCompositionWindow( himc[1], &composition ) ); + + ok_ret( 0, ImmSetCompositionWindow( params.himc[0], &composition ) ); + ok_ret( 0, ImmSetCompositionWindow( params.himc[1], &composition ) ); + ok_ret( 1, ImmGetCompositionWindow( params.himc[0], &composition ) ); + ok_ret( 1, ImmGetCompositionWindow( params.himc[1], &composition ) ); + + /* ImmRequestMessage(W|A) should fail with cross thread HIMC */ + + ok_ret( 0, ImmRequestMessageW( himc[1], IMR_COMPOSITIONWINDOW, (LPARAM)&composition ) ); + ok_ret( 0, ImmRequestMessageA( himc[1], IMR_COMPOSITIONWINDOW, (LPARAM)&composition ) ); + + ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_COMPOSITIONWINDOW, (LPARAM)&composition ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_COMPOSITIONWINDOW, (LPARAM)&composition ) ); + ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_COMPOSITIONWINDOW, (LPARAM)&composition ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_COMPOSITIONWINDOW, (LPARAM)&composition ) ); + + ok_seq( empty_sequence ); - h1 = ImmAssociateContext(hwnd,otherHimc); - ok(h1 == NULL, "Should fail to be able to Associate a default context from a different thread\n"); - h1 = ImmGetContext(hwnd); - ok(h1 == himc, "Context for window should remain unchanged\n"); - ImmReleaseContext(hwnd,h1); - - h1 = ImmAssociateContext(hwnd, threadinfo.u_himc); - ok (h1 == NULL, "Should fail to associate a context from a different thread\n"); - h1 = ImmGetContext(hwnd); - ok(h1 == himc, "Context for window should remain unchanged\n"); - ImmReleaseContext(hwnd,h1); - - h1 = ImmAssociateContext(threadinfo.hwnd, threadinfo.u_himc); - ok (h1 == NULL, "Should fail to associate a context from a different thread into a window from that thread.\n"); - h1 = ImmGetContext(threadinfo.hwnd); - ok(h1 == threadinfo.himc, "Context for window should remain unchanged\n"); - ImmReleaseContext(threadinfo.hwnd,h1); - - /* OpenStatus */ - rc = ImmSetOpenStatus(himc, TRUE); - ok(rc != 0, "ImmSetOpenStatus failed\n"); - rc = ImmGetOpenStatus(himc); - ok(rc != 0, "ImmGetOpenStatus failed\n"); - rc = ImmSetOpenStatus(himc, FALSE); - ok(rc != 0, "ImmSetOpenStatus failed\n"); - rc = ImmGetOpenStatus(himc); - ok(rc == 0, "ImmGetOpenStatus failed\n"); - - rc = ImmSetOpenStatus(otherHimc, TRUE); - ok(rc == 0, "ImmSetOpenStatus should fail\n"); - rc = ImmSetOpenStatus(threadinfo.u_himc, TRUE); - ok(rc == 0, "ImmSetOpenStatus should fail\n"); - rc = ImmGetOpenStatus(otherHimc); - ok(rc == 0, "ImmGetOpenStatus failed\n"); - rc = ImmGetOpenStatus(threadinfo.u_himc); - ok (rc == 1 || broken(rc == 0), "ImmGetOpenStatus should return 1\n"); - rc = ImmSetOpenStatus(otherHimc, FALSE); - ok(rc == 0, "ImmSetOpenStatus should fail\n"); - rc = ImmGetOpenStatus(otherHimc); - ok(rc == 0, "ImmGetOpenStatus failed\n"); - - /* CompositionFont */ - rc = ImmGetCompositionFontA(himc, &lf); - ok(rc != 0, "ImmGetCompositionFont failed\n"); - rc = ImmSetCompositionFontA(himc, &lf); - ok(rc != 0, "ImmSetCompositionFont failed\n"); - - rc = ImmGetCompositionFontA(otherHimc, &lf); - ok(rc != 0 || broken(rc == 0), "ImmGetCompositionFont failed\n"); - rc = ImmGetCompositionFontA(threadinfo.u_himc, &lf); - ok(rc != 0 || broken(rc == 0), "ImmGetCompositionFont user himc failed\n"); - rc = ImmSetCompositionFontA(otherHimc, &lf); - ok(rc == 0, "ImmSetCompositionFont should fail\n"); - rc = ImmSetCompositionFontA(threadinfo.u_himc, &lf); - ok(rc == 0, "ImmSetCompositionFont should fail\n"); - - /* CompositionWindow */ - rc = ImmSetCompositionWindow(himc, &cf); - ok(rc != 0, "ImmSetCompositionWindow failed\n"); - rc = ImmGetCompositionWindow(himc, &cf); - ok(rc != 0, "ImmGetCompositionWindow failed\n"); - - rc = ImmSetCompositionWindow(otherHimc, &cf); - ok(rc == 0, "ImmSetCompositionWindow should fail\n"); - rc = ImmSetCompositionWindow(threadinfo.u_himc, &cf); - ok(rc == 0, "ImmSetCompositionWindow should fail\n"); - rc = ImmGetCompositionWindow(otherHimc, &cf); - ok(rc != 0 || broken(rc == 0), "ImmGetCompositionWindow failed\n"); - rc = ImmGetCompositionWindow(threadinfo.u_himc, &cf); - ok(rc != 0 || broken(rc == 0), "ImmGetCompositionWindow failed\n"); - - /* ConversionStatus */ - rc = ImmGetConversionStatus(himc, &status, &sentence); - ok(rc != 0, "ImmGetConversionStatus failed\n"); - rc = ImmSetConversionStatus(himc, status, sentence); - ok(rc != 0, "ImmSetConversionStatus failed\n"); - - rc = ImmGetConversionStatus(otherHimc, &status, &sentence); - ok(rc != 0 || broken(rc == 0), "ImmGetConversionStatus failed\n"); - rc = ImmGetConversionStatus(threadinfo.u_himc, &status, &sentence); - ok(rc != 0 || broken(rc == 0), "ImmGetConversionStatus failed\n"); - rc = ImmSetConversionStatus(otherHimc, status, sentence); - ok(rc == 0, "ImmSetConversionStatus should fail\n"); - rc = ImmSetConversionStatus(threadinfo.u_himc, status, sentence); - ok(rc == 0, "ImmSetConversionStatus should fail\n"); - - /* StatusWindowPos */ - rc = ImmSetStatusWindowPos(himc, &pt); - ok(rc != 0, "ImmSetStatusWindowPos failed\n"); - rc = ImmGetStatusWindowPos(himc, &pt); - ok(rc != 0, "ImmGetStatusWindowPos failed\n"); - - rc = ImmSetStatusWindowPos(otherHimc, &pt); - ok(rc == 0, "ImmSetStatusWindowPos should fail\n"); - rc = ImmSetStatusWindowPos(threadinfo.u_himc, &pt); - ok(rc == 0, "ImmSetStatusWindowPos should fail\n"); - rc = ImmGetStatusWindowPos(otherHimc, &pt); - ok(rc != 0 || broken(rc == 0), "ImmGetStatusWindowPos failed\n"); - rc = ImmGetStatusWindowPos(threadinfo.u_himc, &pt); - ok(rc != 0 || broken(rc == 0), "ImmGetStatusWindowPos failed\n"); - - h1 = ImmAssociateContext(threadinfo.hwnd, NULL); - ok (h1 == otherHimc, "ImmAssociateContext cross thread with NULL should work\n"); - h1 = ImmGetContext(threadinfo.hwnd); - ok (h1 == NULL, "CrossThread window context should be NULL\n"); - h1 = ImmAssociateContext(threadinfo.hwnd, h1); - ok (h1 == NULL, "Resetting cross thread context should fail\n"); - h1 = ImmGetContext(threadinfo.hwnd); - ok (h1 == NULL, "CrossThread window context should still be NULL\n"); - - rc = ImmDestroyContext(threadinfo.u_himc); - ok (rc == 0, "ImmDestroyContext Cross Thread should fail\n"); - - /* Candidate Window */ - rc = ImmGetCandidateWindow(himc, 0, &cdf); - ok (rc == 0, "ImmGetCandidateWindow should fail\n"); - cdf.dwIndex = 0; - cdf.dwStyle = CFS_CANDIDATEPOS; - cdf.ptCurrentPos.x = 0; - cdf.ptCurrentPos.y = 0; - rc = ImmSetCandidateWindow(himc, &cdf); - ok (rc == 1, "ImmSetCandidateWindow should succeed\n"); - rc = ImmGetCandidateWindow(himc, 0, &cdf); - ok (rc == 1, "ImmGetCandidateWindow should succeed\n"); - - rc = ImmGetCandidateWindow(otherHimc, 0, &cdf); - ok (rc == 0, "ImmGetCandidateWindow should fail\n"); - rc = ImmSetCandidateWindow(otherHimc, &cdf); - ok (rc == 0, "ImmSetCandidateWindow should fail\n"); - rc = ImmGetCandidateWindow(threadinfo.u_himc, 0, &cdf); - ok (rc == 1 || broken( rc == 0), "ImmGetCandidateWindow should succeed\n"); - rc = ImmSetCandidateWindow(threadinfo.u_himc, &cdf); - ok (rc == 0, "ImmSetCandidateWindow should fail\n"); - - ImmReleaseContext(threadinfo.hwnd,otherHimc); - ImmReleaseContext(hwnd,himc); - - SendMessageA(threadinfo.hwnd, WM_CLOSE, 0, 0); - rc = PostThreadMessageA(dwThreadId, WM_QUIT, 1, 0); - ok(rc == 1, "PostThreadMessage should succeed\n"); - WaitForSingleObject(hThread, INFINITE); - CloseHandle(hThread); - - himc = ImmGetContext(GetDesktopWindow()); - ok(himc == NULL, "Should not be able to get himc from other process window\n"); + /* ImmSetCandidateWindow should fail with cross thread HIMC */ + + ok_ret( 1, ImmSetCandidateWindow( himc[1], &candidate ) ); + ok_ret( 1, ImmGetCandidateWindow( himc[1], 0, &candidate ) ); + + ok_ret( 1, ImmGetCandidateWindow( params.himc[0], 1, &candidate ) ); + ok_ret( 1, ImmGetCandidateWindow( params.himc[1], 1, &candidate ) ); + ok_ret( 0, ImmSetCandidateWindow( params.himc[0], &candidate ) ); + ok_ret( 0, ImmSetCandidateWindow( params.himc[1], &candidate ) ); + + /* ImmRequestMessage(W|A) should fail with cross thread HIMC */ + + candidate.dwIndex = -1; + ok_ret( 0, ImmRequestMessageW( himc[1], IMR_CANDIDATEWINDOW, (LPARAM)&candidate ) ); + ok_ret( 0, ImmRequestMessageA( himc[1], IMR_CANDIDATEWINDOW, (LPARAM)&candidate ) ); + + candidate.dwIndex = 0; + ok_ret( 0, ImmRequestMessageW( himc[1], IMR_CANDIDATEWINDOW, (LPARAM)&candidate ) ); + ok_ret( 0, ImmRequestMessageA( himc[1], IMR_CANDIDATEWINDOW, (LPARAM)&candidate ) ); + + ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_CANDIDATEWINDOW, (LPARAM)&candidate ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_CANDIDATEWINDOW, (LPARAM)&candidate ) ); + ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_CANDIDATEWINDOW, (LPARAM)&candidate ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_CANDIDATEWINDOW, (LPARAM)&candidate ) ); + + ok_seq( empty_sequence ); + + /* ImmSetStatusWindowPos should fail with cross thread HIMC */ + + ok_ret( 1, ImmSetStatusWindowPos( himc[1], &pos ) ); + ok_ret( 1, ImmGetStatusWindowPos( himc[1], &pos ) ); + + ok_ret( 0, ImmSetStatusWindowPos( params.himc[0], &pos ) ); + ok_ret( 0, ImmSetStatusWindowPos( params.himc[1], &pos ) ); + ok_ret( 1, ImmGetStatusWindowPos( params.himc[0], &pos ) ); + ok_ret( 1, ImmGetStatusWindowPos( params.himc[1], &pos ) ); + + /* ImmRequestMessage(W|A) should fail with cross thread HIMC */ + + ok_ret( 0, ImmRequestMessageW( himc[1], IMR_QUERYCHARPOSITION, (LPARAM)&char_pos ) ); + ok_ret( 0, ImmRequestMessageA( himc[1], IMR_QUERYCHARPOSITION, (LPARAM)&char_pos ) ); + + ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_QUERYCHARPOSITION, (LPARAM)&char_pos ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_QUERYCHARPOSITION, (LPARAM)&char_pos ) ); + ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_QUERYCHARPOSITION, (LPARAM)&char_pos ) ); + ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_QUERYCHARPOSITION, (LPARAM)&char_pos ) ); + + ok_seq( empty_sequence ); + + /* ImmGenerateMessage should fail with cross thread HIMC */ + + ok_ret( 1, ImmGenerateMessage( himc[1] ) ); + + ok_ret( 0, ImmGenerateMessage( params.himc[0] ) ); + ok_ret( 0, ImmGenerateMessage( params.himc[1] ) ); + + /* ImmAssociateContext should fail with cross thread HWND or HIMC */ + + tmp_himc = ImmAssociateContext( hwnd, params.himc[0] ); + ok_eq( NULL, tmp_himc, HIMC, "%p" ); + tmp_himc = ImmGetContext( hwnd ); + ok_eq( himc[0], tmp_himc, HIMC, "%p" ); + ok_ret( 1, ImmReleaseContext( hwnd, tmp_himc ) ); + + tmp_himc = ImmAssociateContext( hwnd, params.himc[1] ); + ok_eq( NULL, tmp_himc, HIMC, "%p" ); + tmp_himc = ImmGetContext( hwnd ); + ok_eq( himc[0], tmp_himc, HIMC, "%p" ); + ok_ret( 1, ImmReleaseContext( hwnd, tmp_himc ) ); + + tmp_himc = ImmAssociateContext( params.hwnd, params.himc[1] ); + ok_eq( NULL, tmp_himc, HIMC, "%p" ); + tmp_himc = ImmGetContext( params.hwnd ); + ok_eq( params.himc[0], tmp_himc, HIMC, "%p" ); + ok_ret( 1, ImmReleaseContext( params.hwnd, tmp_himc ) ); + + /* ImmAssociateContext should succeed with cross thread HWND and NULL HIMC */ + + tmp_himc = ImmAssociateContext( params.hwnd, NULL ); + ok_eq( params.himc[0], tmp_himc, HIMC, "%p" ); + tmp_himc = ImmGetContext( params.hwnd ); + ok_eq( NULL, tmp_himc, HIMC, "%p" ); + + /* ImmReleaseContext / ImmDestroyContext should fail with cross thread HIMC */ + + ok_ret( 1, ImmReleaseContext( params.hwnd, params.himc[0] ) ); + ok_ret( 0, ImmDestroyContext( params.himc[1] ) ); + + /* ImmGetContext should fail with another process HWND */ + + tmp_himc = ImmGetContext( GetDesktopWindow() ); + ok_eq( NULL, tmp_himc, HIMC, "%p" ); + + ok_ret( 0, SendMessageW( params.hwnd, WM_CLOSE, 0, 0 ) ); + ok_ret( 1, PostThreadMessageW( tid, WM_QUIT, 1, 0 ) ); + ok_ret( 0, WaitForSingleObject( thread, 5000 ) ); + ok_ret( 1, CloseHandle( thread ) ); + ok_ret( 1, CloseHandle( params.event ) ); + + ok_ret( 1, ImmReleaseContext( hwnd, himc[0] ) ); + ok_ret( 1, ImmDestroyContext( himc[1] ) ); } static void test_ImmIsUIMessage(void) @@ -1156,61 +1936,6 @@ static void test_ImmGetContext(void) ok(ImmReleaseContext(hwnd, himc), "ImmReleaseContext failed\n"); } -static void test_ImmGetDescription(void) -{ - HKL hkl; - WCHAR descW[100]; - CHAR descA[100]; - UINT ret, lret; - - /* FIXME: invalid keyboard layouts should not pass */ - ret = ImmGetDescriptionW(NULL, NULL, 0); - ok(!ret, "ImmGetDescriptionW failed, expected 0 received %d.\n", ret); - ret = ImmGetDescriptionA(NULL, NULL, 0); - ok(!ret, "ImmGetDescriptionA failed, expected 0 received %d.\n", ret); - - /* load a language with valid IMM descriptions */ - hkl = GetKeyboardLayout(0); - ok(hkl != 0, "GetKeyboardLayout failed, expected != 0.\n"); - - ret = ImmGetDescriptionW(hkl, NULL, 0); - if(!ret) - { - win_skip("ImmGetDescriptionW is not working for current loaded keyboard.\n"); - return; - } - - SetLastError(0xdeadcafe); - ret = ImmGetDescriptionW(0, NULL, 100); - ok (ret == 0, "ImmGetDescriptionW with 0 hkl should return 0\n"); - ret = GetLastError(); - ok (ret == 0xdeadcafe, "Last Error should remain unchanged\n"); - - ret = ImmGetDescriptionW(hkl, descW, 0); - ok(ret, "ImmGetDescriptionW failed, expected != 0 received 0.\n"); - - lret = ImmGetDescriptionW(hkl, descW, ret + 1); - ok(lret, "ImmGetDescriptionW failed, expected != 0 received 0.\n"); - ok(lret == ret, "ImmGetDescriptionW failed to return the correct amount of data. Expected %d, got %d.\n", ret, lret); - - lret = ImmGetDescriptionA(hkl, descA, ret + 1); - ok(lret, "ImmGetDescriptionA failed, expected != 0 received 0.\n"); - ok(lret == ret, "ImmGetDescriptionA failed to return the correct amount of data. Expected %d, got %d.\n", ret, lret); - - ret /= 2; /* try to copy partially */ - lret = ImmGetDescriptionW(hkl, descW, ret + 1); - ok(lret, "ImmGetDescriptionW failed, expected != 0 received 0.\n"); - ok(lret == ret, "ImmGetDescriptionW failed to return the correct amount of data. Expected %d, got %d.\n", ret, lret); - - lret = ImmGetDescriptionA(hkl, descA, ret + 1); - ok(!lret, "ImmGetDescriptionA should fail\n"); - - ret = ImmGetDescriptionW(hkl, descW, 1); - ok(!ret, "ImmGetDescriptionW failed, expected 0 received %d.\n", ret); - - UnloadKeyboardLayout(hkl); -} - static LRESULT (WINAPI *old_imm_wnd_proc)(HWND, UINT, WPARAM, LPARAM); static LRESULT WINAPI imm_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { @@ -2277,7 +3002,9 @@ static DWORD WINAPI com_initialization_thread(void *arg) static void test_com_initialization(void) { + APTTYPEQUALIFIER qualifier; HANDLE thread; + APTTYPE type; HRESULT hr; HWND wnd; BOOL r; @@ -2332,13 +3059,17 @@ static void test_com_initialization(void) ok(hr == S_OK, "CoInitialize returned %lx\n", hr); test_apttype(APTTYPE_MTA); DestroyWindow(wnd); - test_apttype(-1); + + hr = CoGetApartmentType(&type, &qualifier); + ok(hr == CO_E_NOTINITIALIZED || broken(hr == S_OK) /* w10v22H2 */, + "CoGetApartmentType returned %#lx\n", hr); + test_apttype(hr == S_OK ? APTTYPE_MTA : -1); wnd = CreateWindowA("static", "static", WS_POPUP, 0, 0, 100, 100, 0, 0, 0, 0); ok(wnd != NULL, "CreateWindow failed\n"); - test_apttype(-1); + test_apttype(hr == S_OK ? APTTYPE_MTA : -1); ShowWindow(wnd, SW_SHOW); - test_apttype(APTTYPE_MAINSTA); + test_apttype(hr == S_OK ? APTTYPE_MTA : APTTYPE_MAINSTA); DestroyWindow(wnd); test_apttype(-1); } @@ -2439,27 +3170,4575 @@ static void test_ImmDisableIME(void) ok(!def, "ImmGetDefaultIMEWnd(hwnd) returned %p\n", def); } -START_TEST(imm32) { - if (!is_ime_enabled()) - { - win_skip("IME support not implemented\n"); - return; - } +static BOOL WINAPI ime_ImeConfigure( HKL hkl, HWND hwnd, DWORD mode, void *data ) +{ + ime_trace( "hkl %p, hwnd %p, mode %lu, data %p\n", hkl, hwnd, mode, data ); + ok( 0, "unexpected call\n" ); + return FALSE; +} - test_com_initialization(); +static DWORD WINAPI ime_ImeConversionList( HIMC himc, const WCHAR *source, CANDIDATELIST *dest, + DWORD dest_len, UINT flag ) +{ + ime_trace( "himc %p, source %s, dest %p, dest_len %lu, flag %#x\n", + himc, debugstr_w(source), dest, dest_len, flag ); + ok( 0, "unexpected call\n" ); + return 0; +} + +static BOOL WINAPI ime_ImeDestroy( UINT force ) +{ + ime_trace( "force %u\n", force ); + + todo_wine_if( todo_ImeDestroy ) + CHECK_EXPECT( ImeDestroy ); + + ok( !force, "got force %u\n", force ); + + return TRUE; +} + +static UINT WINAPI ime_ImeEnumRegisterWord( REGISTERWORDENUMPROCW proc, const WCHAR *reading, DWORD style, + const WCHAR *string, void *data ) +{ + ime_trace( "proc %p, reading %s, style %lu, string %s, data %p\n", + proc, debugstr_w(reading), style, debugstr_w(string), data ); + + ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); + CHECK_EXPECT( ImeEnumRegisterWord ); + + if (!style) + { + ok_eq( 0, reading, const void *, "%p" ); + ok_eq( 0, string, const void *, "%p" ); + } + else if (ime_info.fdwProperty & IME_PROP_UNICODE) + { + ok_eq( 0xdeadbeef, style, UINT, "%#x" ); + ok_wcs( L"Reading", reading ); + ok_wcs( L"String", string ); + } + else + { + ok_eq( 0xdeadbeef, style, UINT, "%#x" ); + ok_str( "Reading", (char *)reading ); + ok_str( "String", (char *)string ); + } + + if (style) return proc( reading, style, string, data ); + return 0; +} + +static LRESULT WINAPI ime_ImeEscape( HIMC himc, UINT escape, void *data ) +{ + ime_trace( "himc %p, escape %#x, data %p\n", himc, escape, data ); + + ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); + CHECK_EXPECT( ImeEscape ); + + switch (escape) + { + case IME_ESC_SET_EUDC_DICTIONARY: + if (!data) return 4; + if (ime_info.fdwProperty & IME_PROP_UNICODE) + ok_wcs( L"EscapeIme", data ); + else + ok_str( "EscapeIme", data ); + /* fallthrough */ + case IME_ESC_QUERY_SUPPORT: + case IME_ESC_SEQUENCE_TO_INTERNAL: + case IME_ESC_GET_EUDC_DICTIONARY: + case IME_ESC_MAX_KEY: + case IME_ESC_IME_NAME: + case IME_ESC_HANJA_MODE: + case IME_ESC_GETHELPFILENAME: + if (!data) return 4; + if (ime_info.fdwProperty & IME_PROP_UNICODE) wcscpy( data, L"ImeEscape" ); + else strcpy( data, "ImeEscape" ); + return 4; + } + + ok_eq( 0xdeadbeef, escape, UINT, "%#x" ); + ok_eq( NULL, data, void *, "%p" ); + + return TRUE; +} + +static DWORD WINAPI ime_ImeGetImeMenuItems( HIMC himc, DWORD flags, DWORD type, IMEMENUITEMINFOW *parent, + IMEMENUITEMINFOW *menu, DWORD size ) +{ + ime_trace( "himc %p, flags %#lx, type %lu, parent %p, menu %p, size %#lx\n", + himc, flags, type, parent, menu, size ); + ok( 0, "unexpected call\n" ); + return 0; +} + +static UINT WINAPI ime_ImeGetRegisterWordStyle( UINT item, STYLEBUFW *style ) +{ + ime_trace( "item %u, style %p\n", item, style ); + + ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); + CHECK_EXPECT( ImeGetRegisterWordStyle ); + + if (!style) + ok_eq( 16, item, UINT, "%u" ); + else if (ime_info.fdwProperty & IME_PROP_UNICODE) + { + STYLEBUFW *styleW = style; + styleW->dwStyle = 0xdeadbeef; + wcscpy( styleW->szDescription, L"StyleDescription" ); + } + else + { + STYLEBUFA *styleA = (STYLEBUFA *)style; + styleA->dwStyle = 0xdeadbeef; + strcpy( styleA->szDescription, "StyleDescription" ); + } + + return 0xdeadbeef; +} + +static BOOL WINAPI ime_ImeInquire( IMEINFO *info, WCHAR *ui_class, DWORD flags ) +{ + ime_trace( "info %p, ui_class %p, flags %#lx\n", info, ui_class, flags ); + + todo_wine_if( todo_ImeInquire ) + CHECK_EXPECT( ImeInquire ); + + ok( !!info, "got info %p\n", info ); + ok( !!ui_class, "got ui_class %p\n", ui_class ); + ok( !flags, "got flags %#lx\n", flags ); + + *info = ime_info; + + if (ime_info.fdwProperty & IME_PROP_UNICODE) + wcscpy( ui_class, ime_ui_class.lpszClassName ); + else + WideCharToMultiByte( CP_ACP, 0, ime_ui_class.lpszClassName, -1, + (char *)ui_class, 17, NULL, NULL ); + + return TRUE; +} + +static BOOL WINAPI ime_ImeProcessKey( HIMC himc, UINT vkey, LPARAM lparam, BYTE *state ) +{ + struct ime_call call = + { + .hkl = GetKeyboardLayout( 0 ), .himc = himc, + .func = IME_PROCESS_KEY, .process_key = {.vkey = vkey, .lparam = lparam} + }; + ime_trace( "himc %p, vkey %u, lparam %#Ix, state %p\n", + himc, vkey, lparam, state ); + ime_calls[ime_call_count++] = call; + return LOWORD(lparam); +} + +static BOOL WINAPI ime_ImeRegisterWord( const WCHAR *reading, DWORD style, const WCHAR *string ) +{ + ime_trace( "reading %s, style %lu, string %s\n", debugstr_w(reading), style, debugstr_w(string) ); + + ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); + CHECK_EXPECT( ImeRegisterWord ); + + if (style) ok_eq( 0xdeadbeef, style, UINT, "%#x" ); + if (ime_info.fdwProperty & IME_PROP_UNICODE) + { + if (reading) ok_wcs( L"Reading", reading ); + if (string) ok_wcs( L"String", string ); + } + else + { + if (reading) ok_str( "Reading", (char *)reading ); + if (string) ok_str( "String", (char *)string ); + } + + return FALSE; +} + +static BOOL WINAPI ime_ImeSelect( HIMC himc, BOOL select ) +{ + struct ime_call call = + { + .hkl = GetKeyboardLayout( 0 ), .himc = himc, + .func = IME_SELECT, .select = select + }; + INPUTCONTEXT *ctx; + + ime_trace( "himc %p, select %d\n", himc, select ); + ime_calls[ime_call_count++] = call; + + if (ImeSelect_init_status && select) + { + ctx = ImmLockIMC( himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + ctx->fOpen = ~0; + ctx->fdwConversion = ~0; + ctx->fdwSentence = ~0; + ImmUnlockIMC( himc ); + } + + return TRUE; +} + +static BOOL WINAPI ime_ImeSetActiveContext( HIMC himc, BOOL flag ) +{ + struct ime_call call = + { + .hkl = GetKeyboardLayout( 0 ), .himc = himc, + .func = IME_SET_ACTIVE_CONTEXT, .set_active_context = {.flag = flag} + }; + ime_trace( "himc %p, flag %#x\n", himc, flag ); + ime_calls[ime_call_count++] = call; + return TRUE; +} + +static BOOL WINAPI ime_ImeSetCompositionString( HIMC himc, DWORD index, const void *comp, DWORD comp_len, + const void *read, DWORD read_len ) +{ + ime_trace( "himc %p, index %lu, comp %p, comp_len %lu, read %p, read_len %lu\n", + himc, index, comp, comp_len, read, read_len ); + CHECK_EXPECT( ImeSetCompositionString ); + + ok_eq( expect_ime, GetKeyboardLayout( 0 ), HKL, "%p" ); + ok_ne( default_himc, himc, HIMC, "%p" ); + + if (ime_info.fdwProperty & IME_PROP_UNICODE) + { + switch (index) + { + case SCS_SETSTR: + todo_wine_if( todo_ImeSetCompositionString ) + ok_eq( 22, comp_len, UINT, "%#x" ); + ok_wcs( L"CompString", comp ); + break; + case SCS_CHANGECLAUSE: + { + const UINT *clause = comp; + ok_eq( 8, comp_len, UINT, "%#x" ); + ok_eq( 0, clause[0], UINT, "%#x" ); + todo_wine_if( todo_ImeSetCompositionString ) + ok_eq( 1, clause[1], UINT, "%#x"); + break; + } + case SCS_CHANGEATTR: + { + const BYTE *attr = comp; + todo_wine_if( todo_ImeSetCompositionString && comp_len != 4 ) + ok_eq( 4, comp_len, UINT, "%#x" ); + todo_wine_if( todo_ImeSetCompositionString && attr[0] != 0xcd ) + ok_eq( 0xcd, attr[0], UINT, "%#x" ); + todo_wine_if( todo_ImeSetCompositionString ) + ok_eq( 0xcd, attr[1], UINT, "%#x" ); + break; + } + default: + ok( 0, "unexpected index %#lx\n", index ); + break; + } + } + else + { + switch (index) + { + case SCS_SETSTR: + todo_wine_if( todo_ImeSetCompositionString ) + ok_eq( 11, comp_len, UINT, "%#x" ); + ok_str( "CompString", comp ); + break; + case SCS_CHANGECLAUSE: + { + const UINT *clause = comp; + ok_eq( 8, comp_len, UINT, "%#x" ); + todo_wine_if( todo_ImeSetCompositionString ) + ok_eq( 0, clause[0], UINT, "%#x" ); + todo_wine_if( todo_ImeSetCompositionString ) + ok_eq( 1, clause[1], UINT, "%#x"); + break; + } + case SCS_CHANGEATTR: + { + const BYTE *attr = comp; + todo_wine_if( todo_ImeSetCompositionString && comp_len != 4 ) + ok_eq( 4, comp_len, UINT, "%#x" ); + todo_wine_if( todo_ImeSetCompositionString ) + ok_eq( 0xcd, attr[0], UINT, "%#x" ); + todo_wine_if( todo_ImeSetCompositionString ) + ok_eq( 0xcd, attr[1], UINT, "%#x" ); + break; + } + default: + ok( 0, "unexpected index %#lx\n", index ); + break; + } + } + + ok_eq( NULL, read, const void *, "%p" ); + ok_eq( 0, read_len, UINT, "%#x" ); + + return TRUE; +} + +static UINT WINAPI ime_ImeToAsciiEx( UINT vkey, UINT vsc, BYTE *state, TRANSMSGLIST *msgs, UINT flags, HIMC himc ) +{ + struct ime_call call = + { + .hkl = GetKeyboardLayout( 0 ), .himc = himc, + .func = IME_TO_ASCII_EX, .to_ascii_ex = {.vkey = vkey, .vsc = vsc, .flags = flags} + }; + INPUTCONTEXT *ctx; + UINT count = 0; + + ime_trace( "vkey %#x, vsc %#x, state %p, msgs %p, flags %#x, himc %p\n", + vkey, vsc, state, msgs, flags, himc ); + ime_calls[ime_call_count++] = call; + + ok_ne( NULL, msgs, TRANSMSGLIST *, "%p" ); + todo_wine ok_eq( 256, msgs->uMsgCount, UINT, "%u" ); + + ctx = ImmLockIMC( himc ); + ok_ret( VK_PROCESSKEY, ImmGetVirtualKey( ctx->hWnd ) ); + + if (vsc & 0x200) + { + msgs->TransMsg[0].message = WM_IME_STARTCOMPOSITION; + msgs->TransMsg[0].wParam = 1; + msgs->TransMsg[0].lParam = 0; + count++; + msgs->TransMsg[1].message = WM_IME_ENDCOMPOSITION; + msgs->TransMsg[1].wParam = 1; + msgs->TransMsg[1].lParam = 0; + count++; + } + + if (vsc & 0x400) + { + TRANSMSG *msgs; + + ctx = ImmLockIMC( himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + + ok_ne( NULL, ctx->hMsgBuf, HIMCC, "%p" ); + ok_eq( 0, ctx->dwNumMsgBuf, UINT, "%u" ); + + ctx->hMsgBuf = ImmReSizeIMCC( ctx->hMsgBuf, 64 * sizeof(*msgs) ); + ok_ne( NULL, ctx->hMsgBuf, HIMCC, "%p" ); + + msgs = ImmLockIMCC( ctx->hMsgBuf ); + ok_ne( NULL, msgs, TRANSMSG *, "%p" ); + + msgs[ctx->dwNumMsgBuf].message = WM_IME_STARTCOMPOSITION; + msgs[ctx->dwNumMsgBuf].wParam = 2; + msgs[ctx->dwNumMsgBuf].lParam = 0; + ctx->dwNumMsgBuf++; + msgs[ctx->dwNumMsgBuf].message = WM_IME_ENDCOMPOSITION; + msgs[ctx->dwNumMsgBuf].wParam = 2; + msgs[ctx->dwNumMsgBuf].lParam = 0; + ctx->dwNumMsgBuf++; + + ok_ret( 0, ImmUnlockIMCC( ctx->hMsgBuf ) ); + } + + ok_ret( 1, ImmUnlockIMC( himc ) ); + + if (vsc & 0x800) count = ~0; + return count; +} + +static BOOL WINAPI ime_ImeUnregisterWord( const WCHAR *reading, DWORD style, const WCHAR *string ) +{ + ime_trace( "reading %s, style %lu, string %s\n", debugstr_w(reading), style, debugstr_w(string) ); + + ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); + CHECK_EXPECT( ImeUnregisterWord ); + + if (style) ok_eq( 0xdeadbeef, style, UINT, "%#x" ); + if (ime_info.fdwProperty & IME_PROP_UNICODE) + { + if (reading) ok_wcs( L"Reading", reading ); + if (string) ok_wcs( L"String", string ); + } + else + { + if (reading) ok_str( "Reading", (char *)reading ); + if (string) ok_str( "String", (char *)string ); + } + + return FALSE; +} + +static BOOL WINAPI ime_NotifyIME( HIMC himc, DWORD action, DWORD index, DWORD value ) +{ + struct ime_call call = + { + .hkl = GetKeyboardLayout( 0 ), .himc = himc, + .func = IME_NOTIFY, .notify = {.action = action, .index = index, .value = value} + }; + ime_trace( "himc %p, action %#lx, index %lu, value %lu\n", himc, action, index, value ); + ime_calls[ime_call_count++] = call; + return FALSE; +} + +static BOOL WINAPI ime_DllMain( HINSTANCE instance, DWORD reason, LPVOID reserved ) +{ + ime_trace( "instance %p, reason %lu, reserved %p.\n", instance, reason, reserved ); + + switch (reason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls( instance ); + ime_ui_class.hInstance = instance; + RegisterClassExW( &ime_ui_class ); + todo_wine_if(todo_IME_DLL_PROCESS_ATTACH) + CHECK_EXPECT( IME_DLL_PROCESS_ATTACH ); + break; + + case DLL_PROCESS_DETACH: + UnregisterClassW( ime_ui_class.lpszClassName, instance ); + todo_wine_if(todo_IME_DLL_PROCESS_DETACH) + CHECK_EXPECT( IME_DLL_PROCESS_DETACH ); + break; + } + + return TRUE; +} + +static struct ime_functions ime_functions = +{ + ime_ImeConfigure, + ime_ImeConversionList, + ime_ImeDestroy, + ime_ImeEnumRegisterWord, + ime_ImeEscape, + ime_ImeGetImeMenuItems, + ime_ImeGetRegisterWordStyle, + ime_ImeInquire, + ime_ImeProcessKey, + ime_ImeRegisterWord, + ime_ImeSelect, + ime_ImeSetActiveContext, + ime_ImeSetCompositionString, + ime_ImeToAsciiEx, + ime_ImeUnregisterWord, + ime_NotifyIME, + ime_DllMain, +}; + +static HKL ime_install(void) +{ + WCHAR buffer[MAX_PATH]; + HMODULE module; + DWORD len, ret; + HKEY hkey; + HKL hkl; + + /* IME module gets cached and won't reload from disk as soon as a window has + * loaded it. To workaround the issue we load the module first as a DLL, + * set its function pointers from the test, and later when the cached IME + * gets loaded, read the function pointers from the separately loaded DLL. + */ + + load_resource( L"ime_wrapper.dll", buffer ); + + SetLastError( 0xdeadbeef ); + ret = MoveFileW( buffer, L"c:\\windows\\system32\\winetest_ime.dll" ); + if (!ret) + { + ok( GetLastError() == ERROR_ACCESS_DENIED, "got error %lu\n", GetLastError() ); + win_skip( "Failed to copy DLL to system directory\n" ); + return 0; + } + + module = LoadLibraryW( L"c:\\windows\\system32\\winetest_ime.dll" ); + ok( !!module, "LoadLibraryW failed, error %lu\n", GetLastError() ); + *(struct ime_functions *)GetProcAddress( module, "ime_functions" ) = ime_functions; + + /* install the actual IME module, it will lookup the functions from the DLL */ + load_resource( L"ime_wrapper.dll", buffer ); + + SetLastError( 0xdeadbeef ); + swprintf( ime_path, ARRAY_SIZE(ime_path), L"c:\\windows\\system32\\wine%04x.ime", ime_count++ ); + ret = MoveFileW( buffer, ime_path ); + todo_wine_if( GetLastError() == ERROR_ALREADY_EXISTS ) + ok( ret || broken( !ret ) /* sometimes still in use */, + "MoveFileW failed, error %lu\n", GetLastError() ); + + hkl = ImmInstallIMEW( ime_path, L"WineTest IME" ); + ok( hkl == expect_ime, "ImmInstallIMEW returned %p, error %lu\n", hkl, GetLastError() ); + + swprintf( buffer, ARRAY_SIZE(buffer), L"System\\CurrentControlSet\\Control\\Keyboard Layouts\\%08x", hkl ); + ret = RegOpenKeyW( HKEY_LOCAL_MACHINE, buffer, &hkey ); + ok( !ret, "RegOpenKeyW returned %#lx, error %lu\n", ret, GetLastError() ); + + len = sizeof(buffer); + memset( buffer, 0xcd, sizeof(buffer) ); + ret = RegQueryValueExW( hkey, L"Ime File", NULL, NULL, (BYTE *)buffer, &len ); + ok( !ret, "RegQueryValueExW returned %#lx, error %lu\n", ret, GetLastError() ); + ok( !wcsicmp( buffer, wcsrchr( ime_path, '\\' ) + 1 ), "got Ime File %s\n", debugstr_w(buffer) ); + + len = sizeof(buffer); + memset( buffer, 0xcd, sizeof(buffer) ); + ret = RegQueryValueExW( hkey, L"Layout Text", NULL, NULL, (BYTE *)buffer, &len ); + ok( !ret, "RegQueryValueExW returned %#lx, error %lu\n", ret, GetLastError() ); + ok( !wcscmp( buffer, L"WineTest IME" ), "got Layout Text %s\n", debugstr_w(buffer) ); + + len = sizeof(buffer); + memset( buffer, 0, sizeof(buffer) ); + ret = RegQueryValueExW( hkey, L"Layout File", NULL, NULL, (BYTE *)buffer, &len ); + todo_wine + ok( !ret, "RegQueryValueExW returned %#lx, error %lu\n", ret, GetLastError() ); + todo_wine + ok( !wcscmp( buffer, L"kbdus.dll" ), "got Layout File %s\n", debugstr_w(buffer) ); + + ret = RegCloseKey( hkey ); + ok( !ret, "RegCloseKey returned %#lx, error %lu\n", ret, GetLastError() ); + + return hkl; +} + +static void ime_cleanup( HKL hkl, BOOL free ) +{ + HMODULE module = GetModuleHandleW( L"winetest_ime.dll" ); + WCHAR buffer[MAX_PATH], value[MAX_PATH]; + DWORD i, buffer_len, value_len, ret; + HKEY hkey; + + ret = UnloadKeyboardLayout( hkl ); + todo_wine + ok( ret, "UnloadKeyboardLayout failed, error %lu\n", GetLastError() ); + + if (free) ok_ret( 1, ImmFreeLayout( hkl ) ); + + swprintf( buffer, ARRAY_SIZE(buffer), L"System\\CurrentControlSet\\Control\\Keyboard Layouts\\%08x", hkl ); + ret = RegDeleteKeyW( HKEY_LOCAL_MACHINE, buffer ); + ok( !ret, "RegDeleteKeyW returned %#lx, error %lu\n", ret, GetLastError() ); + + ret = RegOpenKeyW( HKEY_CURRENT_USER, L"Keyboard Layout\\Preload", &hkey ); + ok( !ret, "RegOpenKeyW returned %#lx, error %lu\n", ret, GetLastError() ); + + value_len = ARRAY_SIZE(value); + buffer_len = sizeof(buffer); + for (i = 0; !RegEnumValueW( hkey, i, value, &value_len, NULL, NULL, (void *)buffer, &buffer_len ); i++) + { + value_len = ARRAY_SIZE(value); + buffer_len = sizeof(buffer); + if (hkl != UlongToHandle( wcstoul( buffer, NULL, 16 ) )) continue; + ret = RegDeleteValueW( hkey, value ); + ok( !ret, "RegDeleteValueW returned %#lx, error %lu\n", ret, GetLastError() ); + } + + ret = RegCloseKey( hkey ); + ok( !ret, "RegCloseKey returned %#lx, error %lu\n", ret, GetLastError() ); + + ret = DeleteFileW( ime_path ); + todo_wine_if( GetLastError() == ERROR_ACCESS_DENIED ) + ok( ret || broken( !ret ) /* sometimes still in use */, + "DeleteFileW failed, error %lu\n", GetLastError() ); + + ret = FreeLibrary( module ); + ok( ret, "FreeLibrary failed, error %lu\n", GetLastError() ); + + ret = DeleteFileW( L"c:\\windows\\system32\\winetest_ime.dll" ); + ok( ret, "DeleteFileW failed, error %lu\n", GetLastError() ); +} + +static BOOL CALLBACK enum_get_context( HIMC himc, LPARAM lparam ) +{ + ime_trace( "himc %p\n", himc ); + *(HIMC *)lparam = himc; + return TRUE; +} + +static BOOL CALLBACK enum_find_context( HIMC himc, LPARAM lparam ) +{ + ime_trace( "himc %p\n", himc ); + if (lparam && lparam == (LPARAM)himc) return FALSE; + return TRUE; +} + +static void test_ImmEnumInputContext(void) +{ + HIMC himc; + + ok_ret( 1, ImmEnumInputContext( 0, enum_get_context, (LPARAM)&default_himc ) ); + ok_ret( 1, ImmEnumInputContext( -1, enum_find_context, 0 ) ); + ok_ret( 1, ImmEnumInputContext( GetCurrentThreadId(), enum_find_context, 0 ) ); + + todo_wine + ok_ret( 0, ImmEnumInputContext( 1, enum_find_context, 0 ) ); + todo_wine + ok_ret( 0, ImmEnumInputContext( GetCurrentProcessId(), enum_find_context, 0 ) ); + + himc = ImmCreateContext(); + ok_ne( NULL, himc, HIMC, "%p" ); + ok_ret( 0, ImmEnumInputContext( GetCurrentThreadId(), enum_find_context, (LPARAM)himc ) ); + ok_ret( 1, ImmDestroyContext( himc ) ); + ok_ret( 1, ImmEnumInputContext( GetCurrentThreadId(), enum_find_context, (LPARAM)himc ) ); +} + +static void test_ImmInstallIME(void) +{ + UINT ret; + HKL hkl; + + SET_ENABLE( IME_DLL_PROCESS_ATTACH, TRUE ); + SET_ENABLE( ImeInquire, TRUE ); + SET_ENABLE( ImeDestroy, TRUE ); + SET_ENABLE( IME_DLL_PROCESS_DETACH, TRUE ); + + /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ + ime_info.fdwProperty = IME_PROP_END_UNLOAD; + + if (!(hkl = ime_install())) goto cleanup; + + SET_EXPECT( IME_DLL_PROCESS_ATTACH ); + SET_EXPECT( ImeInquire ); + ret = ImmLoadIME( hkl ); + ok( ret, "ImmLoadIME returned %#x\n", ret ); + CHECK_CALLED( IME_DLL_PROCESS_ATTACH ); + CHECK_CALLED( ImeInquire ); + + ret = ImmLoadIME( hkl ); + ok( ret, "ImmLoadIME returned %#x\n", ret ); + + SET_EXPECT( ImeDestroy ); + SET_EXPECT( IME_DLL_PROCESS_DETACH ); + ret = ImmFreeLayout( hkl ); + ok( ret, "ImmFreeLayout returned %#x\n", ret ); + CHECK_CALLED( ImeDestroy ); + CHECK_CALLED( IME_DLL_PROCESS_DETACH ); + + ret = ImmFreeLayout( hkl ); + ok( ret, "ImmFreeLayout returned %#x\n", ret ); + + ime_info.fdwProperty = 0; + + SET_EXPECT( IME_DLL_PROCESS_ATTACH ); + SET_EXPECT( ImeInquire ); + ret = ImmLoadIME( hkl ); + ok( ret, "ImmLoadIME returned %#x\n", ret ); + CHECK_CALLED( IME_DLL_PROCESS_ATTACH ); + CHECK_CALLED( ImeInquire ); + + ret = ImmLoadIME( hkl ); + ok( ret, "ImmLoadIME returned %#x\n", ret ); + + SET_EXPECT( ImeDestroy ); + SET_EXPECT( IME_DLL_PROCESS_DETACH ); + ret = ImmFreeLayout( hkl ); + ok( ret, "ImmFreeLayout returned %#x\n", ret ); + CHECK_CALLED( ImeDestroy ); + CHECK_CALLED( IME_DLL_PROCESS_DETACH ); + + ret = ImmFreeLayout( hkl ); + ok( ret, "ImmFreeLayout returned %#x\n", ret ); + + ime_cleanup( hkl, FALSE ); + +cleanup: + SET_ENABLE( IME_DLL_PROCESS_ATTACH, FALSE ); + SET_ENABLE( ImeInquire, FALSE ); + SET_ENABLE( ImeDestroy, FALSE ); + SET_ENABLE( IME_DLL_PROCESS_DETACH, FALSE ); +} + +static void test_ImmIsIME(void) +{ + HKL hkl = GetKeyboardLayout( 0 ); + + SET_ENABLE( IME_DLL_PROCESS_ATTACH, TRUE ); + SET_ENABLE( ImeInquire, TRUE ); + SET_ENABLE( ImeDestroy, TRUE ); + SET_ENABLE( IME_DLL_PROCESS_DETACH, TRUE ); + + SetLastError( 0xdeadbeef ); + ok_ret( 0, ImmIsIME( 0 ) ); + ok_ret( 0xdeadbeef, GetLastError() ); + ok_ret( 1, ImmIsIME( hkl ) ); + + /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ + ime_info.fdwProperty = IME_PROP_END_UNLOAD; + + if (!(hkl = wineime_hkl)) goto cleanup; + + todo_ImeInquire = TRUE; + todo_ImeDestroy = TRUE; + todo_IME_DLL_PROCESS_ATTACH = TRUE; + todo_IME_DLL_PROCESS_DETACH = TRUE; + ok_ret( 1, ImmIsIME( hkl ) ); + todo_IME_DLL_PROCESS_ATTACH = FALSE; + todo_IME_DLL_PROCESS_DETACH = FALSE; + todo_ImeInquire = FALSE; + todo_ImeDestroy = FALSE; + +cleanup: + SET_ENABLE( IME_DLL_PROCESS_ATTACH, FALSE ); + SET_ENABLE( ImeInquire, FALSE ); + SET_ENABLE( ImeDestroy, FALSE ); + SET_ENABLE( IME_DLL_PROCESS_DETACH, FALSE ); +} + +static void test_ImmGetProperty(void) +{ + static const IMEINFO expect_ime_info = + { + .fdwProperty = IME_PROP_UNICODE | IME_PROP_AT_CARET, + }; + static const IMEINFO expect_ime_info_0411 = /* MS Japanese IME */ + { + .fdwProperty = IME_PROP_CANDLIST_START_FROM_1 | IME_PROP_UNICODE | IME_PROP_AT_CARET | 0xa, + .fdwConversionCaps = IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_KATAKANA, + .fdwSentenceCaps = IME_SMODE_PLAURALCLAUSE | IME_SMODE_CONVERSATION, + .fdwSCSCaps = SCS_CAP_COMPSTR | SCS_CAP_SETRECONVERTSTRING | SCS_CAP_MAKEREAD, + .fdwSelectCaps = SELECT_CAP_CONVERSION | SELECT_CAP_SENTENCE, + .fdwUICaps = UI_CAP_ROT90, + }; + static const IMEINFO expect_ime_info_0412 = /* MS Korean IME */ + { + .fdwProperty = IME_PROP_CANDLIST_START_FROM_1 | IME_PROP_UNICODE | IME_PROP_AT_CARET | 0xa, + .fdwConversionCaps = IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE, + .fdwSentenceCaps = IME_SMODE_NONE, + .fdwSCSCaps = SCS_CAP_COMPSTR | SCS_CAP_SETRECONVERTSTRING, + .fdwSelectCaps = SELECT_CAP_CONVERSION, + .fdwUICaps = UI_CAP_ROT90, + }; + static const IMEINFO expect_ime_info_0804 = /* MS Chinese IME */ + { + .fdwProperty = IME_PROP_CANDLIST_START_FROM_1 | IME_PROP_UNICODE | IME_PROP_AT_CARET | 0xa, + .fdwConversionCaps = IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE, + .fdwSentenceCaps = IME_SMODE_PLAURALCLAUSE, + .fdwSCSCaps = SCS_CAP_COMPSTR | SCS_CAP_SETRECONVERTSTRING | SCS_CAP_MAKEREAD, + .fdwUICaps = UI_CAP_ROT90, + }; + HKL hkl = GetKeyboardLayout( 0 ); + const IMEINFO *expect; + + SET_ENABLE( ImeInquire, TRUE ); + SET_ENABLE( ImeDestroy, TRUE ); + + SetLastError( 0xdeadbeef ); + ok_ret( 0, ImmGetProperty( 0, 0 ) ); + ok_ret( 0, ImmGetProperty( hkl, 0 ) ); + + if (hkl == (HKL)0x04110411) expect = &expect_ime_info_0411; + else if (hkl == (HKL)0x04120412) expect = &expect_ime_info_0412; + else if (hkl == (HKL)0x08040804) expect = &expect_ime_info_0804; + else expect = &expect_ime_info; + + /* IME_PROP_COMPLETE_ON_UNSELECT seems to be somtimes set on CJK locales IMEs, sometimes not */ + ok_ret( expect->fdwProperty, ImmGetProperty( hkl, IGP_PROPERTY ) & ~IME_PROP_COMPLETE_ON_UNSELECT ); + todo_wine + ok_ret( expect->fdwConversionCaps, ImmGetProperty( hkl, IGP_CONVERSION ) ); + todo_wine + ok_ret( expect->fdwSentenceCaps, ImmGetProperty( hkl, IGP_SENTENCE ) ); + ok_ret( expect->fdwSCSCaps, ImmGetProperty( hkl, IGP_SETCOMPSTR ) ); + todo_wine + ok_ret( expect->fdwSelectCaps, ImmGetProperty( hkl, IGP_SELECT ) ); + ok_ret( IMEVER_0400, ImmGetProperty( hkl, IGP_GETIMEVERSION ) ); + ok_ret( expect->fdwUICaps, ImmGetProperty( hkl, IGP_UI ) ); + todo_wine + ok_ret( 0xdeadbeef, GetLastError() ); + + /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ + ime_info.fdwProperty = IME_PROP_END_UNLOAD; + + if (!(hkl = wineime_hkl)) goto cleanup; + + SET_EXPECT( ImeInquire ); + SET_EXPECT( ImeDestroy ); + ok_ret( 0, ImmGetProperty( hkl, 0 ) ); + CHECK_CALLED( ImeInquire ); + CHECK_CALLED( ImeDestroy ); + + expect = &ime_info; + todo_ImeInquire = TRUE; + todo_ImeDestroy = TRUE; + ok_ret( expect->fdwProperty, ImmGetProperty( hkl, IGP_PROPERTY ) ); + ok_ret( expect->fdwConversionCaps, ImmGetProperty( hkl, IGP_CONVERSION ) ); + ok_ret( expect->fdwSentenceCaps, ImmGetProperty( hkl, IGP_SENTENCE ) ); + ok_ret( expect->fdwSCSCaps, ImmGetProperty( hkl, IGP_SETCOMPSTR ) ); + ok_ret( expect->fdwSelectCaps, ImmGetProperty( hkl, IGP_SELECT ) ); + ok_ret( IMEVER_0400, ImmGetProperty( hkl, IGP_GETIMEVERSION ) ); + ok_ret( expect->fdwUICaps, ImmGetProperty( hkl, IGP_UI ) ); + todo_ImeInquire = FALSE; + called_ImeInquire = FALSE; + todo_ImeDestroy = FALSE; + called_ImeDestroy = FALSE; + +cleanup: + SET_ENABLE( ImeInquire, FALSE ); + SET_ENABLE( ImeDestroy, FALSE ); +} + +static void test_ImmGetDescription(void) +{ + HKL hkl = GetKeyboardLayout( 0 ); + WCHAR bufferW[MAX_PATH]; + char bufferA[MAX_PATH]; + DWORD ret; + + SET_ENABLE( IME_DLL_PROCESS_ATTACH, TRUE ); + SET_ENABLE( ImeInquire, TRUE ); + SET_ENABLE( ImeDestroy, TRUE ); + SET_ENABLE( IME_DLL_PROCESS_DETACH, TRUE ); + + SetLastError( 0xdeadbeef ); + ret = ImmGetDescriptionW( NULL, NULL, 0 ); + ok( !ret, "ImmGetDescriptionW returned %lu\n", ret ); + ret = ImmGetDescriptionA( NULL, NULL, 0 ); + ok( !ret, "ImmGetDescriptionA returned %lu\n", ret ); + ret = ImmGetDescriptionW( NULL, NULL, 100 ); + ok( !ret, "ImmGetDescriptionW returned %lu\n", ret ); + ret = ImmGetDescriptionA( NULL, NULL, 100 ); + ok( !ret, "ImmGetDescriptionA returned %lu\n", ret ); + ret = ImmGetDescriptionW( hkl, bufferW, 100 ); + ok( !ret, "ImmGetDescriptionW returned %lu\n", ret ); + ret = ImmGetDescriptionA( hkl, bufferA, 100 ); + ok( !ret, "ImmGetDescriptionA returned %lu\n", ret ); + ret = GetLastError(); + ok( ret == 0xdeadbeef, "got error %lu\n", ret ); + + if (!(hkl = wineime_hkl)) goto cleanup; + + memset( bufferW, 0xcd, sizeof(bufferW) ); + ret = ImmGetDescriptionW( hkl, bufferW, 2 ); + ok( ret == 1, "ImmGetDescriptionW returned %lu\n", ret ); + ok( !wcscmp( bufferW, L"W" ), "got bufferW %s\n", debugstr_w(bufferW) ); + memset( bufferA, 0xcd, sizeof(bufferA) ); + ret = ImmGetDescriptionA( hkl, bufferA, 2 ); + ok( ret == 0, "ImmGetDescriptionA returned %lu\n", ret ); + ok( !strcmp( bufferA, "" ), "got bufferA %s\n", debugstr_a(bufferA) ); + + memset( bufferW, 0xcd, sizeof(bufferW) ); + ret = ImmGetDescriptionW( hkl, bufferW, 11 ); + ok( ret == 10, "ImmGetDescriptionW returned %lu\n", ret ); + ok( !wcscmp( bufferW, L"WineTest I" ), "got bufferW %s\n", debugstr_w(bufferW) ); + memset( bufferA, 0xcd, sizeof(bufferA) ); + ret = ImmGetDescriptionA( hkl, bufferA, 11 ); + ok( ret == 0, "ImmGetDescriptionA returned %lu\n", ret ); + ok( !strcmp( bufferA, "" ), "got bufferA %s\n", debugstr_a(bufferA) ); + + memset( bufferW, 0xcd, sizeof(bufferW) ); + ret = ImmGetDescriptionW( hkl, bufferW, 12 ); + ok( ret == 11, "ImmGetDescriptionW returned %lu\n", ret ); + ok( !wcscmp( bufferW, L"WineTest IM" ), "got bufferW %s\n", debugstr_w(bufferW) ); + memset( bufferA, 0xcd, sizeof(bufferA) ); + ret = ImmGetDescriptionA( hkl, bufferA, 12 ); + ok( ret == 12, "ImmGetDescriptionA returned %lu\n", ret ); + ok( !strcmp( bufferA, "WineTest IME" ), "got bufferA %s\n", debugstr_a(bufferA) ); + + memset( bufferW, 0xcd, sizeof(bufferW) ); + ret = ImmGetDescriptionW( hkl, bufferW, 13 ); + ok( ret == 12, "ImmGetDescriptionW returned %lu\n", ret ); + ok( !wcscmp( bufferW, L"WineTest IME" ), "got bufferW %s\n", debugstr_w(bufferW) ); + memset( bufferA, 0xcd, sizeof(bufferA) ); + ret = ImmGetDescriptionA( hkl, bufferA, 13 ); + ok( ret == 12, "ImmGetDescriptionA returned %lu\n", ret ); + ok( !strcmp( bufferA, "WineTest IME" ), "got bufferA %s\n", debugstr_a(bufferA) ); + +cleanup: + SET_ENABLE( IME_DLL_PROCESS_ATTACH, FALSE ); + SET_ENABLE( ImeInquire, FALSE ); + SET_ENABLE( ImeDestroy, FALSE ); + SET_ENABLE( IME_DLL_PROCESS_DETACH, FALSE ); +} + +static void test_ImmGetIMEFileName(void) +{ + HKL hkl = GetKeyboardLayout( 0 ); + WCHAR bufferW[MAX_PATH], expectW[16]; + char bufferA[MAX_PATH], expectA[16]; + DWORD ret; + + SET_ENABLE( IME_DLL_PROCESS_ATTACH, TRUE ); + SET_ENABLE( ImeInquire, TRUE ); + SET_ENABLE( ImeDestroy, TRUE ); + SET_ENABLE( IME_DLL_PROCESS_DETACH, TRUE ); + + SetLastError( 0xdeadbeef ); + ret = ImmGetIMEFileNameW( NULL, NULL, 0 ); + ok( !ret, "ImmGetIMEFileNameW returned %lu\n", ret ); + ret = ImmGetIMEFileNameA( NULL, NULL, 0 ); + ok( !ret, "ImmGetIMEFileNameA returned %lu\n", ret ); + ret = ImmGetIMEFileNameW( NULL, NULL, 100 ); + ok( !ret, "ImmGetIMEFileNameW returned %lu\n", ret ); + ret = ImmGetIMEFileNameA( NULL, NULL, 100 ); + ok( !ret, "ImmGetIMEFileNameA returned %lu\n", ret ); + ret = ImmGetIMEFileNameW( hkl, bufferW, 100 ); + ok( !ret, "ImmGetIMEFileNameW returned %lu\n", ret ); + ret = ImmGetIMEFileNameA( hkl, bufferA, 100 ); + ok( !ret, "ImmGetIMEFileNameA returned %lu\n", ret ); + ret = GetLastError(); + ok( ret == 0xdeadbeef, "got error %lu\n", ret ); + + if (!(hkl = wineime_hkl)) goto cleanup; + + memset( bufferW, 0xcd, sizeof(bufferW) ); + ret = ImmGetIMEFileNameW( hkl, bufferW, 2 ); + ok( ret == 1, "ImmGetIMEFileNameW returned %lu\n", ret ); + ok( !wcscmp( bufferW, L"W" ), "got bufferW %s\n", debugstr_w(bufferW) ); + memset( bufferA, 0xcd, sizeof(bufferA) ); + ret = ImmGetIMEFileNameA( hkl, bufferA, 2 ); + ok( ret == 0, "ImmGetIMEFileNameA returned %lu\n", ret ); + ok( !strcmp( bufferA, "" ), "got bufferA %s\n", debugstr_a(bufferA) ); + + swprintf( expectW, ARRAY_SIZE(expectW), L"WINE%04X.I", ime_count - 1 ); + memset( bufferW, 0xcd, sizeof(bufferW) ); + ret = ImmGetIMEFileNameW( hkl, bufferW, 11 ); + ok( ret == 10, "ImmGetIMEFileNameW returned %lu\n", ret ); + ok( !wcscmp( bufferW, expectW ), "got bufferW %s\n", debugstr_w(bufferW) ); + memset( bufferA, 0xcd, sizeof(bufferA) ); + ret = ImmGetIMEFileNameA( hkl, bufferA, 11 ); + ok( ret == 0, "ImmGetIMEFileNameA returned %lu\n", ret ); + ok( !strcmp( bufferA, "" ), "got bufferA %s\n", debugstr_a(bufferA) ); + + swprintf( expectW, ARRAY_SIZE(expectW), L"WINE%04X.IM", ime_count - 1 ); + memset( bufferW, 0xcd, sizeof(bufferW) ); + ret = ImmGetIMEFileNameW( hkl, bufferW, 12 ); + ok( ret == 11, "ImmGetIMEFileNameW returned %lu\n", ret ); + ok( !wcscmp( bufferW, expectW ), "got bufferW %s\n", debugstr_w(bufferW) ); + snprintf( expectA, ARRAY_SIZE(expectA), "WINE%04X.IME", ime_count - 1 ); + memset( bufferA, 0xcd, sizeof(bufferA) ); + ret = ImmGetIMEFileNameA( hkl, bufferA, 12 ); + ok( ret == 12, "ImmGetIMEFileNameA returned %lu\n", ret ); + ok( !strcmp( bufferA, expectA ), "got bufferA %s\n", debugstr_a(bufferA) ); + + swprintf( expectW, ARRAY_SIZE(expectW), L"WINE%04X.IME", ime_count - 1 ); + memset( bufferW, 0xcd, sizeof(bufferW) ); + ret = ImmGetIMEFileNameW( hkl, bufferW, 13 ); + ok( ret == 12, "ImmGetIMEFileNameW returned %lu\n", ret ); + ok( !wcscmp( bufferW, expectW ), "got bufferW %s\n", debugstr_w(bufferW) ); + memset( bufferA, 0xcd, sizeof(bufferA) ); + ret = ImmGetIMEFileNameA( hkl, bufferA, 13 ); + ok( ret == 12, "ImmGetIMEFileNameA returned %lu\n", ret ); + ok( !strcmp( bufferA, expectA ), "got bufferA %s\n", debugstr_a(bufferA) ); + +cleanup: + SET_ENABLE( IME_DLL_PROCESS_ATTACH, FALSE ); + SET_ENABLE( ImeInquire, FALSE ); + SET_ENABLE( ImeDestroy, FALSE ); + SET_ENABLE( IME_DLL_PROCESS_DETACH, FALSE ); +} + +static void test_ImmEscape( BOOL unicode ) +{ + HKL hkl = GetKeyboardLayout( 0 ); + DWORD i, codes[] = + { + IME_ESC_QUERY_SUPPORT, + IME_ESC_SEQUENCE_TO_INTERNAL, + IME_ESC_GET_EUDC_DICTIONARY, + IME_ESC_SET_EUDC_DICTIONARY, + IME_ESC_MAX_KEY, + IME_ESC_IME_NAME, + IME_ESC_HANJA_MODE, + IME_ESC_GETHELPFILENAME, + }; + WCHAR bufferW[512]; + char bufferA[512]; + + SET_ENABLE( ImeEscape, TRUE ); + + winetest_push_context( unicode ? "unicode" : "ansi" ); + + ok_ret( 0, ImmEscapeW( hkl, 0, 0, NULL ) ); + ok_ret( 0, ImmEscapeA( hkl, 0, 0, NULL ) ); + + /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ + ime_info.fdwProperty = IME_PROP_END_UNLOAD; + if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) goto cleanup; + + for (i = 0; i < ARRAY_SIZE(codes); ++i) + { + winetest_push_context( "esc %#lx", codes[i] ); + + SET_EXPECT( ImeEscape ); + ok_ret( 4, ImmEscapeW( hkl, 0, codes[i], NULL ) ); + CHECK_CALLED( ImeEscape ); + + SET_EXPECT( ImeEscape ); + memset( bufferW, 0xcd, sizeof(bufferW) ); + if (codes[i] == IME_ESC_SET_EUDC_DICTIONARY) wcscpy( bufferW, L"EscapeIme" ); + ok_ret( 4, ImmEscapeW( hkl, 0, codes[i], bufferW ) ); + if (unicode || codes[i] == IME_ESC_GET_EUDC_DICTIONARY || codes[i] == IME_ESC_IME_NAME || + codes[i] == IME_ESC_GETHELPFILENAME) + { + ok_wcs( L"ImeEscape", bufferW ); + ok_eq( 0xcdcd, bufferW[10], WORD, "%#x" ); + } + else if (codes[i] == IME_ESC_SET_EUDC_DICTIONARY) + { + ok_wcs( L"EscapeIme", bufferW ); + ok_eq( 0xcdcd, bufferW[10], WORD, "%#x" ); + } + else if (codes[i] == IME_ESC_HANJA_MODE) + { + todo_wine + ok_eq( 0xcdcd, bufferW[0], WORD, "%#x" ); + } + else + { + ok( !memcmp( bufferW, "ImeEscape", 10 ), "got bufferW %s\n", debugstr_w(bufferW) ); + ok_eq( 0xcdcd, bufferW[5], WORD, "%#x" ); + } + CHECK_CALLED( ImeEscape ); + + SET_EXPECT( ImeEscape ); + ok_ret( 4, ImmEscapeA( hkl, 0, codes[i], NULL ) ); + CHECK_CALLED( ImeEscape ); + + SET_EXPECT( ImeEscape ); + memset( bufferA, 0xcd, sizeof(bufferA) ); + if (codes[i] == IME_ESC_SET_EUDC_DICTIONARY) strcpy( bufferA, "EscapeIme" ); + ok_ret( 4, ImmEscapeA( hkl, 0, codes[i], bufferA ) ); + if (!unicode || codes[i] == IME_ESC_GET_EUDC_DICTIONARY || codes[i] == IME_ESC_IME_NAME || + codes[i] == IME_ESC_GETHELPFILENAME) + { + ok_str( "ImeEscape", bufferA ); + ok_eq( 0xcd, bufferA[10], BYTE, "%#x" ); + } + else if (codes[i] == IME_ESC_SET_EUDC_DICTIONARY) + { + ok_str( "EscapeIme", bufferA ); + ok_eq( 0xcd, bufferA[10], BYTE, "%#x" ); + } + else if (codes[i] == IME_ESC_HANJA_MODE) + { + todo_wine + ok_eq( 0xcd, bufferA[0], BYTE, "%#x" ); + } + else + { + ok( !memcmp( bufferA, L"ImeEscape", 10 * sizeof(WCHAR) ), "got bufferA %s\n", debugstr_a(bufferA) ); + ok_eq( 0xcd, bufferA[20], BYTE, "%#x" ); + } + CHECK_CALLED( ImeEscape ); + + winetest_pop_context(); + } + +cleanup: + SET_ENABLE( ImeEscape, FALSE ); + + winetest_pop_context(); +} + +static int CALLBACK enum_register_wordA( const char *reading, DWORD style, const char *string, void *user ) +{ + ime_trace( "reading %s, style %#lx, string %s, user %p\n", debugstr_a(reading), style, debugstr_a(string), user ); + + ok_eq( 0xdeadbeef, style, UINT, "%#x" ); + ok_str( "Reading", reading ); + ok_str( "String", string ); + + return 0xdeadbeef; +} + +static int CALLBACK enum_register_wordW( const WCHAR *reading, DWORD style, const WCHAR *string, void *user ) +{ + ime_trace( "reading %s, style %#lx, string %s, user %p\n", debugstr_w(reading), style, debugstr_w(string), user ); + + ok_eq( 0xdeadbeef, style, UINT, "%#x" ); + ok_wcs( L"Reading", reading ); + ok_wcs( L"String", string ); + + return 0xdeadbeef; +} + +static void test_ImmEnumRegisterWord( BOOL unicode ) +{ + HKL hkl = GetKeyboardLayout( 0 ); + + winetest_push_context( unicode ? "unicode" : "ansi" ); + + SET_ENABLE( ImeEnumRegisterWord, TRUE ); + + SetLastError( 0xdeadbeef ); + ok_ret( 0, ImmEnumRegisterWordW( NULL, enum_register_wordW, NULL, 0, NULL, NULL ) ); + ok_ret( 0, ImmEnumRegisterWordA( NULL, enum_register_wordA, NULL, 0, NULL, NULL ) ); + ok_ret( 0, ImmEnumRegisterWordW( hkl, enum_register_wordW, NULL, 0, NULL, NULL ) ); + ok_ret( 0, ImmEnumRegisterWordA( hkl, enum_register_wordA, NULL, 0, NULL, NULL ) ); + todo_wine + ok_ret( 0xdeadbeef, GetLastError() ); + + /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ + ime_info.fdwProperty = IME_PROP_END_UNLOAD; + if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) goto cleanup; + + SET_EXPECT( ImeEnumRegisterWord ); + ok_ret( 0, ImmEnumRegisterWordW( hkl, enum_register_wordW, NULL, 0, NULL, NULL ) ); + CHECK_CALLED( ImeEnumRegisterWord ); + + SET_EXPECT( ImeEnumRegisterWord ); + ok_ret( 0, ImmEnumRegisterWordA( hkl, enum_register_wordA, NULL, 0, NULL, NULL ) ); + CHECK_CALLED( ImeEnumRegisterWord ); + + SET_EXPECT( ImeEnumRegisterWord ); + ok_ret( 0xdeadbeef, ImmEnumRegisterWordW( hkl, enum_register_wordW, L"Reading", 0xdeadbeef, L"String", NULL ) ); + CHECK_CALLED( ImeEnumRegisterWord ); + + SET_EXPECT( ImeEnumRegisterWord ); + ok_ret( 0xdeadbeef, ImmEnumRegisterWordA( hkl, enum_register_wordA, "Reading", 0xdeadbeef, "String", NULL ) ); + CHECK_CALLED( ImeEnumRegisterWord ); + +cleanup: + SET_ENABLE( ImeEnumRegisterWord, FALSE ); + + winetest_pop_context(); +} + +static void test_ImmRegisterWord( BOOL unicode ) +{ + HKL hkl = GetKeyboardLayout( 0 ); + + SET_ENABLE( ImeRegisterWord, TRUE ); + + winetest_push_context( unicode ? "unicode" : "ansi" ); + + SetLastError( 0xdeadbeef ); + ok_ret( 0, ImmRegisterWordW( NULL, NULL, 0, NULL ) ); + ok_ret( 0, ImmRegisterWordA( NULL, NULL, 0, NULL ) ); + ok_ret( 0, ImmRegisterWordW( hkl, NULL, 0, NULL ) ); + ok_ret( 0, ImmRegisterWordA( hkl, NULL, 0, NULL ) ); + todo_wine + ok_ret( 0xdeadbeef, GetLastError() ); + + /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ + ime_info.fdwProperty = IME_PROP_END_UNLOAD; + if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) goto cleanup; + + SET_EXPECT( ImeRegisterWord ); + ok_ret( 0, ImmRegisterWordW( hkl, NULL, 0, NULL ) ); + CHECK_CALLED( ImeRegisterWord ); + + SET_EXPECT( ImeRegisterWord ); + ok_ret( 0, ImmRegisterWordA( hkl, NULL, 0, NULL ) ); + CHECK_CALLED( ImeRegisterWord ); + + SET_EXPECT( ImeRegisterWord ); + ok_ret( 0, ImmRegisterWordW( hkl, L"Reading", 0, NULL ) ); + CHECK_CALLED( ImeRegisterWord ); + + SET_EXPECT( ImeRegisterWord ); + ok_ret( 0, ImmRegisterWordA( hkl, "Reading", 0, NULL ) ); + CHECK_CALLED( ImeRegisterWord ); + + SET_EXPECT( ImeRegisterWord ); + ok_ret( 0, ImmRegisterWordW( hkl, NULL, 0xdeadbeef, NULL ) ); + CHECK_CALLED( ImeRegisterWord ); + + SET_EXPECT( ImeRegisterWord ); + ok_ret( 0, ImmRegisterWordA( hkl, NULL, 0xdeadbeef, NULL ) ); + CHECK_CALLED( ImeRegisterWord ); + + SET_EXPECT( ImeRegisterWord ); + ok_ret( 0, ImmRegisterWordW( hkl, NULL, 0, L"String" ) ); + CHECK_CALLED( ImeRegisterWord ); + + SET_EXPECT( ImeRegisterWord ); + ok_ret( 0, ImmRegisterWordA( hkl, NULL, 0, "String" ) ); + CHECK_CALLED( ImeRegisterWord ); + +cleanup: + SET_ENABLE( ImeRegisterWord, FALSE ); + + winetest_pop_context(); +} + +static void test_ImmGetRegisterWordStyle( BOOL unicode ) +{ + HKL hkl = GetKeyboardLayout( 0 ); + STYLEBUFW styleW; + STYLEBUFA styleA; + + winetest_push_context( unicode ? "unicode" : "ansi" ); + + SET_ENABLE( ImeGetRegisterWordStyle, TRUE ); + + SetLastError( 0xdeadbeef ); + ok_ret( 0, ImmGetRegisterWordStyleW( NULL, 0, &styleW ) ); + ok_ret( 0, ImmGetRegisterWordStyleA( NULL, 0, &styleA ) ); + ok_ret( 0, ImmGetRegisterWordStyleW( hkl, 0, &styleW ) ); + ok_ret( 0, ImmGetRegisterWordStyleA( hkl, 0, &styleA ) ); + todo_wine + ok_ret( 0xdeadbeef, GetLastError() ); + + /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ + ime_info.fdwProperty = IME_PROP_END_UNLOAD; + if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) goto cleanup; + + if (!strcmp( winetest_platform, "wine" )) goto skip_null; + + SET_EXPECT( ImeGetRegisterWordStyle ); + ok_ret( 0xdeadbeef, ImmGetRegisterWordStyleW( hkl, 16, NULL ) ); + CHECK_CALLED( ImeGetRegisterWordStyle ); + + SET_EXPECT( ImeGetRegisterWordStyle ); + ok_ret( 0xdeadbeef, ImmGetRegisterWordStyleA( hkl, 16, NULL ) ); + CHECK_CALLED( ImeGetRegisterWordStyle ); + +skip_null: + SET_EXPECT( ImeGetRegisterWordStyle ); + memset( &styleW, 0xcd, sizeof(styleW) ); + ok_ret( 0xdeadbeef, ImmGetRegisterWordStyleW( hkl, 1, &styleW ) ); + if (ime_info.fdwProperty & IME_PROP_UNICODE) + { + ok_eq( 0xdeadbeef, styleW.dwStyle, UINT, "%#x" ); + ok_wcs( L"StyleDescription", styleW.szDescription ); + } + else + { + todo_wine + ok_eq( 0xcdcdcdcd, styleW.dwStyle, UINT, "%#x" ); + todo_wine + ok_eq( 0xcdcd, styleW.szDescription[0], WORD, "%#x" ); + } + CHECK_CALLED( ImeGetRegisterWordStyle ); + + SET_EXPECT( ImeGetRegisterWordStyle ); + memset( &styleA, 0xcd, sizeof(styleA) ); + ok_ret( 0xdeadbeef, ImmGetRegisterWordStyleA( hkl, 1, &styleA ) ); + if (ime_info.fdwProperty & IME_PROP_UNICODE) + { + todo_wine + ok_eq( 0xcdcdcdcd, styleA.dwStyle, UINT, "%#x" ); + todo_wine + ok_eq( 0xcd, styleA.szDescription[0], BYTE, "%#x" ); + } + else + { + ok_eq( 0xdeadbeef, styleA.dwStyle, UINT, "%#x" ); + ok_str( "StyleDescription", styleA.szDescription ); + } + CHECK_CALLED( ImeGetRegisterWordStyle ); + +cleanup: + SET_ENABLE( ImeGetRegisterWordStyle, FALSE ); + + winetest_pop_context(); +} + +static void test_ImmUnregisterWord( BOOL unicode ) +{ + HKL hkl = GetKeyboardLayout( 0 ); + + winetest_push_context( unicode ? "unicode" : "ansi" ); + + SET_ENABLE( ImeUnregisterWord, TRUE ); + + SetLastError( 0xdeadbeef ); + ok_ret( 0, ImmUnregisterWordW( NULL, NULL, 0, NULL ) ); + ok_ret( 0, ImmUnregisterWordA( NULL, NULL, 0, NULL ) ); + ok_ret( 0, ImmUnregisterWordW( hkl, NULL, 0, NULL ) ); + ok_ret( 0, ImmUnregisterWordA( hkl, NULL, 0, NULL ) ); + todo_wine + ok_ret( 0xdeadbeef, GetLastError() ); + + /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ + ime_info.fdwProperty = IME_PROP_END_UNLOAD; + if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) goto cleanup; + + SET_EXPECT( ImeUnregisterWord ); + ok_ret( 0, ImmUnregisterWordW( hkl, NULL, 0, NULL ) ); + CHECK_CALLED( ImeUnregisterWord ); + + SET_EXPECT( ImeUnregisterWord ); + ok_ret( 0, ImmUnregisterWordA( hkl, NULL, 0, NULL ) ); + CHECK_CALLED( ImeUnregisterWord ); + + SET_EXPECT( ImeUnregisterWord ); + ok_ret( 0, ImmUnregisterWordW( hkl, L"Reading", 0, NULL ) ); + CHECK_CALLED( ImeUnregisterWord ); + + SET_EXPECT( ImeUnregisterWord ); + ok_ret( 0, ImmUnregisterWordA( hkl, "Reading", 0, NULL ) ); + CHECK_CALLED( ImeUnregisterWord ); + + SET_EXPECT( ImeUnregisterWord ); + ok_ret( 0, ImmUnregisterWordW( hkl, NULL, 0xdeadbeef, NULL ) ); + CHECK_CALLED( ImeUnregisterWord ); + + SET_EXPECT( ImeUnregisterWord ); + ok_ret( 0, ImmUnregisterWordA( hkl, NULL, 0xdeadbeef, NULL ) ); + CHECK_CALLED( ImeUnregisterWord ); + + SET_EXPECT( ImeUnregisterWord ); + ok_ret( 0, ImmUnregisterWordW( hkl, NULL, 0, L"String" ) ); + CHECK_CALLED( ImeUnregisterWord ); + + SET_EXPECT( ImeUnregisterWord ); + ok_ret( 0, ImmUnregisterWordA( hkl, NULL, 0, "String" ) ); + CHECK_CALLED( ImeUnregisterWord ); + +cleanup: + SET_ENABLE( ImeUnregisterWord, FALSE ); + + winetest_pop_context(); +} + +struct ime_windows +{ + HWND ime_hwnd; + HWND ime_ui_hwnd; +}; + +static BOOL CALLBACK enum_thread_ime_windows( HWND hwnd, LPARAM lparam ) +{ + struct ime_windows *params = (void *)lparam; + WCHAR buffer[256]; + UINT ret; + + ime_trace( "hwnd %p, lparam %#Ix\n", hwnd, lparam ); + + ret = RealGetWindowClassW( hwnd, buffer, ARRAY_SIZE(buffer) ); + ok( ret, "RealGetWindowClassW returned %#x\n", ret ); + + if (!wcscmp( buffer, L"IME" )) + { + ok( !params->ime_hwnd, "Found extra IME window %p\n", hwnd ); + params->ime_hwnd = hwnd; + } + if (!wcscmp( buffer, ime_ui_class.lpszClassName )) + { + ok( !params->ime_ui_hwnd, "Found extra IME UI window %p\n", hwnd ); + params->ime_ui_hwnd = hwnd; + } + + return TRUE; +} + +static void test_ImmSetConversionStatus(void) +{ + const struct ime_call set_conversion_status_0_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETCONVERSIONMODE}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCONVERSIONMODE}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCONVERSIONMODE}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETSENTENCEMODE}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETSENTENCEMODE}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETSENTENCEMODE}, + }, + {0}, + }; + const struct ime_call set_conversion_status_1_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0xdeadbeef, .value = IMC_SETCONVERSIONMODE}, + }, + {0}, + }; + const struct ime_call set_conversion_status_2_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETCONVERSIONMODE}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCONVERSIONMODE}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCONVERSIONMODE}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0xfeedcafe, .value = IMC_SETSENTENCEMODE}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETSENTENCEMODE}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETSENTENCEMODE}, + }, + {0}, + }; + DWORD old_conversion, old_sentence, conversion, sentence; + HKL hkl; + INPUTCONTEXT *ctx; + HWND hwnd; + + ok_ret( 0, ImmGetConversionStatus( 0, &old_conversion, &old_sentence ) ); + ok_ret( 1, ImmGetConversionStatus( default_himc, &old_conversion, &old_sentence ) ); + + ctx = ImmLockIMC( default_himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + ok_eq( old_conversion, ctx->fdwConversion, UINT, "%#x" ); + ok_eq( old_sentence, ctx->fdwSentence, UINT, "%#x" ); + + hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + process_messages(); + + ok_ret( 1, ImmGetConversionStatus( default_himc, &conversion, &sentence ) ); + ok_eq( old_conversion, conversion, UINT, "%#x" ); + ok_eq( old_sentence, sentence, UINT, "%#x" ); + ok_eq( old_conversion, ctx->fdwConversion, UINT, "%#x" ); + ok_eq( old_sentence, ctx->fdwSentence, UINT, "%#x" ); + + ok_ret( 1, ImmSetConversionStatus( default_himc, 0, 0 ) ); + ok_ret( 1, ImmGetConversionStatus( default_himc, &conversion, &sentence ) ); + ok_eq( 0, conversion, UINT, "%#x" ); + ok_eq( 0, sentence, UINT, "%#x" ); + ok_eq( 0, ctx->fdwConversion, UINT, "%#x" ); + ok_eq( 0, ctx->fdwSentence, UINT, "%#x" ); + + ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) goto cleanup; + + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_ret( 1, ImmLoadIME( hkl ) ); + process_messages(); + /* initial values are dependent on both old and new IME */ + ok_ret( 1, ImmSetConversionStatus( default_himc, 0, 0 ) ); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + ok_ret( 1, ImmGetConversionStatus( default_himc, &conversion, &sentence ) ); + ok_eq( 0, conversion, UINT, "%#x" ); + ok_eq( 0, sentence, UINT, "%#x" ); + ok_eq( 0, ctx->fdwConversion, UINT, "%#x" ); + ok_eq( 0, ctx->fdwSentence, UINT, "%#x" ); + + ok_seq( empty_sequence ); + ok_ret( 1, ImmSetConversionStatus( default_himc, 0xdeadbeef, 0xfeedcafe ) ); + ok_seq( set_conversion_status_0_seq ); + + ok_ret( 1, ImmGetConversionStatus( default_himc, &conversion, &sentence ) ); + ok_eq( 0xdeadbeef, conversion, UINT, "%#x" ); + ok_eq( 0xfeedcafe, sentence, UINT, "%#x" ); + ok_eq( 0xdeadbeef, ctx->fdwConversion, UINT, "%#x" ); + ok_eq( 0xfeedcafe, ctx->fdwSentence, UINT, "%#x" ); + + ok_ret( 1, ImmSetConversionStatus( default_himc, 0xdeadbeef, 0xfeedcafe ) ); + ok_seq( empty_sequence ); + + ok_ret( 1, ImmGetConversionStatus( default_himc, &conversion, NULL ) ); + ok_eq( 0xdeadbeef, conversion, UINT, "%#x" ); + ok_eq( 0xdeadbeef, ctx->fdwConversion, UINT, "%#x" ); + ok_eq( 0xfeedcafe, ctx->fdwSentence, UINT, "%#x" ); + + ctx->hWnd = 0; + ok_seq( empty_sequence ); + ok_ret( 1, ImmSetConversionStatus( default_himc, 0, 0xfeedcafe ) ); + ok_seq( set_conversion_status_1_seq ); + + ok_ret( 1, ImmGetConversionStatus( default_himc, &conversion, &sentence ) ); + ok_eq( 0, conversion, UINT, "%#x" ); + ok_eq( 0xfeedcafe, sentence, UINT, "%#x" ); + ok_eq( 0, ctx->fdwConversion, UINT, "%#x" ); + ok_eq( 0xfeedcafe, ctx->fdwSentence, UINT, "%#x" ); + + ctx->hWnd = hwnd; + ok_seq( empty_sequence ); + ok_ret( 1, ImmSetConversionStatus( default_himc, ~0, ~0 ) ); + ok_seq( set_conversion_status_2_seq ); + + ok_ret( 1, ImmGetConversionStatus( default_himc, NULL, &sentence ) ); + ok_eq( ~0, sentence, UINT, "%#x" ); + ok_eq( ~0, ctx->fdwConversion, UINT, "%#x" ); + ok_eq( ~0, ctx->fdwSentence, UINT, "%#x" ); + + ok_ret( 1, ImmSetConversionStatus( default_himc, ~0, ~0 ) ); + ok_seq( empty_sequence ); + + ok_ret( 1, ImmGetConversionStatus( default_himc, &conversion, &sentence ) ); + ok_eq( ~0, conversion, UINT, "%#x" ); + ok_eq( ~0, sentence, UINT, "%#x" ); + ok_eq( ~0, ctx->fdwConversion, UINT, "%#x" ); + ok_eq( ~0, ctx->fdwSentence, UINT, "%#x" ); + + /* status is cached and some bits are kept from the previous active IME */ + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + todo_wine ok_eq( 0x200, ctx->fdwConversion, UINT, "%#x" ); + ok_eq( old_sentence, ctx->fdwSentence, UINT, "%#x" ); + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_eq( ~0, ctx->fdwConversion, UINT, "%#x" ); + ok_eq( ~0, ctx->fdwSentence, UINT, "%#x" ); + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + todo_wine ok_eq( 0x200, ctx->fdwConversion, UINT, "%#x" ); + ok_eq( old_sentence, ctx->fdwSentence, UINT, "%#x" ); + + ok_ret( 1, ImmFreeLayout( hkl ) ); + +cleanup: + /* sanitize conversion status to some sane default */ + ok_ret( 1, ImmSetConversionStatus( default_himc, 0, 0 ) ); + ok_ret( 1, ImmGetConversionStatus( default_himc, &conversion, &sentence ) ); + ok_eq( 0, conversion, UINT, "%#x" ); + ok_eq( 0, sentence, UINT, "%#x" ); + ok_eq( 0, ctx->fdwConversion, UINT, "%#x" ); + ok_eq( 0, ctx->fdwSentence, UINT, "%#x" ); + + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + ok_ret( 1, ImmUnlockIMC( default_himc ) ); +} + +static void test_ImmSetOpenStatus(void) +{ + const struct ime_call set_open_status_0_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETOPENSTATUS}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETOPENSTATUS}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETOPENSTATUS}, + }, + {0}, + }; + const struct ime_call set_open_status_1_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETOPENSTATUS}, + }, + {0}, + }; + const struct ime_call set_open_status_2_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETOPENSTATUS}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETOPENSTATUS}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETOPENSTATUS}, + }, + {0}, + }; + HKL hkl; + struct ime_windows ime_windows = {0}; + DWORD old_status, status; + INPUTCONTEXT *ctx; + HIMC himc; + HWND hwnd; + + ok_ret( 0, ImmGetOpenStatus( 0 ) ); + old_status = ImmGetOpenStatus( default_himc ); + + ctx = ImmLockIMC( default_himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + ok_eq( old_status, ctx->fOpen, UINT, "%#x" ); + + hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + process_messages(); + + status = ImmGetOpenStatus( default_himc ); + ok_eq( old_status, status, UINT, "%#x" ); + ok_eq( old_status, ctx->fOpen, UINT, "%#x" ); + + ok_ret( 1, ImmSetOpenStatus( default_himc, 0 ) ); + status = ImmGetOpenStatus( default_himc ); + ok_eq( 0, status, UINT, "%#x" ); + ok_eq( 0, ctx->fOpen, UINT, "%#x" ); + + ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) goto cleanup; + + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_ret( 1, ImmLoadIME( hkl ) ); + process_messages(); + /* initial values are dependent on both old and new IME */ + ok_ret( 1, ImmSetOpenStatus( default_himc, 0 ) ); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + status = ImmGetOpenStatus( default_himc ); + ok_eq( 0, status, UINT, "%#x" ); + ok_eq( 0, ctx->fOpen, UINT, "%#x" ); + + ok_seq( empty_sequence ); + ok_ret( 1, ImmSetOpenStatus( default_himc, 0xdeadbeef ) ); + ok_seq( set_open_status_0_seq ); + + status = ImmGetOpenStatus( default_himc ); + ok_eq( 0xdeadbeef, status, UINT, "%#x" ); + ok_eq( 0xdeadbeef, ctx->fOpen, UINT, "%#x" ); + + ok_ret( 1, ImmSetOpenStatus( default_himc, 0xdeadbeef ) ); + ok_seq( empty_sequence ); + + himc = ImmCreateContext(); + ok_ne( NULL, himc, HIMC, "%p" ); + ok_ret( 1, EnumThreadWindows( GetCurrentThreadId(), enum_thread_ime_windows, (LPARAM)&ime_windows ) ); + ok_ne( NULL, ime_windows.ime_hwnd, HWND, "%p" ); + ok_ne( NULL, ime_windows.ime_ui_hwnd, HWND, "%p" ); + ok_eq( default_himc, (HIMC)GetWindowLongPtrW( ime_windows.ime_ui_hwnd, IMMGWL_IMC ), HIMC, "%p" ); + ok_ret( 1, ImmSetOpenStatus( himc, TRUE ) ); + ok_eq( default_himc, (HIMC)GetWindowLongPtrW( ime_windows.ime_ui_hwnd, IMMGWL_IMC ), HIMC, "%p" ); + ok_ret( 1, ImmSetOpenStatus( himc, FALSE ) ); + ok_eq( default_himc, (HIMC)GetWindowLongPtrW( ime_windows.ime_ui_hwnd, IMMGWL_IMC ), HIMC, "%p" ); + ok_ret( 1, ImmDestroyContext( himc ) ); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + status = ImmGetOpenStatus( default_himc ); + ok( status == 0xdeadbeef || status == 0 /* MS Korean IME */, "got status %#lx\n", status ); + ok_eq( status, ctx->fOpen, UINT, "%#x" ); + + ctx->hWnd = 0; + ok_ret( 1, ImmSetOpenStatus( default_himc, 0xfeedcafe ) ); + ok_seq( set_open_status_1_seq ); + + status = ImmGetOpenStatus( default_himc ); + ok_eq( 0xfeedcafe, status, UINT, "%#x" ); + ok_eq( 0xfeedcafe, ctx->fOpen, UINT, "%#x" ); + + ctx->hWnd = hwnd; + ok_seq( empty_sequence ); + ok_ret( 1, ImmSetOpenStatus( default_himc, ~0 ) ); + ok_seq( set_open_status_2_seq ); + + status = ImmGetOpenStatus( default_himc ); + ok_eq( ~0, status, UINT, "%#x" ); + ok_eq( ~0, ctx->fOpen, UINT, "%#x" ); + + ok_ret( 1, ImmSetOpenStatus( default_himc, ~0 ) ); + ok_seq( empty_sequence ); + + status = ImmGetOpenStatus( default_himc ); + ok_eq( ~0, status, UINT, "%#x" ); + ok_eq( ~0, ctx->fOpen, UINT, "%#x" ); + + /* status is cached between IME activations */ + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + status = ImmGetOpenStatus( default_himc ); + ok_eq( old_status, status, UINT, "%#x" ); + ok_eq( old_status, ctx->fOpen, UINT, "%#x" ); + ok_ret( 1, ImmActivateLayout( hkl ) ); + status = ImmGetOpenStatus( default_himc ); + todo_wine ok_eq( 1, status, UINT, "%#x" ); + todo_wine ok_eq( 1, ctx->fOpen, UINT, "%#x" ); + ok_ret( 1, ImmSetOpenStatus( default_himc, 0 ) ); + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + status = ImmGetOpenStatus( default_himc ); + ok_eq( old_status, status, UINT, "%#x" ); + ok_eq( old_status, ctx->fOpen, UINT, "%#x" ); + + ok_ret( 1, ImmFreeLayout( hkl ) ); + +cleanup: + /* sanitize open status to some sane default */ + ok_ret( 1, ImmSetOpenStatus( default_himc, 0 ) ); + status = ImmGetOpenStatus( default_himc ); + ok_eq( 0, status, UINT, "%#x" ); + ok_eq( 0, ctx->fOpen, UINT, "%#x" ); + + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + ok_ret( 1, ImmUnlockIMC( default_himc ) ); +} + +static void test_ImmProcessKey(void) +{ + const struct ime_call process_key_0[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_PROCESS_KEY, .process_key = {.vkey = 'A', .lparam = MAKELONG(0, 0x1e)}, + }, + {0}, + }; + const struct ime_call process_key_1[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_PROCESS_KEY, .process_key = {.vkey = 'A', .lparam = MAKELONG(1, 0x1e)}, + }, + {0}, + }; + const struct ime_call process_key_2[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_PROCESS_KEY, .process_key = {.vkey = 'A', .lparam = MAKELONG(2, 0x1e)}, + }, + {0}, + }; + HKL hkl; + UINT_PTR ret; + HIMC himc; + HWND hwnd; + + hwnd = CreateWindowW( L"static", NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + process_messages(); + + ok_ret( 0, ImmProcessKey( hwnd, default_hkl, 'A', MAKELONG(1, 0x1e), 0 ) ); + ok_seq( empty_sequence ); + + ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) goto cleanup; + + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_ret( 1, ImmLoadIME( hkl ) ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + ok_ret( 0, ImmProcessKey( 0, hkl, 'A', MAKELONG(1, 0x1e), 0 ) ); + ok_seq( empty_sequence ); + + ok_ret( 0, ImmProcessKey( hwnd, hkl, 'A', MAKELONG(0, 0x1e), 0 ) ); + ok_seq( process_key_0 ); + ret = ImmProcessKey( hwnd, hkl, 'A', MAKELONG(1, 0x1e), 0 ); + todo_wine + ok_ret( 2, ret ); + ok_seq( process_key_1 ); + ok_ret( 2, ImmProcessKey( hwnd, hkl, 'A', MAKELONG(2, 0x1e), 0 ) ); + ok_seq( process_key_2 ); + + ok_eq( hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); + ok_ret( 0, ImmProcessKey( hwnd, default_hkl, 'A', MAKELONG(1, 0x1e), 0 ) ); + ok_seq( empty_sequence ); + ok_eq( hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); + + himc = ImmCreateContext(); + ok_ne( NULL, himc, HIMC, "%p" ); + ok_ret( 'A', ImmGetVirtualKey( hwnd ) ); + ok_eq( default_himc, ImmAssociateContext( hwnd, himc ), HIMC, "%p" ); + todo_wine ok_ret( VK_PROCESSKEY, ImmGetVirtualKey( hwnd ) ); + ok_eq( himc, ImmAssociateContext( hwnd, default_himc ), HIMC, "%p" ); + ok_ret( 'A', ImmGetVirtualKey( hwnd ) ); + ImmDestroyContext( himc ); + + ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYUP, 'A', 0 ) ); + ok_ret( VK_PROCESSKEY, ImmGetVirtualKey( hwnd ) ); + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + + ok_ret( 1, ImmFreeLayout( hkl ) ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + +cleanup: + ok_ret( 1, DestroyWindow( hwnd ) ); +} + +static void test_ImmActivateLayout(void) +{ + const struct ime_call activate_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_SELECT, .select = 1, + }, + {0}, + }; + const struct ime_call deactivate_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_NOTIFY, .notify = {.action = NI_COMPOSITIONSTR, .index = CPS_CANCEL, .value = 0}, + .todo = TRUE, + }, + { + .hkl = default_hkl, .himc = default_himc, + .func = IME_SELECT, .select = 0, + }, + {0}, + }; + struct ime_call activate_with_window_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_SELECT, .select = 1, + .flaky_himc = TRUE, + }, + { + .hkl = expect_ime, .himc = 0/*himc*/, + .func = IME_SELECT, .select = 1, + .flaky_himc = TRUE, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_SELECT, .wparam = 1, .lparam = (LPARAM)expect_ime}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_OPENSTATUSWINDOW}, + .todo = TRUE, .broken = TRUE, /* broken after SetForegroundWindow(GetDesktopWindow()) as in d3d8:device */ + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETOPENSTATUS}, + .todo = TRUE, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCONVERSIONMODE}, + .todo = TRUE, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETSENTENCEMODE}, + .todo = TRUE, + }, + {0}, + }; + struct ime_call deactivate_with_window_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_NOTIFY, .notify = {.action = NI_COMPOSITIONSTR, .index = CPS_CANCEL, .value = 0}, + .todo = TRUE, .flaky_himc = TRUE, + }, + { + .hkl = expect_ime, .himc = 0/*himc*/, + .func = IME_NOTIFY, .notify = {.action = NI_COMPOSITIONSTR, .index = CPS_CANCEL, .value = 0}, + .todo = TRUE, .flaky_himc = TRUE, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_CLOSESTATUSWINDOW}, + .todo = TRUE, .broken = TRUE, /* broken after SetForegroundWindow(GetDesktopWindow()) as in d3d8:device */ + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_SELECT, .wparam = 0, .lparam = (LPARAM)expect_ime}, + }, + { + .hkl = default_hkl, .himc = default_himc, + .func = IME_SELECT, .select = 0, + .flaky_himc = TRUE, + }, + { + .hkl = default_hkl, .himc = 0/*himc*/, + .func = IME_SELECT, .select = 0, + .flaky_himc = TRUE, + }, + {0}, + }; + HKL hkl; + struct ime_windows ime_windows = {0}; + HIMC himc; + HWND hwnd; + UINT ret; + + SET_ENABLE( ImeInquire, TRUE ); + SET_ENABLE( ImeDestroy, TRUE ); + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + + ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) goto cleanup; + + /* ActivateKeyboardLayout doesn't call ImeInquire / ImeDestroy */ + + ok_seq( empty_sequence ); + ok_eq( default_hkl, ActivateKeyboardLayout( hkl, 0 ), HKL, "%p" ); + ok_eq( hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); + ok_eq( hkl, ActivateKeyboardLayout( default_hkl, 0 ), HKL, "%p" ); + ok_seq( empty_sequence ); + ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); + + + /* ImmActivateLayout changes active HKL */ + + SET_EXPECT( ImeInquire ); + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_seq( activate_seq ); + CHECK_CALLED( ImeInquire ); + ok_ret( 1, ImmLoadIME( hkl ) ); + + ok_eq( hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); + + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_seq( empty_sequence ); + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + ok_seq( deactivate_seq ); + + ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); + + + /* ImmActivateLayout leaks the IME, we need to free it manually */ + + SET_EXPECT( ImeDestroy ); + ret = ImmFreeLayout( hkl ); + ok( ret, "ImmFreeLayout returned %u\n", ret ); + CHECK_CALLED( ImeDestroy ); + ok_seq( empty_sequence ); + + + /* when there's a window, ActivateKeyboardLayout calls ImeInquire */ + + hwnd = CreateWindowW( L"static", NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + process_messages(); + ok_seq( empty_sequence ); + + himc = ImmCreateContext(); + ok( !!himc, "got himc %p\n", himc ); + ok_seq( empty_sequence ); + + SET_EXPECT( ImeInquire ); + ok_eq( default_hkl, ActivateKeyboardLayout( hkl, 0 ), HKL, "%p" ); + CHECK_CALLED( ImeInquire ); + activate_with_window_seq[1].himc = himc; + ok_seq( activate_with_window_seq ); + ok_ret( 1, ImmLoadIME( hkl ) ); + + ok_eq( hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); + + /* FIXME: ignore spurious VK_CONTROL ImeProcessKey / ImeToAsciiEx calls */ + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + ok_eq( hkl, ActivateKeyboardLayout( default_hkl, 0 ), HKL, "%p" ); + deactivate_with_window_seq[1].himc = himc; + deactivate_with_window_seq[5].himc = himc; + ok_seq( deactivate_with_window_seq ); + + ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); + + ok_ret( 1, ImmDestroyContext( himc ) ); + ok_seq( empty_sequence ); + + + ok_eq( default_hkl, ActivateKeyboardLayout( hkl, 0 ), HKL, "%p" ); + ok_eq( hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); + + ok_ret( 1, EnumThreadWindows( GetCurrentThreadId(), enum_thread_ime_windows, (LPARAM)&ime_windows ) ); + ok( !!ime_windows.ime_hwnd, "missing IME window\n" ); + ok( !!ime_windows.ime_ui_hwnd, "missing IME UI window\n" ); + ok_ret( (UINT_PTR)ime_windows.ime_hwnd, (UINT_PTR)GetParent( ime_windows.ime_ui_hwnd ) ); + + ok_eq( hkl, ActivateKeyboardLayout( default_hkl, 0 ), HKL, "%p" ); + ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); + process_messages(); + + + SET_EXPECT( ImeDestroy ); + ok_ret( 1, ImmFreeLayout( hkl ) ); + CHECK_CALLED( ImeDestroy ); + + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + +cleanup: + SET_ENABLE( ImeInquire, FALSE ); + SET_ENABLE( ImeDestroy, FALSE ); +} + +static void test_ImmCreateInputContext(void) +{ + struct ime_call activate_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_SELECT, .select = 1, + .flaky_himc = TRUE, + }, + { + .hkl = expect_ime, .himc = 0/*himc[0]*/, + .func = IME_SELECT, .select = 1, + .flaky_himc = TRUE, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_SELECT, .wparam = 1, .lparam = (LPARAM)expect_ime}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_OPENSTATUSWINDOW}, + .todo = TRUE, .broken = TRUE, /* broken after SetForegroundWindow(GetDesktopWindow()) as in d3d8:device */ + }, + {0}, + }; + struct ime_call select1_seq[] = + { + { + .hkl = expect_ime, .himc = 0/*himc[0]*/, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETOPENSTATUS}, + .todo = TRUE, .flaky_himc = TRUE, .broken = TRUE /* sometimes */, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETOPENSTATUS}, + .todo = TRUE, .flaky_himc = TRUE, .broken = TRUE /* sometimes */, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETOPENSTATUS}, + .todo = TRUE, .broken = TRUE /* sometimes */, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCONVERSIONMODE}, + .todo = TRUE, .broken = TRUE /* sometimes */, + }, + { + .hkl = expect_ime, .himc = 0/*himc[1]*/, + .func = IME_SELECT, .select = 1, + }, + {0}, + }; + struct ime_call select0_seq[] = + { + { + .hkl = expect_ime, .himc = 0/*himc[1]*/, + .func = IME_SELECT, .select = 0, + }, + {0}, + }; + struct ime_call deactivate_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_NOTIFY, .notify = {.action = NI_COMPOSITIONSTR, .index = CPS_CANCEL, .value = 0}, + .todo = TRUE, .flaky_himc = TRUE, + }, + { + .hkl = expect_ime, .himc = 0/*himc[0]*/, + .func = IME_NOTIFY, .notify = {.action = NI_COMPOSITIONSTR, .index = CPS_CANCEL, .value = 0}, + .todo = TRUE, .flaky_himc = TRUE, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_CLOSESTATUSWINDOW}, + .todo = TRUE, .broken = TRUE, /* broken after SetForegroundWindow(GetDesktopWindow()) as in d3d8:device */ + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_SELECT, .wparam = 0, .lparam = (LPARAM)expect_ime}, + }, + { + .hkl = default_hkl, .himc = default_himc, + .func = IME_SELECT, .select = 0, + .flaky_himc = TRUE, + }, + { + .hkl = default_hkl, .himc = 0/*himc[0]*/, + .func = IME_SELECT, .select = 0, + .flaky_himc = TRUE, + }, + {0}, + }; + HKL hkl; + INPUTCONTEXT *ctx; + HIMC himc[2]; + HWND hwnd; + + ctx = ImmLockIMC( default_himc ); + ok( !!ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); + ok_ret( 0, IsWindow( ctx->hWnd ) ); + ok_ret( 1, ImmUnlockIMC( default_himc ) ); + + + /* new input contexts cannot be locked before IME window has been created */ + + himc[0] = ImmCreateContext(); + ok( !!himc[0], "ImmCreateContext failed, error %lu\n", GetLastError() ); + ctx = ImmLockIMC( himc[0] ); + todo_wine + ok( !ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); + if (ctx) ImmUnlockIMCC( himc[0] ); + + hwnd = CreateWindowW( L"static", NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + process_messages(); + + ctx = ImmLockIMC( default_himc ); + ok( !!ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); + ok_ret( 1, ImmUnlockIMC( default_himc ) ); + + ctx = ImmLockIMC( himc[0] ); + ok( !!ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); + ok_ret( 1, ImmUnlockIMC( himc[0] ) ); + + + ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; + ime_info.dwPrivateDataSize = 123; + + if (!(hkl = wineime_hkl)) goto cleanup; + + ok_ret( 1, ImmLoadIME( hkl ) ); + + /* Activating the layout calls ImeSelect 1 on existing HIMC */ + + ok_seq( empty_sequence ); + ok_ret( 1, ImmActivateLayout( hkl ) ); + activate_seq[1].himc = himc[0]; + ok_seq( activate_seq ); + + ok_eq( hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); + + ctx = ImmLockIMC( default_himc ); + ok( !!ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); + ok_ret( 1, ImmUnlockIMC( default_himc ) ); + + ctx = ImmLockIMC( himc[0] ); + ok( !!ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); + ok_ret( 1, ImmUnlockIMC( himc[0] ) ); + + + /* ImmLockIMC triggers the ImeSelect call, to allocate private data */ + + himc[1] = ImmCreateContext(); + ok( !!himc[1], "ImmCreateContext failed, error %lu\n", GetLastError() ); + + ok_seq( empty_sequence ); + ctx = ImmLockIMC( himc[1] ); + ok( !!ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); + select1_seq[0].himc = himc[0]; + select1_seq[4].himc = himc[1]; + ok_seq( select1_seq ); + + ok_ret( 1, ImmUnlockIMC( himc[1] ) ); + + ok_seq( empty_sequence ); + ok_ret( 1, ImmDestroyContext( himc[1] ) ); + select0_seq[0].himc = himc[1]; + ok_seq( select0_seq ); + + + /* Deactivating the layout calls ImeSelect 0 on existing HIMC */ + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + deactivate_seq[1].himc = himc[0]; + deactivate_seq[5].himc = himc[0]; + ok_seq( deactivate_seq ); + + ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); + + ok_ret( 1, ImmFreeLayout( hkl ) ); + ok_seq( empty_sequence ); + +cleanup: + ok_ret( 1, ImmDestroyContext( himc[0] ) ); + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + ime_info.dwPrivateDataSize = 0; +} + +static void test_DefWindowProc(void) +{ + const struct ime_call start_composition_seq[] = + { + {.hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_STARTCOMPOSITION}}, + {0}, + }; + const struct ime_call end_composition_seq[] = + { + {.hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_ENDCOMPOSITION}}, + {0}, + }; + const struct ime_call composition_seq[] = + { + {.hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_COMPOSITION}}, + {0}, + }; + const struct ime_call set_context_seq[] = + { + {.hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_SETCONTEXT}}, + {0}, + }; + const struct ime_call notify_seq[] = + { + {.hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY}}, + {0}, + }; + HKL hkl; + UINT_PTR ret; + HWND hwnd; + + ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) return; + + hwnd = CreateWindowW( L"static", NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_ret( 1, ImmLoadIME( hkl ) ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + ok_ret( 0, DefWindowProcW( hwnd, WM_IME_STARTCOMPOSITION, 0, 0 ) ); + ok_seq( start_composition_seq ); + ok_ret( 0, DefWindowProcW( hwnd, WM_IME_ENDCOMPOSITION, 0, 0 ) ); + ok_seq( end_composition_seq ); + ok_ret( 0, DefWindowProcW( hwnd, WM_IME_COMPOSITION, 0, 0 ) ); + ok_seq( composition_seq ); + ok_ret( 0, DefWindowProcW( hwnd, WM_IME_SETCONTEXT, 0, 0 ) ); + ok_seq( set_context_seq ); + ok_ret( 0, DefWindowProcW( hwnd, WM_IME_NOTIFY, 0, 0 ) ); + ok_seq( notify_seq ); + ok_ret( 0, DefWindowProcW( hwnd, WM_IME_CONTROL, 0, 0 ) ); + ok_seq( empty_sequence ); + ok_ret( 0, DefWindowProcW( hwnd, WM_IME_COMPOSITIONFULL, 0, 0 ) ); + ok_seq( empty_sequence ); + ok_ret( 0, DefWindowProcW( hwnd, WM_IME_SELECT, 0, 0 ) ); + ok_seq( empty_sequence ); + ok_ret( 0, DefWindowProcW( hwnd, WM_IME_CHAR, 0, 0 ) ); + ok_seq( empty_sequence ); + ok_ret( 0, DefWindowProcW( hwnd, 0x287, 0, 0 ) ); + ok_seq( empty_sequence ); + ok_ret( 0, DefWindowProcW( hwnd, WM_IME_REQUEST, 0, 0 ) ); + ok_seq( empty_sequence ); + ret = DefWindowProcW( hwnd, WM_IME_KEYDOWN, 0, 0 ); + todo_wine + ok_ret( 0, ret ); + ok_seq( empty_sequence ); + ret = DefWindowProcW( hwnd, WM_IME_KEYUP, 0, 0 ); + todo_wine + ok_ret( 0, ret ); + ok_seq( empty_sequence ); + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + + ok_ret( 1, ImmFreeLayout( hkl ) ); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; +} + +static void test_ImmSetActiveContext(void) +{ + const struct ime_call activate_0_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_SET_ACTIVE_CONTEXT, .set_active_context = {.flag = 1} + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_SETCONTEXT, .wparam = 1, .lparam = ISC_SHOWUIALL} + }, + {0}, + }; + const struct ime_call deactivate_0_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_SET_ACTIVE_CONTEXT, .set_active_context = {.flag = 0} + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_SETCONTEXT, .wparam = 0, .lparam = ISC_SHOWUIALL} + }, + {0}, + }; + struct ime_call deactivate_1_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETOPENSTATUS}, + .todo = TRUE, .flaky_himc = TRUE, .broken = TRUE /* sometimes */, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETOPENSTATUS}, + .todo = TRUE, .broken = TRUE /* sometimes */, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCONVERSIONMODE}, + .todo = TRUE, .broken = TRUE /* sometimes */, + }, + { + .hkl = expect_ime, .himc = 0/*himc*/, + .func = IME_SELECT, .select = 1, + }, + { + .hkl = expect_ime, .himc = 0/*himc*/, + .func = IME_SET_ACTIVE_CONTEXT, .set_active_context = {.flag = 0} + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_SETCONTEXT, .wparam = 0, .lparam = ISC_SHOWUIALL} + }, + {0}, + }; + struct ime_call deactivate_2_seq[] = + { + { + .hkl = expect_ime, .himc = 0/*himc*/, + .func = IME_SET_ACTIVE_CONTEXT, .set_active_context = {.flag = 0} + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_SETCONTEXT, .wparam = 0, .lparam = ISC_SHOWUIALL} + }, + {0}, + }; + struct ime_call activate_1_seq[] = + { + { + .hkl = expect_ime, .himc = 0/*himc*/, + .func = IME_SET_ACTIVE_CONTEXT, .set_active_context = {.flag = 1} + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_SETCONTEXT, .wparam = 1, .lparam = ISC_SHOWUIALL} + }, + {0}, + }; + HKL hkl; + struct ime_windows ime_windows = {0}; + INPUTCONTEXT *ctx; + HIMC himc; + HWND hwnd; + + ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) return; + + hwnd = CreateWindowW( L"static", NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_ret( 1, ImmLoadIME( hkl ) ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + ok_ret( 1, EnumThreadWindows( GetCurrentThreadId(), enum_thread_ime_windows, (LPARAM)&ime_windows ) ); + ok_ne( NULL, ime_windows.ime_hwnd, HWND, "%p" ); + ok_ne( NULL, ime_windows.ime_ui_hwnd, HWND, "%p" ); + ok_ret( 0, IsWindowVisible( ime_windows.ime_ui_hwnd ) ); + + SetLastError( 0xdeadbeef ); + ok_ret( 1, ImmSetActiveContext( hwnd, default_himc, TRUE ) ); + ok_seq( activate_0_seq ); + ok_ret( 0, GetLastError() ); + ok_ret( 0, IsWindowVisible( ime_windows.ime_ui_hwnd ) ); + ok_ret( 1, ImmSetActiveContext( hwnd, default_himc, TRUE ) ); + ok_seq( activate_0_seq ); + ok_ret( 1, ImmSetActiveContext( hwnd, default_himc, FALSE ) ); + ok_seq( deactivate_0_seq ); + + himc = ImmCreateContext(); + ok_ne( NULL, himc, HIMC, "%p" ); + ctx = ImmLockIMC( himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + ok_eq( 0, ctx->hWnd, HWND, "%p" ); + + ok_ret( 1, ImmSetActiveContext( hwnd, himc, FALSE ) ); + deactivate_1_seq[3].himc = himc; + deactivate_1_seq[4].himc = himc; + ok_seq( deactivate_1_seq ); + ok_ret( 1, ImmSetActiveContext( hwnd, himc, TRUE ) ); + activate_1_seq[0].himc = himc; + ok_seq( activate_1_seq ); + + ctx->hWnd = (HWND)0xdeadbeef; + ok_ret( 1, ImmSetActiveContext( hwnd, himc, FALSE ) ); + ok_eq( (HWND)0xdeadbeef, ctx->hWnd, HWND, "%p" ); + deactivate_2_seq[0].himc = himc; + ok_seq( deactivate_2_seq ); + + ctx->hWnd = 0; + ok_ret( 1, ImmSetActiveContext( hwnd, himc, TRUE ) ); + ok_eq( hwnd, ctx->hWnd, HWND, "%p" ); + activate_1_seq[0].himc = himc; + ok_seq( activate_1_seq ); + + ok_eq( default_himc, (HIMC)GetWindowLongPtrW( ime_windows.ime_ui_hwnd, IMMGWL_IMC ), HIMC, "%p" ); + ok_ret( 1, ImmSetActiveContext( hwnd, himc, TRUE ) ); + ok_ret( 0, IsWindowVisible( ime_windows.ime_ui_hwnd ) ); + ok_eq( default_himc, (HIMC)GetWindowLongPtrW( ime_windows.ime_ui_hwnd, IMMGWL_IMC ), HIMC, "%p" ); + + ctx->hWnd = 0; + ok_eq( default_himc, ImmAssociateContext( hwnd, himc ), HIMC, "%p" ); + ok_eq( himc, (HIMC)GetWindowLongPtrW( ime_windows.ime_ui_hwnd, IMMGWL_IMC ), HIMC, "%p" ); + ok_ret( 0, IsWindowVisible( ime_windows.ime_ui_hwnd ) ); + ok_eq( hwnd, ctx->hWnd, HWND, "%p" ); + + ctx->hWnd = (HWND)0xdeadbeef; + ok_eq( himc, ImmGetContext( hwnd ), HIMC, "%p" ); + ok_eq( (HWND)0xdeadbeef, ctx->hWnd, HWND, "%p" ); + ok_ret( 1, ImmReleaseContext( hwnd, himc ) ); + + ok_ret( 1, ImmUnlockIMC( himc ) ); + ok_ret( 1, ImmDestroyContext( himc ) ); + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + + ok_ret( 1, ImmFreeLayout( hkl ) ); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; +} + +static void test_ImmRequestMessage(void) +{ + struct ime_call composition_window_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_REQUEST, .wparam = IMR_COMPOSITIONWINDOW, .lparam = 0/*&comp_form*/} + }, + {0}, + }; + struct ime_call candidate_window_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_REQUEST, .wparam = IMR_CANDIDATEWINDOW, .lparam = 0/*&cand_form*/} + }, + {0}, + }; + struct ime_call composition_font_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_REQUEST, .wparam = IMR_COMPOSITIONFONT, .lparam = 0/*&log_font*/} + }, + {0}, + }; + struct ime_call reconvert_string_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_REQUEST, .wparam = IMR_RECONVERTSTRING, .lparam = 0/*&reconv*/} + }, + {0}, + }; + struct ime_call confirm_reconvert_string_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_REQUEST, .wparam = IMR_CONFIRMRECONVERTSTRING, .lparam = 0/*&reconv*/} + }, + {0}, + }; + struct ime_call query_char_position_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_REQUEST, .wparam = IMR_QUERYCHARPOSITION, .lparam = 0/*&char_pos*/} + }, + {0}, + }; + struct ime_call document_feed_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_REQUEST, .wparam = IMR_DOCUMENTFEED, .lparam = 0/*&reconv*/} + }, + {0}, + }; + HKL hkl; + COMPOSITIONFORM comp_form = {0}; + IMECHARPOSITION char_pos = {0}; + RECONVERTSTRING reconv = {0}; + CANDIDATEFORM cand_form = {0}; + LOGFONTW log_font = {0}; + INPUTCONTEXT *ctx; + HIMC himc; + HWND hwnd; + + if (!(hkl = wineime_hkl)) return; + + hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + + ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; + + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_ret( 1, ImmLoadIME( hkl ) ); + himc = ImmCreateContext(); + ok_ne( NULL, himc, HIMC, "%p" ); + ctx = ImmLockIMC( himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + ok_ret( 0, ImmRequestMessageW( default_himc, 0xdeadbeef, 0 ) ); + todo_wine ok_seq( empty_sequence ); + ok_ret( 0, ImmRequestMessageW( default_himc, IMR_COMPOSITIONWINDOW, (LPARAM)&comp_form ) ); + composition_window_seq[0].message.lparam = (LPARAM)&comp_form; + ok_seq( composition_window_seq ); + ok_ret( 0, ImmRequestMessageW( default_himc, IMR_CANDIDATEWINDOW, (LPARAM)&cand_form ) ); + candidate_window_seq[0].message.lparam = (LPARAM)&cand_form; + ok_seq( candidate_window_seq ); + ok_ret( 0, ImmRequestMessageW( default_himc, IMR_COMPOSITIONFONT, (LPARAM)&log_font ) ); + composition_font_seq[0].message.lparam = (LPARAM)&log_font; + ok_seq( composition_font_seq ); + ok_ret( 0, ImmRequestMessageW( default_himc, IMR_RECONVERTSTRING, (LPARAM)&reconv ) ); + todo_wine ok_seq( empty_sequence ); + reconv.dwSize = sizeof(RECONVERTSTRING); + ok_ret( 0, ImmRequestMessageW( default_himc, IMR_RECONVERTSTRING, (LPARAM)&reconv ) ); + reconvert_string_seq[0].message.lparam = (LPARAM)&reconv; + ok_seq( reconvert_string_seq ); + ok_ret( 0, ImmRequestMessageW( default_himc, IMR_CONFIRMRECONVERTSTRING, (LPARAM)&reconv ) ); + confirm_reconvert_string_seq[0].message.lparam = (LPARAM)&reconv; + ok_seq( confirm_reconvert_string_seq ); + ok_ret( 0, ImmRequestMessageW( default_himc, IMR_QUERYCHARPOSITION, (LPARAM)&char_pos ) ); + query_char_position_seq[0].message.lparam = (LPARAM)&char_pos; + ok_seq( query_char_position_seq ); + ok_ret( 0, ImmRequestMessageW( default_himc, IMR_DOCUMENTFEED, (LPARAM)&reconv ) ); + document_feed_seq[0].message.lparam = (LPARAM)&reconv; + ok_seq( document_feed_seq ); + + ok_ret( 0, ImmRequestMessageW( himc, IMR_CANDIDATEWINDOW, (LPARAM)&cand_form ) ); + ok_seq( empty_sequence ); + ok_ret( 1, ImmSetActiveContext( hwnd, himc, TRUE ) ); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + ok_ret( 0, ImmRequestMessageW( himc, IMR_CANDIDATEWINDOW, (LPARAM)&cand_form ) ); + candidate_window_seq[0].message.lparam = (LPARAM)&cand_form; + ok_seq( candidate_window_seq ); + ok_ret( 0, ImmRequestMessageW( default_himc, IMR_CANDIDATEWINDOW, (LPARAM)&cand_form ) ); + ok_seq( candidate_window_seq ); + + ok_ret( 1, ImmUnlockIMC( himc ) ); + ok_ret( 1, ImmDestroyContext( himc ) ); + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + + ok_ret( 1, ImmFreeLayout( hkl ) ); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; +} + +static void test_ImmGetCandidateList( BOOL unicode ) +{ + char buffer[512], expect_bufW[512] = {0}, expect_bufA[512] = {0}; + CANDIDATELIST *cand_list = (CANDIDATELIST *)buffer, *expect_listW, *expect_listA; + HKL hkl; + CANDIDATEINFO *cand_info; + INPUTCONTEXT *ctx; + HIMC himc; + HWND hwnd; + + expect_listW = (CANDIDATELIST *)expect_bufW; + expect_listW->dwSize = offsetof(CANDIDATELIST, dwOffset[2]) + 32 * sizeof(WCHAR); + expect_listW->dwStyle = 0xdeadbeef; + expect_listW->dwCount = 2; + expect_listW->dwSelection = 3; + expect_listW->dwPageStart = 4; + expect_listW->dwPageSize = 5; + expect_listW->dwOffset[0] = offsetof(CANDIDATELIST, dwOffset[2]) + 2 * sizeof(WCHAR); + expect_listW->dwOffset[1] = offsetof(CANDIDATELIST, dwOffset[2]) + 16 * sizeof(WCHAR); + wcscpy( (WCHAR *)(expect_bufW + expect_listW->dwOffset[0]), L"Candidate 1" ); + wcscpy( (WCHAR *)(expect_bufW + expect_listW->dwOffset[1]), L"Candidate 2" ); + + expect_listA = (CANDIDATELIST *)expect_bufA; + expect_listA->dwSize = offsetof(CANDIDATELIST, dwOffset[2]) + 32; + expect_listA->dwStyle = 0xdeadbeef; + expect_listA->dwCount = 2; + expect_listA->dwSelection = 3; + expect_listA->dwPageStart = 4; + expect_listA->dwPageSize = 5; + expect_listA->dwOffset[0] = offsetof(CANDIDATELIST, dwOffset[2]) + 2; + expect_listA->dwOffset[1] = offsetof(CANDIDATELIST, dwOffset[2]) + 16; + strcpy( (char *)(expect_bufA + expect_listA->dwOffset[0]), "Candidate 1" ); + strcpy( (char *)(expect_bufA + expect_listA->dwOffset[1]), "Candidate 2" ); + + winetest_push_context( unicode ? "unicode" : "ansi" ); + + /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ + ime_info.fdwProperty = IME_PROP_END_UNLOAD; + if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) goto cleanup; + + hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_ret( 1, ImmLoadIME( hkl ) ); + himc = ImmCreateContext(); + ok_ne( NULL, himc, HIMC, "%p" ); + ctx = ImmLockIMC( himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + ok_ret( 0, ImmGetCandidateListW( default_himc, 0, NULL, 0 ) ); + ok_seq( empty_sequence ); + ok_ret( 0, ImmGetCandidateListW( default_himc, 1, NULL, 0 ) ); + ok_seq( empty_sequence ); + ok_ret( 0, ImmGetCandidateListW( default_himc, 0, cand_list, sizeof(buffer) ) ); + ok_seq( empty_sequence ); + + ok_ret( 0, ImmGetCandidateListW( himc, 0, cand_list, sizeof(buffer) ) ); + ok_seq( empty_sequence ); + + todo_wine ok_seq( empty_sequence ); + ctx->hCandInfo = ImmReSizeIMCC( ctx->hCandInfo, sizeof(*cand_info) + sizeof(buffer) ); + ok( !!ctx->hCandInfo, "ImmReSizeIMCC failed, error %lu\n", GetLastError() ); + + cand_info = ImmLockIMCC( ctx->hCandInfo ); + ok( !!cand_info, "ImmLockIMCC failed, error %lu\n", GetLastError() ); + cand_info->dwCount = 1; + cand_info->dwOffset[0] = sizeof(*cand_info); + if (unicode) memcpy( cand_info + 1, expect_bufW, sizeof(expect_bufW) ); + else memcpy( cand_info + 1, expect_bufA, sizeof(expect_bufA) ); + ok_ret( 0, ImmUnlockIMCC( ctx->hCandInfo ) ); + + ok_ret( (unicode ? 96 : 80), ImmGetCandidateListW( himc, 0, NULL, 0 ) ); + ok_seq( empty_sequence ); + ok_ret( 0, ImmGetCandidateListW( himc, 1, NULL, 0 ) ); + ok_seq( empty_sequence ); + memset( buffer, 0xcd, sizeof(buffer) ); + ok_ret( (unicode ? 96 : 80), ImmGetCandidateListW( himc, 0, cand_list, sizeof(buffer) ) ); + ok_seq( empty_sequence ); + + if (!unicode) + { + expect_listW->dwSize = offsetof(CANDIDATELIST, dwOffset[2]) + 24 * sizeof(WCHAR); + expect_listW->dwOffset[0] = offsetof(CANDIDATELIST, dwOffset[2]); + expect_listW->dwOffset[1] = offsetof(CANDIDATELIST, dwOffset[2]) + 12 * sizeof(WCHAR); + wcscpy( (WCHAR *)(expect_bufW + expect_listW->dwOffset[0]), L"Candidate 1" ); + wcscpy( (WCHAR *)(expect_bufW + expect_listW->dwOffset[1]), L"Candidate 2" ); + } + check_candidate_list_( __LINE__, cand_list, expect_listW, TRUE ); + + ok_ret( (unicode ? 56 : 64), ImmGetCandidateListA( himc, 0, NULL, 0 ) ); + ok_seq( empty_sequence ); + ok_ret( 0, ImmGetCandidateListA( himc, 1, NULL, 0 ) ); + ok_seq( empty_sequence ); + memset( buffer, 0xcd, sizeof(buffer) ); + ok_ret( (unicode ? 56 : 64), ImmGetCandidateListA( himc, 0, cand_list, sizeof(buffer) ) ); + ok_seq( empty_sequence ); + + if (unicode) + { + expect_listA->dwSize = offsetof(CANDIDATELIST, dwOffset[2]) + 24; + expect_listA->dwOffset[0] = offsetof(CANDIDATELIST, dwOffset[2]); + expect_listA->dwOffset[1] = offsetof(CANDIDATELIST, dwOffset[2]) + 12; + strcpy( (char *)(expect_bufA + expect_listA->dwOffset[0]), "Candidate 1" ); + strcpy( (char *)(expect_bufA + expect_listA->dwOffset[1]), "Candidate 2" ); + } + check_candidate_list_( __LINE__, cand_list, expect_listA, FALSE ); + + ok_ret( 1, ImmUnlockIMC( himc ) ); + ok_ret( 1, ImmDestroyContext( himc ) ); + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + + ok_ret( 1, ImmFreeLayout( hkl ) ); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + +cleanup: + winetest_pop_context(); +} + +static void test_ImmGetCandidateListCount( BOOL unicode ) +{ + HKL hkl; + CANDIDATEINFO *cand_info; + INPUTCONTEXT *ctx; + DWORD count; + HIMC himc; + HWND hwnd; + + winetest_push_context( unicode ? "unicode" : "ansi" ); + + /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ + ime_info.fdwProperty = IME_PROP_END_UNLOAD; + if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) goto cleanup; + + hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_ret( 1, ImmLoadIME( hkl ) ); + himc = ImmCreateContext(); + ok_ne( NULL, himc, HIMC, "%p" ); + ctx = ImmLockIMC( himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + ok_ret( 144, ImmGetCandidateListCountW( default_himc, &count ) ); + ok_eq( 0, count, UINT, "%u" ); + ok_seq( empty_sequence ); + ok_ret( 144, ImmGetCandidateListCountA( default_himc, &count ) ); + ok_eq( 0, count, UINT, "%u" ); + ok_seq( empty_sequence ); + + ok_ret( 144, ImmGetCandidateListCountW( himc, &count ) ); + ok_eq( 0, count, UINT, "%u" ); + ok_seq( empty_sequence ); + ok_ret( 144, ImmGetCandidateListCountA( himc, &count ) ); + ok_eq( 0, count, UINT, "%u" ); + ok_seq( empty_sequence ); + + cand_info = ImmLockIMCC( ctx->hCandInfo ); + ok( !!cand_info, "ImmLockIMCC failed, error %lu\n", GetLastError() ); + cand_info->dwCount = 1; + ok_ret( 0, ImmUnlockIMCC( ctx->hCandInfo ) ); + + todo_wine_if(!unicode) + ok_ret( (unicode ? 144 : 172), ImmGetCandidateListCountW( himc, &count ) ); + ok_eq( 1, count, UINT, "%u" ); + ok_seq( empty_sequence ); + todo_wine_if(unicode) + ok_ret( (unicode ? 172 : 144), ImmGetCandidateListCountA( himc, &count ) ); + ok_eq( 1, count, UINT, "%u" ); + ok_seq( empty_sequence ); + + ok_ret( 1, ImmUnlockIMC( himc ) ); + ok_ret( 1, ImmDestroyContext( himc ) ); + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + + ok_ret( 1, ImmFreeLayout( hkl ) ); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + +cleanup: + winetest_pop_context(); +} + +static void test_ImmGetCandidateWindow(void) +{ + HKL hkl; + const CANDIDATEFORM expect_form = + { + .dwIndex = 0xdeadbeef, + .dwStyle = 0xfeedcafe, + .ptCurrentPos = {.x = 123, .y = 456}, + .rcArea = {.left = 1, .top = 2, .right = 3, .bottom = 4}, + }; + CANDIDATEFORM cand_form; + INPUTCONTEXT *ctx; + HIMC himc; + HWND hwnd; + + ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) return; + + hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_ret( 1, ImmLoadIME( hkl ) ); + himc = ImmCreateContext(); + ok_ne( NULL, himc, HIMC, "%p" ); + ctx = ImmLockIMC( himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + memset( &cand_form, 0xcd, sizeof(cand_form) ); + ok_ret( 0, ImmGetCandidateWindow( default_himc, 0, &cand_form ) ); + ok_eq( 0xcdcdcdcd, cand_form.dwIndex, UINT, "%u" ); + ok_ret( 0, ImmGetCandidateWindow( default_himc, 1, &cand_form ) ); + ok_eq( 0xcdcdcdcd, cand_form.dwIndex, UINT, "%u" ); + ok_ret( 0, ImmGetCandidateWindow( default_himc, 2, &cand_form ) ); + ok_eq( 0xcdcdcdcd, cand_form.dwIndex, UINT, "%u" ); + ok_ret( 0, ImmGetCandidateWindow( default_himc, 3, &cand_form ) ); + ok_eq( 0xcdcdcdcd, cand_form.dwIndex, UINT, "%u" ); + ok_ret( 1, ImmGetCandidateWindow( default_himc, 4, &cand_form ) ); + ok_seq( empty_sequence ); + + ok_ret( 0, ImmGetCandidateWindow( himc, 0, &cand_form ) ); + ok_seq( empty_sequence ); + + ok_seq( empty_sequence ); + ok( !!ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); + ctx->cfCandForm[0] = expect_form; + + ok_ret( 1, ImmGetCandidateWindow( himc, 0, &cand_form ) ); + check_candidate_form( &cand_form, &expect_form ); + ok_seq( empty_sequence ); + + ok_seq( empty_sequence ); + ok( !!ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); + ctx->cfCandForm[0].dwIndex = -1; + + ok_ret( 0, ImmGetCandidateWindow( himc, 0, &cand_form ) ); + ok_seq( empty_sequence ); + + ok_ret( 1, ImmUnlockIMC( himc ) ); + ok_ret( 1, ImmDestroyContext( himc ) ); + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + + ok_ret( 1, ImmFreeLayout( hkl ) ); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; +} + +static void test_ImmGetCompositionString( BOOL unicode ) +{ + static COMPOSITIONSTRING expect_string_empty = {.dwSize = sizeof(COMPOSITIONSTRING)}; + static COMPOSITIONSTRING expect_stringA = + { + .dwSize = 176, + .dwCompReadAttrLen = 8, + .dwCompReadAttrOffset = 116, + .dwCompReadClauseLen = 8, + .dwCompReadClauseOffset = 108, + .dwCompReadStrLen = 8, + .dwCompReadStrOffset = 100, + .dwCompAttrLen = 4, + .dwCompAttrOffset = 136, + .dwCompClauseLen = 8, + .dwCompClauseOffset = 128, + .dwCompStrLen = 4, + .dwCompStrOffset = 124, + .dwCursorPos = 3, + .dwDeltaStart = 1, + .dwResultReadClauseLen = 8, + .dwResultReadClauseOffset = 150, + .dwResultReadStrLen = 10, + .dwResultReadStrOffset = 140, + .dwResultClauseLen = 8, + .dwResultClauseOffset = 164, + .dwResultStrLen = 6, + .dwResultStrOffset = 158, + .dwPrivateSize = 4, + .dwPrivateOffset = 172, + }; + static const COMPOSITIONSTRING expect_stringW = + { + .dwSize = 204, + .dwCompReadAttrLen = 8, + .dwCompReadAttrOffset = 124, + .dwCompReadClauseLen = 8, + .dwCompReadClauseOffset = 116, + .dwCompReadStrLen = 8, + .dwCompReadStrOffset = 100, + .dwCompAttrLen = 4, + .dwCompAttrOffset = 148, + .dwCompClauseLen = 8, + .dwCompClauseOffset = 140, + .dwCompStrLen = 4, + .dwCompStrOffset = 132, + .dwCursorPos = 3, + .dwDeltaStart = 1, + .dwResultReadClauseLen = 8, + .dwResultReadClauseOffset = 172, + .dwResultReadStrLen = 10, + .dwResultReadStrOffset = 152, + .dwResultClauseLen = 8, + .dwResultClauseOffset = 192, + .dwResultStrLen = 6, + .dwResultStrOffset = 180, + .dwPrivateSize = 4, + .dwPrivateOffset = 200, + }; + static const UINT gcs_indexes[] = + { + GCS_COMPREADSTR, + GCS_COMPREADATTR, + GCS_COMPREADCLAUSE, + GCS_COMPSTR, + GCS_COMPATTR, + GCS_COMPCLAUSE, + GCS_CURSORPOS, + GCS_DELTASTART, + GCS_RESULTREADSTR, + GCS_RESULTREADCLAUSE, + GCS_RESULTSTR, + GCS_RESULTCLAUSE, + }; + static const UINT scs_indexes[] = + { + SCS_SETSTR, + SCS_CHANGEATTR, + SCS_CHANGECLAUSE, + }; + static const UINT expect_retW[ARRAY_SIZE(gcs_indexes)] = {16, 8, 8, 8, 4, 8, 3, 1, 20, 8, 12, 8}; + static const UINT expect_retA[ARRAY_SIZE(gcs_indexes)] = {8, 8, 8, 4, 4, 8, 3, 1, 10, 8, 6, 8}; + HKL hkl; + COMPOSITIONSTRING *string; + char buffer[1024]; + INPUTCONTEXT *old_ctx, *ctx; + const void *str; + HIMCC old_himcc; + UINT i, len; + BYTE *dst; + HIMC himc; + HWND hwnd; + + SET_ENABLE( ImeSetCompositionString, TRUE ); + + winetest_push_context( unicode ? "unicode" : "ansi" ); + + /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ + ime_info.fdwProperty = IME_PROP_END_UNLOAD; + if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) goto cleanup; + + hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_ret( 1, ImmLoadIME( hkl ) ); + himc = ImmCreateContext(); + ok_ne( NULL, himc, HIMC, "%p" ); + ctx = ImmLockIMC( himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + memset( buffer, 0xcd, sizeof(buffer) ); + todo_wine ok_ret( -2, ImmGetCompositionStringW( default_himc, GCS_COMPSTR | GCS_COMPATTR, buffer, sizeof(buffer) ) ); + memset( buffer, 0xcd, sizeof(buffer) ); + todo_wine ok_ret( -2, ImmGetCompositionStringA( default_himc, GCS_COMPSTR | GCS_COMPATTR, buffer, sizeof(buffer) ) ); + + for (i = 0; i < ARRAY_SIZE(gcs_indexes); ++i) + { + memset( buffer, 0xcd, sizeof(buffer) ); + ok_ret( 0, ImmGetCompositionStringW( default_himc, gcs_indexes[i], buffer, sizeof(buffer) ) ); + memset( buffer, 0xcd, sizeof(buffer) ); + ok_ret( 0, ImmGetCompositionStringA( default_himc, gcs_indexes[i], buffer, sizeof(buffer) ) ); + + memset( buffer, 0xcd, sizeof(buffer) ); + ok_ret( 0, ImmGetCompositionStringW( himc, gcs_indexes[i], buffer, sizeof(buffer) ) ); + memset( buffer, 0xcd, sizeof(buffer) ); + ok_ret( 0, ImmGetCompositionStringA( himc, gcs_indexes[i], buffer, sizeof(buffer) ) ); + } + + ctx->hCompStr = ImmReSizeIMCC( ctx->hCompStr, unicode ? expect_stringW.dwSize : expect_stringA.dwSize ); + string = ImmLockIMCC( ctx->hCompStr ); + ok( !!string, "ImmLockIMCC failed, error %lu\n", GetLastError() ); + check_composition_string( string, &expect_string_empty ); + + string->dwCursorPos = 3; + string->dwDeltaStart = 1; + + if (unicode) str = L"ReadComp"; + else str = "ReadComp"; + len = unicode ? wcslen( str ) : strlen( str ); + + string->dwCompReadStrLen = len; + string->dwCompReadStrOffset = string->dwSize; + dst = (BYTE *)string + string->dwCompReadStrOffset; + memcpy( dst, str, len * (unicode ? sizeof(WCHAR) : 1) ); + string->dwSize += len * (unicode ? sizeof(WCHAR) : 1); + + string->dwCompReadClauseLen = 2 * sizeof(DWORD); + string->dwCompReadClauseOffset = string->dwSize; + dst = (BYTE *)string + string->dwCompReadClauseOffset; + *(DWORD *)(dst + 0 * sizeof(DWORD)) = 0; + *(DWORD *)(dst + 1 * sizeof(DWORD)) = len; + string->dwSize += 2 * sizeof(DWORD); + + string->dwCompReadAttrLen = len; + string->dwCompReadAttrOffset = string->dwSize; + dst = (BYTE *)string + string->dwCompReadAttrOffset; + memset( dst, ATTR_INPUT, len ); + string->dwSize += len; + + if (unicode) str = L"Comp"; + else str = "Comp"; + len = unicode ? wcslen( str ) : strlen( str ); + + string->dwCompStrLen = len; + string->dwCompStrOffset = string->dwSize; + dst = (BYTE *)string + string->dwCompStrOffset; + memcpy( dst, str, len * (unicode ? sizeof(WCHAR) : 1) ); + string->dwSize += len * (unicode ? sizeof(WCHAR) : 1); + + string->dwCompClauseLen = 2 * sizeof(DWORD); + string->dwCompClauseOffset = string->dwSize; + dst = (BYTE *)string + string->dwCompClauseOffset; + *(DWORD *)(dst + 0 * sizeof(DWORD)) = 0; + *(DWORD *)(dst + 1 * sizeof(DWORD)) = len; + string->dwSize += 2 * sizeof(DWORD); + + string->dwCompAttrLen = len; + string->dwCompAttrOffset = string->dwSize; + dst = (BYTE *)string + string->dwCompAttrOffset; + memset( dst, ATTR_INPUT, len ); + string->dwSize += len; + + if (unicode) str = L"ReadResult"; + else str = "ReadResult"; + len = unicode ? wcslen( str ) : strlen( str ); + + string->dwResultReadStrLen = len; + string->dwResultReadStrOffset = string->dwSize; + dst = (BYTE *)string + string->dwResultReadStrOffset; + memcpy( dst, str, len * (unicode ? sizeof(WCHAR) : 1) ); + string->dwSize += len * (unicode ? sizeof(WCHAR) : 1); + + string->dwResultReadClauseLen = 2 * sizeof(DWORD); + string->dwResultReadClauseOffset = string->dwSize; + dst = (BYTE *)string + string->dwResultReadClauseOffset; + *(DWORD *)(dst + 0 * sizeof(DWORD)) = 0; + *(DWORD *)(dst + 1 * sizeof(DWORD)) = len; + string->dwSize += 2 * sizeof(DWORD); + + if (unicode) str = L"Result"; + else str = "Result"; + len = unicode ? wcslen( str ) : strlen( str ); + + string->dwResultStrLen = len; + string->dwResultStrOffset = string->dwSize; + dst = (BYTE *)string + string->dwResultStrOffset; + memcpy( dst, str, len * (unicode ? sizeof(WCHAR) : 1) ); + string->dwSize += len * (unicode ? sizeof(WCHAR) : 1); + + string->dwResultClauseLen = 2 * sizeof(DWORD); + string->dwResultClauseOffset = string->dwSize; + dst = (BYTE *)string + string->dwResultClauseOffset; + *(DWORD *)(dst + 0 * sizeof(DWORD)) = 0; + *(DWORD *)(dst + 1 * sizeof(DWORD)) = len; + string->dwSize += 2 * sizeof(DWORD); + + string->dwPrivateSize = 4; + string->dwPrivateOffset = string->dwSize; + dst = (BYTE *)string + string->dwPrivateOffset; + memset( dst, 0xa5, string->dwPrivateSize ); + string->dwSize += 4; + + check_composition_string( string, unicode ? &expect_stringW : &expect_stringA ); + ok_ret( 0, ImmUnlockIMCC( ctx->hCompStr ) ); + old_himcc = ctx->hCompStr; + + for (i = 0; i < ARRAY_SIZE(gcs_indexes); ++i) + { + UINT_PTR expect; + + winetest_push_context( "%u", i ); + + memset( buffer, 0xcd, sizeof(buffer) ); + expect = expect_retW[i]; + ok_ret( expect, ImmGetCompositionStringW( himc, gcs_indexes[i], buffer, sizeof(buffer) ) ); + memset( buffer + expect, 0, 4 ); + + if (i == 0) ok_wcs( L"ReadComp", (WCHAR *)buffer ); + else if (i == 3) ok_wcs( L"Comp", (WCHAR *)buffer ); + else if (i == 8) ok_wcs( L"ReadResult", (WCHAR *)buffer ); + else if (i == 10) ok_wcs( L"Result", (WCHAR *)buffer ); + else if (i != 6 && i != 7) ok_wcs( L"", (WCHAR *)buffer ); + + memset( buffer, 0xcd, sizeof(buffer) ); + expect = expect_retA[i]; + ok_ret( expect, ImmGetCompositionStringA( himc, gcs_indexes[i], buffer, sizeof(buffer) ) ); + memset( buffer + expect, 0, 4 ); + + if (i == 0) ok_str( "ReadComp", (char *)buffer ); + else if (i == 3) ok_str( "Comp", (char *)buffer ); + else if (i == 8) ok_str( "ReadResult", (char *)buffer ); + else if (i == 10) ok_str( "Result", (char *)buffer ); + else if (i != 6 && i != 7) ok_str( "", (char *)buffer ); + + winetest_pop_context(); + } + + for (i = 0; i < ARRAY_SIZE(gcs_indexes); ++i) + { + winetest_push_context( "%u", i ); + ok_ret( 0, ImmSetCompositionStringW( himc, gcs_indexes[i], buffer, sizeof(buffer), NULL, 0 ) ); + ok_ret( 0, ImmSetCompositionStringA( himc, gcs_indexes[i], buffer, sizeof(buffer), NULL, 0 ) ); + winetest_pop_context(); + } + ok_ret( 0, ImmSetCompositionStringW( himc, SCS_SETSTR | SCS_CHANGEATTR, buffer, sizeof(buffer), NULL, 0 ) ); + ok_ret( 0, ImmSetCompositionStringA( himc, SCS_SETSTR | SCS_CHANGEATTR, buffer, sizeof(buffer), NULL, 0 ) ); + ok_ret( 0, ImmSetCompositionStringW( himc, SCS_CHANGECLAUSE | SCS_CHANGEATTR, buffer, sizeof(buffer), NULL, 0 ) ); + ok_ret( 0, ImmSetCompositionStringA( himc, SCS_CHANGECLAUSE | SCS_CHANGEATTR, buffer, sizeof(buffer), NULL, 0 ) ); + + for (i = 0; i < ARRAY_SIZE(scs_indexes); ++i) + { + winetest_push_context( "%u", i ); + + if (scs_indexes[i] == SCS_CHANGECLAUSE) + { + memset( buffer, 0, sizeof(buffer) ); + *((DWORD *)buffer + 1) = 1; + len = 2 * sizeof(DWORD); + } + else if (scs_indexes[i] == SCS_CHANGEATTR) + { + memset( buffer, 0xcd, sizeof(buffer) ); + len = expect_stringW.dwCompAttrLen; + } + else if (scs_indexes[i] == SCS_SETSTR) + { + wcscpy( (WCHAR *)buffer, L"CompString" ); + len = 11 * sizeof(WCHAR); + } + + todo_ImeSetCompositionString = !unicode; + SET_EXPECT( ImeSetCompositionString ); + ok_ret( 1, ImmSetCompositionStringW( himc, scs_indexes[i], buffer, len, NULL, 0 ) ); + CHECK_CALLED( ImeSetCompositionString ); + todo_ImeSetCompositionString = FALSE; + ok_seq( empty_sequence ); + + string = ImmLockIMCC( ctx->hCompStr ); + ok_ne( NULL, string, COMPOSITIONSTRING *, "%p" ); + check_composition_string( string, unicode ? &expect_stringW : &expect_stringA ); + ok_ret( 0, ImmUnlockIMCC( ctx->hCompStr ) ); + + if (scs_indexes[i] == SCS_CHANGECLAUSE) + { + memset( buffer, 0, sizeof(buffer) ); + *((DWORD *)buffer + 1) = 1; + len = 2 * sizeof(DWORD); + } + else if (scs_indexes[i] == SCS_CHANGEATTR) + { + memset( buffer, 0xcd, sizeof(buffer) ); + len = expect_stringA.dwCompAttrLen; + } + else if (scs_indexes[i] == SCS_SETSTR) + { + strcpy( buffer, "CompString" ); + len = 11; + } + + todo_ImeSetCompositionString = unicode; + SET_EXPECT( ImeSetCompositionString ); + ok_ret( 1, ImmSetCompositionStringA( himc, scs_indexes[i], buffer, len, NULL, 0 ) ); + CHECK_CALLED( ImeSetCompositionString ); + todo_ImeSetCompositionString = FALSE; + ok_seq( empty_sequence ); + + string = ImmLockIMCC( ctx->hCompStr ); + ok_ne( NULL, string, COMPOSITIONSTRING *, "%p" ); + check_composition_string( string, unicode ? &expect_stringW : &expect_stringA ); + ok_ret( 0, ImmUnlockIMCC( ctx->hCompStr ) ); + + winetest_pop_context(); + } + ok_seq( empty_sequence ); + + old_ctx = ctx; + ok_ret( 1, ImmUnlockIMC( himc ) ); + + /* composition strings are kept between IME selections */ + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + ctx = ImmLockIMC( himc ); + ok_eq( old_ctx, ctx, INPUTCONTEXT *, "%p" ); + ok_eq( old_himcc, ctx->hCompStr, HIMCC, "%p" ); + string = ImmLockIMCC( ctx->hCompStr ); + ok_ne( NULL, string, COMPOSITIONSTRING *, "%p" ); + *string = expect_string_empty; + ok_ret( 0, ImmUnlockIMCC( ctx->hCompStr ) ); + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_eq( old_himcc, ctx->hCompStr, HIMCC, "%p" ); + check_composition_string( string, &expect_string_empty ); + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + ok_eq( old_himcc, ctx->hCompStr, HIMCC, "%p" ); + check_composition_string( string, &expect_string_empty ); + + ok_ret( 1, ImmUnlockIMC( himc ) ); + ok_ret( 1, ImmDestroyContext( himc ) ); + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + + ok_ret( 1, ImmFreeLayout( hkl ) ); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + +cleanup: + winetest_pop_context(); + SET_ENABLE( ImeSetCompositionString, FALSE ); +} + +static void test_ImmSetCompositionWindow(void) +{ + struct ime_call set_composition_window_0_seq[] = + { + { + .hkl = expect_ime, .himc = 0/*himc*/, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETCOMPOSITIONWINDOW}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCOMPOSITIONWINDOW}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCOMPOSITIONWINDOW}, + }, + {0}, + }; + struct ime_call set_composition_window_1_seq[] = + { + { + .hkl = expect_ime, .himc = 0/*himc*/, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETCOMPOSITIONWINDOW}, + }, + {0}, + }; + COMPOSITIONFORM comp_form, expect_form = + { + .dwStyle = 0xfeedcafe, + .ptCurrentPos = {.x = 123, .y = 456}, + .rcArea = {.left = 1, .top = 2, .right = 3, .bottom = 4}, + }; + struct ime_windows ime_windows = {0}; + INPUTCONTEXT *ctx; + HIMC himc; + HWND hwnd; + HKL hkl; + + ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) return; + + hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_ret( 1, ImmLoadIME( hkl ) ); + himc = ImmCreateContext(); + ok_ne( NULL, himc, HIMC, "%p" ); + ctx = ImmLockIMC( himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + set_composition_window_0_seq[0].himc = himc; + set_composition_window_1_seq[0].himc = himc; + + ok_ret( 1, EnumThreadWindows( GetCurrentThreadId(), enum_thread_ime_windows, (LPARAM)&ime_windows ) ); + ok_ne( NULL, ime_windows.ime_hwnd, HWND, "%p" ); + ok_ne( NULL, ime_windows.ime_ui_hwnd, HWND, "%p" ); + + ctx->cfCompForm = expect_form; + ctx->fdwInit = ~INIT_COMPFORM; + memset( &comp_form, 0xcd, sizeof(comp_form) ); + ok_ret( 0, ImmGetCompositionWindow( himc, &comp_form ) ); + ok_eq( 0xcdcdcdcd, comp_form.dwStyle, UINT, "%#x" ); + ctx->fdwInit = INIT_COMPFORM; + ok_ret( 1, ImmGetCompositionWindow( himc, &comp_form ) ); + check_composition_form( &comp_form, &expect_form ); + ok_seq( empty_sequence ); + ok_ret( 0, IsWindowVisible( ime_windows.ime_ui_hwnd ) ); + + ok_ret( 0, ShowWindow( ime_windows.ime_ui_hwnd, SW_SHOWNOACTIVATE ) ); + process_messages(); + ok_seq( empty_sequence ); + check_WM_SHOWWINDOW = TRUE; + + ctx->hWnd = hwnd; + ctx->fdwInit = 0; + memset( &comp_form, 0xcd, sizeof(comp_form) ); + ok_ret( 1, ImmSetCompositionWindow( himc, &comp_form ) ); + process_messages(); + ok_seq( set_composition_window_0_seq ); + ok_eq( INIT_COMPFORM, ctx->fdwInit, UINT, "%u" ); + check_composition_form( &ctx->cfCompForm, &comp_form ); + ok_ret( 1, IsWindowVisible( ime_windows.ime_ui_hwnd ) ); + check_WM_SHOWWINDOW = FALSE; + + ShowWindow( ime_windows.ime_ui_hwnd, SW_HIDE ); + process_messages(); + ok_seq( empty_sequence ); + + ok_ret( 1, ImmSetCompositionWindow( himc, &expect_form ) ); + ok_seq( set_composition_window_0_seq ); + check_composition_form( &ctx->cfCompForm, &expect_form ); + + ctx->cfCompForm = expect_form; + ok_ret( 1, ImmGetCompositionWindow( himc, &comp_form ) ); + check_composition_form( &comp_form, &expect_form ); + ok_seq( empty_sequence ); + + ctx->hWnd = 0; + memset( &comp_form, 0xcd, sizeof(comp_form) ); + ok_ret( 1, ImmSetCompositionWindow( himc, &comp_form ) ); + ok_seq( set_composition_window_1_seq ); + check_composition_form( &ctx->cfCompForm, &comp_form ); + + ok_ret( 1, ImmUnlockIMC( himc ) ); + ok_ret( 1, ImmDestroyContext( himc ) ); + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + + ok_ret( 1, ImmFreeLayout( hkl ) ); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; +} + +static void test_ImmSetStatusWindowPos(void) +{ + struct ime_call set_status_window_pos_0_seq[] = + { + { + .hkl = expect_ime, .himc = 0/*himc*/, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETSTATUSWINDOWPOS}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETSTATUSWINDOWPOS}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETSTATUSWINDOWPOS}, + }, + {0}, + }; + struct ime_call set_status_window_pos_1_seq[] = + { + { + .hkl = expect_ime, .himc = 0/*himc*/, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETSTATUSWINDOWPOS}, + }, + {0}, + }; + INPUTCONTEXT *ctx; + POINT pos; + HIMC himc; + HWND hwnd; + HKL hkl; + + ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) return; + + hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_ret( 1, ImmLoadIME( hkl ) ); + himc = ImmCreateContext(); + ok_ne( NULL, himc, HIMC, "%p" ); + ctx = ImmLockIMC( himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + set_status_window_pos_0_seq[0].himc = himc; + set_status_window_pos_1_seq[0].himc = himc; + + memset( &pos, 0xcd, sizeof(pos) ); + ctx->ptStatusWndPos.x = 0xdeadbeef; + ctx->ptStatusWndPos.y = 0xfeedcafe; + ctx->fdwInit = ~INIT_STATUSWNDPOS; + ok_ret( 0, ImmGetStatusWindowPos( himc, &pos ) ); + ok_eq( 0xcdcdcdcd, pos.x, UINT, "%u" ); + ok_eq( 0xcdcdcdcd, pos.y, UINT, "%u" ); + ctx->fdwInit = INIT_STATUSWNDPOS; + ok_ret( 1, ImmGetStatusWindowPos( himc, &pos ) ); + ok_eq( 0xdeadbeef, pos.x, UINT, "%u" ); + ok_eq( 0xfeedcafe, pos.y, UINT, "%u" ); + ok_seq( empty_sequence ); + + pos.x = 123; + pos.y = 456; + ctx->hWnd = hwnd; + ctx->fdwInit = 0; + ok_ret( 1, ImmSetStatusWindowPos( himc, &pos ) ); + ok_seq( set_status_window_pos_0_seq ); + ok_eq( INIT_STATUSWNDPOS, ctx->fdwInit, UINT, "%u" ); + + ok_ret( 1, ImmSetStatusWindowPos( himc, &pos ) ); + ok_seq( set_status_window_pos_0_seq ); + ok_ret( 1, ImmGetStatusWindowPos( himc, &pos ) ); + ok_eq( 123, pos.x, UINT, "%u" ); + ok_eq( 123, ctx->ptStatusWndPos.x, UINT, "%u" ); + ok_eq( 456, pos.y, UINT, "%u" ); + ok_eq( 456, ctx->ptStatusWndPos.y, UINT, "%u" ); + ok_seq( empty_sequence ); + + ctx->hWnd = 0; + ok_ret( 1, ImmSetStatusWindowPos( himc, &pos ) ); + ok_seq( set_status_window_pos_1_seq ); + + ok_ret( 1, ImmUnlockIMC( himc ) ); + ok_ret( 1, ImmDestroyContext( himc ) ); + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + + ok_ret( 1, ImmFreeLayout( hkl ) ); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; +} + +static void test_ImmSetCompositionFont( BOOL unicode ) +{ + struct ime_call set_composition_font_0_seq[] = + { + { + .hkl = expect_ime, .himc = 0/*himc*/, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETCOMPOSITIONFONT}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCOMPOSITIONFONT}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCOMPOSITIONFONT}, + }, + {0}, + }; + struct ime_call set_composition_font_1_seq[] = + { + { + .hkl = expect_ime, .himc = 0/*himc*/, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETCOMPOSITIONFONT}, + }, + {0}, + }; + LOGFONTW fontW, expect_fontW = + { + .lfHeight = 1, + .lfWidth = 2, + .lfEscapement = 3, + .lfOrientation = 4, + .lfWeight = 5, + .lfItalic = 6, + .lfUnderline = 7, + .lfStrikeOut = 8, + .lfCharSet = 8, + .lfOutPrecision = 10, + .lfClipPrecision = 11, + .lfQuality = 12, + .lfPitchAndFamily = 13, + .lfFaceName = L"FontFace", + }; + LOGFONTA fontA, expect_fontA = + { + .lfHeight = 1, + .lfWidth = 2, + .lfEscapement = 3, + .lfOrientation = 4, + .lfWeight = 5, + .lfItalic = 6, + .lfUnderline = 7, + .lfStrikeOut = 8, + .lfCharSet = 8, + .lfOutPrecision = 10, + .lfClipPrecision = 11, + .lfQuality = 12, + .lfPitchAndFamily = 13, + .lfFaceName = "FontFace", + }; + INPUTCONTEXT *ctx; + HIMC himc; + HWND hwnd; + HKL hkl; + + winetest_push_context( unicode ? "unicode" : "ansi" ); + + /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ + ime_info.fdwProperty = IME_PROP_END_UNLOAD; + if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) goto cleanup; + + hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_ret( 1, ImmLoadIME( hkl ) ); + himc = ImmCreateContext(); + ok_ne( NULL, himc, HIMC, "%p" ); + ctx = ImmLockIMC( himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + set_composition_font_0_seq[0].himc = himc; + set_composition_font_1_seq[0].himc = himc; + + memset( &fontW, 0xcd, sizeof(fontW) ); + memset( &fontA, 0xcd, sizeof(fontA) ); + + if (unicode) ctx->lfFont.W = expect_fontW; + else ctx->lfFont.A = expect_fontA; + ctx->fdwInit = ~INIT_LOGFONT; + ok_ret( 0, ImmGetCompositionFontW( himc, &fontW ) ); + ok_ret( 0, ImmGetCompositionFontA( himc, &fontA ) ); + ctx->fdwInit = INIT_LOGFONT; + ok_ret( 1, ImmGetCompositionFontW( himc, &fontW ) ); + check_logfont_w( &fontW, &expect_fontW ); + ok_ret( 1, ImmGetCompositionFontA( himc, &fontA ) ); + check_logfont_a( &fontA, &expect_fontA ); + + ctx->hWnd = hwnd; + ctx->fdwInit = 0; + memset( &ctx->lfFont, 0xcd, sizeof(ctx->lfFont) ); + ok_ret( 1, ImmSetCompositionFontW( himc, &expect_fontW ) ); + ok_eq( INIT_LOGFONT, ctx->fdwInit, UINT, "%u" ); + ok_seq( set_composition_font_0_seq ); + ok_ret( 1, ImmSetCompositionFontW( himc, &expect_fontW ) ); + ok_seq( set_composition_font_0_seq ); + if (unicode) check_logfont_w( &ctx->lfFont.W, &expect_fontW ); + else check_logfont_a( &ctx->lfFont.A, &expect_fontA ); + + ok_ret( 1, ImmGetCompositionFontW( himc, &fontW ) ); + check_logfont_w( &fontW, &expect_fontW ); + ok_ret( 1, ImmGetCompositionFontA( himc, &fontA ) ); + check_logfont_a( &fontA, &expect_fontA ); + + ctx->hWnd = hwnd; + ctx->fdwInit = 0; + memset( &ctx->lfFont, 0xcd, sizeof(ctx->lfFont) ); + ok_ret( 1, ImmSetCompositionFontA( himc, &expect_fontA ) ); + ok_eq( INIT_LOGFONT, ctx->fdwInit, UINT, "%u" ); + ok_seq( set_composition_font_0_seq ); + ok_ret( 1, ImmSetCompositionFontA( himc, &expect_fontA ) ); + ok_seq( set_composition_font_0_seq ); + if (unicode) check_logfont_w( &ctx->lfFont.W, &expect_fontW ); + else check_logfont_a( &ctx->lfFont.A, &expect_fontA ); + + ok_ret( 1, ImmGetCompositionFontW( himc, &fontW ) ); + check_logfont_w( &fontW, &expect_fontW ); + ok_ret( 1, ImmGetCompositionFontA( himc, &fontA ) ); + check_logfont_a( &fontA, &expect_fontA ); + + ctx->hWnd = 0; + ok_ret( 1, ImmSetCompositionFontW( himc, &expect_fontW ) ); + ok_seq( set_composition_font_1_seq ); + ok_ret( 1, ImmSetCompositionFontA( himc, &expect_fontA ) ); + ok_seq( set_composition_font_1_seq ); + + ok_ret( 1, ImmUnlockIMC( himc ) ); + ok_ret( 1, ImmDestroyContext( himc ) ); + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + + ok_ret( 1, ImmFreeLayout( hkl ) ); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + +cleanup: + winetest_pop_context(); +} + +static void test_ImmSetCandidateWindow(void) +{ + struct ime_call set_candidate_window_0_seq[] = + { + { + .hkl = expect_ime, .himc = 0/*himc*/, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETCANDIDATEPOS}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCANDIDATEPOS, .lparam = 4}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCANDIDATEPOS, .lparam = 4}, + }, + {0}, + }; + struct ime_call set_candidate_window_1_seq[] = + { + { + .hkl = expect_ime, .himc = 0/*himc*/, + .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETCANDIDATEPOS}, + }, + {0}, + }; + CANDIDATEFORM cand_form, expect_form = + { + .dwIndex = 2, .dwStyle = 0xfeedcafe, + .ptCurrentPos = {.x = 123, .y = 456}, + .rcArea = {.left = 1, .top = 2, .right = 3, .bottom = 4}, + }; + INPUTCONTEXT *ctx; + HIMC himc; + HWND hwnd; + HKL hkl; + + ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) return; + + hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_ret( 1, ImmLoadIME( hkl ) ); + himc = ImmCreateContext(); + ok_ne( NULL, himc, HIMC, "%p" ); + ctx = ImmLockIMC( himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + set_candidate_window_0_seq[0].himc = himc; + set_candidate_window_1_seq[0].himc = himc; + + ctx->cfCandForm[1] = expect_form; + ctx->cfCandForm[2] = expect_form; + ctx->fdwInit = 0; + memset( &cand_form, 0xcd, sizeof(cand_form) ); + ok_ret( 0, ImmGetCandidateWindow( himc, 0, &cand_form ) ); + ok_eq( 0xcdcdcdcd, cand_form.dwStyle, UINT, "%#x" ); + ok_ret( 1, ImmGetCandidateWindow( himc, 1, &cand_form ) ); + check_candidate_form( &cand_form, &expect_form ); + ok_ret( 1, ImmGetCandidateWindow( himc, 2, &cand_form ) ); + check_candidate_form( &cand_form, &expect_form ); + ok_seq( empty_sequence ); + + ctx->hWnd = hwnd; + memset( &cand_form, 0xcd, sizeof(cand_form) ); + cand_form.dwIndex = 2; + ok_ret( 1, ImmSetCandidateWindow( himc, &cand_form ) ); + ok_seq( set_candidate_window_0_seq ); + check_candidate_form( &ctx->cfCandForm[2], &cand_form ); + ok_eq( 0, ctx->fdwInit, UINT, "%u" ); + + ok_ret( 1, ImmSetCandidateWindow( himc, &expect_form ) ); + ok_seq( set_candidate_window_0_seq ); + check_candidate_form( &ctx->cfCandForm[2], &expect_form ); + + ctx->hWnd = 0; + memset( &cand_form, 0xcd, sizeof(cand_form) ); + cand_form.dwIndex = 2; + ok_ret( 1, ImmSetCandidateWindow( himc, &cand_form ) ); + ok_seq( set_candidate_window_1_seq ); + check_candidate_form( &ctx->cfCandForm[2], &cand_form ); + + ok_ret( 1, ImmUnlockIMC( himc ) ); + ok_ret( 1, ImmDestroyContext( himc ) ); + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + + ok_ret( 1, ImmFreeLayout( hkl ) ); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; +} + +static void test_ImmGenerateMessage(void) +{ + const struct ime_call generate_sequence[] = + { + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_TEST_WIN, .message = {.msg = WM_IME_COMPOSITION, .wparam = 0, .lparam = GCS_COMPSTR}, + }, + { + .hkl = expect_ime, .himc = default_himc, + .func = MSG_IME_UI, .message = {.msg = WM_IME_COMPOSITION, .wparam = 0, .lparam = GCS_COMPSTR}, + }, + {0}, + }; + TRANSMSG *msgs, *tmp_msgs; + INPUTCONTEXT *ctx; + HIMC himc; + HWND hwnd; + HKL hkl; + + ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; + + if (!(hkl = wineime_hkl)) return; + + hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_ret( 1, ImmLoadIME( hkl ) ); + himc = ImmCreateContext(); + ok_ne( NULL, himc, HIMC, "%p" ); + ctx = ImmLockIMC( himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + todo_wine ok_ret( 4, ImmGetIMCCSize( ctx->hMsgBuf ) ); + ctx->hMsgBuf = ImmReSizeIMCC( ctx->hMsgBuf, sizeof(*msgs) ); + ok_ne( NULL, ctx->hMsgBuf, HIMCC, "%p" ); + + msgs = ImmLockIMCC( ctx->hMsgBuf ); + ok_ne( NULL, msgs, TRANSMSG *, "%p" ); + msgs[0].message = WM_IME_COMPOSITION; + msgs[0].wParam = 0; + msgs[0].lParam = GCS_COMPSTR; + ok_ret( 0, ImmUnlockIMCC( ctx->hMsgBuf ) ); + + ctx->hWnd = 0; + ctx->dwNumMsgBuf = 0; + ok_ret( 1, ImmGenerateMessage( himc ) ); + ok_seq( empty_sequence ); + ok_ret( sizeof(*msgs), ImmGetIMCCSize( ctx->hMsgBuf ) ); + tmp_msgs = ImmLockIMCC( ctx->hMsgBuf ); + ok_eq( msgs, tmp_msgs, TRANSMSG *, "%p" ); + ok_ret( 0, ImmUnlockIMCC( ctx->hMsgBuf ) ); + + ctx->dwNumMsgBuf = 1; + ok_ret( 1, ImmGenerateMessage( himc ) ); + ok_seq( empty_sequence ); + ok_eq( 0, ctx->dwNumMsgBuf, UINT, "%u" ); + ok_ret( sizeof(*msgs), ImmGetIMCCSize( ctx->hMsgBuf ) ); + tmp_msgs = ImmLockIMCC( ctx->hMsgBuf ); + ok_eq( msgs, tmp_msgs, TRANSMSG *, "%p" ); + ok_ret( 0, ImmUnlockIMCC( ctx->hMsgBuf ) ); + + ctx->hWnd = hwnd; + ctx->dwNumMsgBuf = 0; + ok_ret( 1, ImmGenerateMessage( himc ) ); + ok_seq( empty_sequence ); + ok_ret( sizeof(*msgs), ImmGetIMCCSize( ctx->hMsgBuf ) ); + tmp_msgs = ImmLockIMCC( ctx->hMsgBuf ); + ok_eq( msgs, tmp_msgs, TRANSMSG *, "%p" ); + ok_ret( 0, ImmUnlockIMCC( ctx->hMsgBuf ) ); + + ctx->dwNumMsgBuf = 1; + ok_ret( 1, ImmGenerateMessage( himc ) ); + ok_seq( generate_sequence ); + ok_eq( 0, ctx->dwNumMsgBuf, UINT, "%u" ); + ok_ret( sizeof(*msgs), ImmGetIMCCSize( ctx->hMsgBuf ) ); + tmp_msgs = ImmLockIMCC( ctx->hMsgBuf ); + ok_eq( msgs, tmp_msgs, TRANSMSG *, "%p" ); + ok_ret( 0, ImmUnlockIMCC( ctx->hMsgBuf ) ); + + tmp_msgs = ImmLockIMCC( ctx->hMsgBuf ); + ok_eq( msgs, tmp_msgs, TRANSMSG *, "%p" ); + ok_ret( 0, ImmUnlockIMCC( ctx->hMsgBuf ) ); + + ok_ret( 1, ImmUnlockIMC( himc ) ); + ok_ret( 1, ImmDestroyContext( himc ) ); + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + + ok_ret( 1, ImmFreeLayout( hkl ) ); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; +} + +static void test_ImmTranslateMessage( BOOL kbd_char_first ) +{ + const struct ime_call process_key_seq[] = + { + { + .hkl = expect_ime, .himc = default_himc, .func = IME_PROCESS_KEY, + .process_key = {.vkey = 'Q', .lparam = MAKELONG(2, 0x10)}, + }, + { + .hkl = expect_ime, .himc = default_himc, .func = IME_PROCESS_KEY, + .process_key = {.vkey = 'Q', .lparam = MAKELONG(2, 0xc010)}, + }, + {0}, + }; + const struct ime_call to_ascii_ex_0[] = + { + { + .hkl = expect_ime, .himc = default_himc, .func = IME_TO_ASCII_EX, + .to_ascii_ex = {.vkey = kbd_char_first ? MAKELONG('Q', 'q') : 'Q', .vsc = 0x10}, + }, + {0}, + }; + const struct ime_call to_ascii_ex_1[] = + { + { + .hkl = expect_ime, .himc = default_himc, .func = IME_PROCESS_KEY, + .process_key = {.vkey = 'Q', .lparam = MAKELONG(2, 0xc010)}, + }, + { + .hkl = expect_ime, .himc = default_himc, .func = IME_TO_ASCII_EX, + /* FIXME what happened to kbd_char_first here!? */ + .to_ascii_ex = {.vkey = 'Q', .vsc = 0xc010}, + .todo_value = TRUE, + }, + {0}, + }; + struct ime_call to_ascii_ex_2[] = + { + { + .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_PROCESS_KEY, + .process_key = {.vkey = 'Q', .lparam = MAKELONG(2, 0x210)}, + }, + { + .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_TO_ASCII_EX, + .to_ascii_ex = {.vkey = kbd_char_first ? MAKELONG('Q', 'q') : 'Q', .vsc = 0x210}, + }, + { + .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_PROCESS_KEY, + .process_key = {.vkey = 'Q', .lparam = MAKELONG(2, 0x410)}, + }, + { + .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_TO_ASCII_EX, + .to_ascii_ex = {.vkey = kbd_char_first ? MAKELONG('Q', 'q') : 'Q', .vsc = 0x410}, + }, + {0}, + }; + struct ime_call to_ascii_ex_3[] = + { + { + .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_PROCESS_KEY, + .process_key = {.vkey = 'Q', .lparam = MAKELONG(2, 0xa10)}, + }, + { + .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_TO_ASCII_EX, + .to_ascii_ex = {.vkey = kbd_char_first ? MAKELONG('Q', 'q') : 'Q', .vsc = 0xa10}, + }, + { + .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_PROCESS_KEY, + .process_key = {.vkey = 'Q', .lparam = MAKELONG(2, 0xc10)}, + }, + { + .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_TO_ASCII_EX, + .to_ascii_ex = {.vkey = kbd_char_first ? MAKELONG('Q', 'q') : 'Q', .vsc = 0xc10}, + }, + {0}, + }; + struct ime_call post_messages[] = + { + {.hkl = expect_ime, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 1}}, + {.hkl = expect_ime, .himc = 0/*himc*/, .func = MSG_IME_UI, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 1}}, + {.hkl = expect_ime, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 1}}, + {.hkl = expect_ime, .himc = 0/*himc*/, .func = MSG_IME_UI, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 1}}, + {0}, + }; + struct ime_call sent_messages[] = + { + {.hkl = expect_ime, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 2}}, + {.hkl = expect_ime, .himc = 0/*himc*/, .func = MSG_IME_UI, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 2}}, + {.hkl = expect_ime, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 2}}, + {.hkl = expect_ime, .himc = 0/*himc*/, .func = MSG_IME_UI, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 2}}, + {0}, + }; + HWND hwnd, other_hwnd; + INPUTCONTEXT *ctx; + HIMC himc; + HKL hkl; + UINT i; + + ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; + if (kbd_char_first) ime_info.fdwProperty |= IME_PROP_KBD_CHAR_FIRST; + + winetest_push_context( kbd_char_first ? "kbd_char_first" : "default" ); + + if (!(hkl = wineime_hkl)) goto cleanup; + + other_hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!other_hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + flush_events(); + + hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + flush_events(); + + ok_ret( 1, ImmActivateLayout( hkl ) ); + ok_ret( 1, ImmLoadIME( hkl ) ); + himc = ImmCreateContext(); + ok_ne( NULL, himc, HIMC, "%p" ); + ctx = ImmLockIMC( himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + process_messages(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + ok_ret( 2, ImmProcessKey( hwnd, expect_ime, 'Q', MAKELONG(2, 0x10), 0 ) ); + ok_ret( 2, ImmProcessKey( hwnd, expect_ime, 'Q', MAKELONG(2, 0xc010), 0 ) ); + + ok_ret( 0, ImmTranslateMessage( hwnd, 0, 0, 0 ) ); + ok_ret( 'Q', ImmGetVirtualKey( hwnd ) ); + ok_seq( process_key_seq ); + + ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, 'Q', MAKELONG(2, 0x10) ) ); + ok_ret( VK_PROCESSKEY, ImmGetVirtualKey( hwnd ) ); + ok_seq( to_ascii_ex_0 ); + + ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYUP, 'Q', MAKELONG(2, 0xc010) ) ); + ok_seq( empty_sequence ); + + ok_ret( 2, ImmProcessKey( hwnd, expect_ime, 'Q', MAKELONG(2, 0xc010), 0 ) ); + ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYUP, 'Q', MAKELONG(2, 0xc010) ) ); + ok_ret( VK_PROCESSKEY, ImmGetVirtualKey( hwnd ) ); + ok_seq( to_ascii_ex_1 ); + + ok_eq( default_himc, ImmAssociateContext( hwnd, himc ), HIMC, "%p" ); + ok_eq( default_himc, ImmAssociateContext( other_hwnd, himc ), HIMC, "%p" ); + for (i = 0; i < ARRAY_SIZE(to_ascii_ex_2); i++) to_ascii_ex_2[i].himc = himc; + for (i = 0; i < ARRAY_SIZE(to_ascii_ex_3); i++) to_ascii_ex_3[i].himc = himc; + for (i = 0; i < ARRAY_SIZE(post_messages); i++) post_messages[i].himc = himc; + for (i = 0; i < ARRAY_SIZE(sent_messages); i++) sent_messages[i].himc = himc; + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + ctx->hWnd = hwnd; + ok_ret( 2, ImmProcessKey( hwnd, expect_ime, 'Q', MAKELONG(2, 0x210), 0 ) ); + ok_ret( 1, ImmTranslateMessage( hwnd, WM_KEYUP, 'Q', MAKELONG(2, 0x210) ) ); + ok_ret( 2, ImmProcessKey( hwnd, expect_ime, 'Q', MAKELONG(2, 0x410), 0 ) ); + ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYUP, 'Q', MAKELONG(2, 0x410) ) ); + ok_ret( VK_PROCESSKEY, ImmGetVirtualKey( hwnd ) ); + ok_seq( to_ascii_ex_2 ); + process_messages(); + ok_seq( post_messages ); + ok_ret( 1, ImmGenerateMessage( himc ) ); + ok_seq( sent_messages ); + + ok_ret( 2, ImmProcessKey( hwnd, expect_ime, 'Q', MAKELONG(2, 0xa10), 0 ) ); + ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYUP, 'Q', MAKELONG(2, 0xa10) ) ); + ok_ret( 2, ImmProcessKey( hwnd, expect_ime, 'Q', MAKELONG(2, 0xc10), 0 ) ); + ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYUP, 'Q', MAKELONG(2, 0xc10) ) ); + ok_ret( VK_PROCESSKEY, ImmGetVirtualKey( hwnd ) ); + ok_seq( to_ascii_ex_3 ); + process_messages(); + ok_seq( empty_sequence ); + ok_ret( 1, ImmGenerateMessage( himc ) ); + ok_seq( sent_messages ); + + ctx->hWnd = 0; + ok_ret( 2, ImmProcessKey( other_hwnd, expect_ime, 'Q', MAKELONG(2, 0x210), 0 ) ); + ok_ret( 1, ImmTranslateMessage( other_hwnd, WM_KEYUP, 'Q', MAKELONG(2, 0x210) ) ); + ok_ret( 2, ImmProcessKey( other_hwnd, expect_ime, 'Q', MAKELONG(2, 0x410), 0 ) ); + ok_ret( 0, ImmTranslateMessage( other_hwnd, WM_KEYUP, 'Q', MAKELONG(2, 0x410) ) ); + ok_ret( VK_PROCESSKEY, ImmGetVirtualKey( other_hwnd ) ); + ok_seq( to_ascii_ex_2 ); + process_messages_( hwnd ); + ok_seq( empty_sequence ); + process_messages_( other_hwnd ); + ok_seq( post_messages ); + ok_ret( 1, ImmGenerateMessage( himc ) ); + ok_seq( empty_sequence ); + + ok_ret( 1, ImmUnlockIMC( himc ) ); + ok_ret( 1, ImmDestroyContext( himc ) ); + + ok_ret( 1, ImmActivateLayout( default_hkl ) ); + ok_ret( 1, DestroyWindow( other_hwnd ) ); + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + + ok_ret( 1, ImmFreeLayout( hkl ) ); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + +cleanup: + winetest_pop_context(); +} + +static void test_ga_na_da(void) +{ + /* These sequences have some additional WM_IME_NOTIFY messages with unknown wparam > IMN_PRIVATE */ + struct ime_call complete_seq[] = + { + /* G */ + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 0, .lparam = 0}}, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u3131", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x3131, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, + }, + + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x1b, .lparam = GCS_CURSORPOS|GCS_DELTASTART|GCS_COMPSTR|GCS_COMPATTR|GCS_COMPCLAUSE| + GCS_COMPREADSTR|GCS_COMPREADATTR|GCS_COMPREADCLAUSE}, + }, + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 0, .lparam = 0}}, + + /* G */ + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 0, .lparam = 0}}, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u3131", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x3131, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, + }, + /* A */ + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uac00", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac00, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, + }, + /* N */ + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uac04", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac04, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, + }, + /* A */ + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub098", .result = L"\uac00", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac00, .lparam = GCS_RESULTSTR}, + }, + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0xac00, .lparam = 0x1}}, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub098", .result = L"\uac00", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb098, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, + }, + /* D */ + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub09f", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb09f, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, + }, + /* A */ + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub2e4", .result = L"\ub098", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb098, .lparam = GCS_RESULTSTR}, + }, + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0xb098, .lparam = 0x1}}, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub2e4", .result = L"\ub098", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb2e4, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, + }, + /* RETURN */ + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 0, .lparam = 0}}, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"", .result = L"\ub2e4", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb2e4, .lparam = GCS_RESULTSTR}, + }, + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0xb2e4, .lparam = 0x1}}, + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_KEYDOWN, .wparam = 0xd, .lparam = 0x1c0001}}, + {0}, + }; + struct ime_call partial_g_seq[] = + { + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 0, .lparam = 0}}, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u3131", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x3131, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, + }, + {0}, + }; + struct ime_call partial_ga_seq[] = + { + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uac00", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac00, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, + }, + {0}, + }; + struct ime_call partial_n_seq[] = + { + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uac04", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac04, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, + }, + {0}, + }; + struct ime_call partial_na_seq[] = + { + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub098", .result = L"\uac00", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac00, .lparam = GCS_RESULTSTR}, + }, + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0xac00, .lparam = 0x1}}, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub098", .result = L"\uac00", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb098, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, + }, + {0}, + }; + struct ime_call partial_d_seq[] = + { + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub09f", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb09f, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, + }, + {0}, + }; + struct ime_call partial_da_seq[] = + { + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub2e4", .result = L"\ub098", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb098, .lparam = GCS_RESULTSTR}, + }, + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0xb098, .lparam = 0x1}}, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub2e4", .result = L"\ub098", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb2e4, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, + }, + {0}, + }; + struct ime_call partial_return_seq[] = + { + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 0, .lparam = 0}}, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"", .result = L"\ub2e4", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb2e4, .lparam = GCS_RESULTSTR}, + }, + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0xb2e4, .lparam = 0x1}}, + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_KEYDOWN, .wparam = 0xd, .lparam = 0x1c0001}}, + {0}, + }; + + INPUTCONTEXT *ctx; + HWND hwnd; + HIMC himc; + UINT i; + + /* this test doesn't work on Win32 / WoW64 */ + if (sizeof(void *) == 4 || default_hkl != (HKL)0x04120412 /* MS Korean IME */) + { + skip( "Got hkl %p, skipping Korean IME-specific test\n", default_hkl ); + process_messages(); + return; + } + + hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + flush_events(); + + himc = ImmCreateContext(); + ok_ne( NULL, himc, HIMC, "%p" ); + ctx = ImmLockIMC( himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + ok_eq( default_himc, ImmAssociateContext( hwnd, himc ), HIMC, "%p" ); + ok_ret( 1, ImmSetOpenStatus( himc, TRUE ) ); + ok_ret( 1, ImmSetConversionStatus( himc, IME_CMODE_FULLSHAPE | IME_CMODE_NATIVE, IME_SMODE_PHRASEPREDICT ) ); + flush_events(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + for (i = 0; i < ARRAY_SIZE(complete_seq); i++) complete_seq[i].himc = himc; + for (i = 0; i < ARRAY_SIZE(partial_g_seq); i++) partial_g_seq[i].himc = himc; + for (i = 0; i < ARRAY_SIZE(partial_ga_seq); i++) partial_ga_seq[i].himc = himc; + for (i = 0; i < ARRAY_SIZE(partial_n_seq); i++) partial_n_seq[i].himc = himc; + for (i = 0; i < ARRAY_SIZE(partial_na_seq); i++) partial_na_seq[i].himc = himc; + for (i = 0; i < ARRAY_SIZE(partial_d_seq); i++) partial_d_seq[i].himc = himc; + for (i = 0; i < ARRAY_SIZE(partial_da_seq); i++) partial_da_seq[i].himc = himc; + for (i = 0; i < ARRAY_SIZE(partial_return_seq); i++) partial_return_seq[i].himc = himc; + + keybd_event( 'R', 0x13, 0, 0 ); + flush_events(); + keybd_event( 'R', 0x13, KEYEVENTF_KEYUP, 0 ); + + keybd_event( VK_BACK, 0x0e, 0, 0 ); + flush_events(); + keybd_event( VK_BACK, 0x0e, KEYEVENTF_KEYUP, 0 ); + + keybd_event( 'R', 0x13, 0, 0 ); + flush_events(); + keybd_event( 'R', 0x13, KEYEVENTF_KEYUP, 0 ); + + keybd_event( 'K', 0x25, 0, 0 ); + flush_events(); + keybd_event( 'K', 0x25, KEYEVENTF_KEYUP, 0 ); + + keybd_event( 'S', 0x1f, 0, 0 ); + flush_events(); + keybd_event( 'S', 0x1f, KEYEVENTF_KEYUP, 0 ); + + keybd_event( 'K', 0x25, 0, 0 ); + flush_events(); + keybd_event( 'K', 0x25, KEYEVENTF_KEYUP, 0 ); + + keybd_event( 'E', 0x12, 0, 0 ); + flush_events(); + keybd_event( 'E', 0x12, KEYEVENTF_KEYUP, 0 ); + + keybd_event( 'K', 0x25, 0, 0 ); + flush_events(); + keybd_event( 'K', 0x25, KEYEVENTF_KEYUP, 0 ); + + keybd_event( VK_RETURN, 0x1c, 0, 0 ); + flush_events(); + keybd_event( VK_RETURN, 0x1c, KEYEVENTF_KEYUP, 0 ); + + flush_events(); + todo_wine ok_seq( complete_seq ); + + + /* Korean IME uses ImeProcessKey and posts messages */ + + todo_wine ok_ret( 2, ImmProcessKey( hwnd, default_hkl, 'R', MAKELONG(1, 0x13), 0 ) ); + ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, 'R', MAKELONG(1, 0x13) ) ); + ok_seq( empty_sequence ); + process_messages(); + todo_wine ok_seq( partial_g_seq ); + + /* Korean IME doesn't eat WM_KEYUP */ + + ok_ret( 0, ImmProcessKey( hwnd, default_hkl, 'R', MAKELONG(1, 0xc013), 0 ) ); + ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYUP, 'R', MAKELONG(1, 0xc013) ) ); + process_messages(); + ok_seq( empty_sequence ); + + todo_wine ok_ret( 2, ImmProcessKey( hwnd, default_hkl, 'K', MAKELONG(1, 0x25), 0 ) ); + ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, 'K', MAKELONG(1, 0x25) ) ); + process_messages(); + todo_wine ok_seq( partial_ga_seq ); + + todo_wine ok_ret( 2, ImmProcessKey( hwnd, default_hkl, 'S', MAKELONG(1, 0x1f), 0 ) ); + ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, 'S', MAKELONG(1, 0x1f) ) ); + process_messages(); + todo_wine ok_seq( partial_n_seq ); + + todo_wine ok_ret( 2, ImmProcessKey( hwnd, default_hkl, 'K', MAKELONG(1, 0x25), 0 ) ); + ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, 'K', MAKELONG(1, 0x25) ) ); + process_messages(); + todo_wine ok_seq( partial_na_seq ); + + todo_wine ok_ret( 2, ImmProcessKey( hwnd, default_hkl, 'E', MAKELONG(1, 0x12), 0 ) ); + ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, 'E', MAKELONG(1, 0x12) ) ); + process_messages(); + todo_wine ok_seq( partial_d_seq ); + + todo_wine ok_ret( 2, ImmProcessKey( hwnd, default_hkl, 'K', MAKELONG(1, 0x25), 0 ) ); + ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, 'K', MAKELONG(1, 0x25) ) ); + process_messages(); + todo_wine ok_seq( partial_da_seq ); + + todo_wine ok_ret( 2, ImmProcessKey( hwnd, default_hkl, VK_RETURN, MAKELONG(1, 0x1c), 0 ) ); + ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, VK_RETURN, MAKELONG(1, 0x1c) ) ); + process_messages(); + todo_wine ok_seq( partial_return_seq ); + + + ok_ret( 1, ImmSetConversionStatus( himc, 0, IME_SMODE_PHRASEPREDICT ) ); + ok_ret( 1, ImmSetOpenStatus( himc, FALSE ) ); + + ok_ret( 1, ImmUnlockIMC( himc ) ); + ok_ret( 1, ImmDestroyContext( himc ) ); + + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; +} + +static void test_nihongo_no(void) +{ + /* These sequences have some additional WM_IME_NOTIFY messages with wparam > IMN_PRIVATE */ + /* Some out-of-order WM_IME_REQUEST and WM_IME_NOTIFY messages are also ignored */ + struct ime_call complete_seq[] = + { + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 0, .lparam = 0}}, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uff4e", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xff4e, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, + }, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, + }, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b\uff48", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, + }, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b\u307b", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, + }, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b\u307b\uff4e", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, + }, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b\u307b\u3093\uff47", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, + }, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b\u307b\u3093\u3054", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, + }, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u65e5\u672c\u8a9e", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x65e5, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, + }, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"", .result = L"\u65e5\u672c\u8a9e", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x65e5, .lparam = GCS_RESULTSTR|GCS_RESULTCLAUSE|GCS_RESULTREADSTR|GCS_RESULTREADCLAUSE}, + }, + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0x65e5, .lparam = 0x1}}, + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0x672c, .lparam = 0x1}}, + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0x8a9e, .lparam = 0x1}}, + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 0, .lparam = 0}}, + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 0, .lparam = 0}}, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uff4e", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xff4e, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, + }, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306e", .result = L"", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306e, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, + }, + { + .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"", .result = L"\u306e", + .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306e, .lparam = GCS_RESULTSTR|GCS_RESULTCLAUSE|GCS_RESULTREADSTR|GCS_RESULTREADCLAUSE}, + }, + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0x306e, .lparam = 0x1}}, + {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 0, .lparam = 0}}, + {0}, + }; + + INPUTCONTEXT *ctx; + HWND hwnd; + HIMC himc; + UINT i; + + /* this test doesn't work on Win32 / WoW64 */ + if (sizeof(void *) == 4 || default_hkl != (HKL)0x04110411 /* MS Japanese IME */) + { + skip( "Got hkl %p, skipping Japanese IME-specific test\n", default_hkl ); + return; + } + + hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 100, 100, NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + flush_events(); + + himc = ImmCreateContext(); + ok_ne( NULL, himc, HIMC, "%p" ); + ctx = ImmLockIMC( himc ); + ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); + ok_eq( default_himc, ImmAssociateContext( hwnd, himc ), HIMC, "%p" ); + ok_ret( 1, ImmSetOpenStatus( himc, TRUE ) ); + ok_ret( 1, ImmSetConversionStatus( himc, IME_CMODE_FULLSHAPE | IME_CMODE_NATIVE, IME_SMODE_PHRASEPREDICT ) ); + flush_events(); + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; + + for (i = 0; i < ARRAY_SIZE(complete_seq); i++) complete_seq[i].himc = himc; + ignore_WM_IME_REQUEST = TRUE; + ignore_WM_IME_NOTIFY = TRUE; + + + keybd_event( 'N', 0x31, 0, 0 ); + flush_events(); + keybd_event( 'N', 0x31, KEYEVENTF_KEYUP, 0 ); + + keybd_event( 'I', 0x17, 0, 0 ); + flush_events(); + keybd_event( 'I', 0x17, KEYEVENTF_KEYUP, 0 ); + + keybd_event( 'H', 0x23, 0, 0 ); + flush_events(); + keybd_event( 'H', 0x23, KEYEVENTF_KEYUP, 0 ); + + keybd_event( 'O', 0x18, 0, 0 ); + flush_events(); + keybd_event( 'O', 0x18, KEYEVENTF_KEYUP, 0 ); + + keybd_event( 'N', 0x31, 0, 0 ); + flush_events(); + keybd_event( 'N', 0x31, KEYEVENTF_KEYUP, 0 ); + + keybd_event( 'G', 0x22, 0, 0 ); + flush_events(); + keybd_event( 'G', 0x22, KEYEVENTF_KEYUP, 0 ); + + keybd_event( 'O', 0x18, 0, 0 ); + flush_events(); + keybd_event( 'O', 0x18, KEYEVENTF_KEYUP, 0 ); + + keybd_event( VK_SPACE, 0x39, 0, 0 ); + flush_events(); + keybd_event( VK_SPACE, 0x39, KEYEVENTF_KEYUP, 0 ); + + keybd_event( 'N', 0x31, 0, 0 ); + flush_events(); + keybd_event( 'N', 0x31, KEYEVENTF_KEYUP, 0 ); + + keybd_event( 'O', 0x18, 0, 0 ); + flush_events(); + keybd_event( 'O', 0x18, KEYEVENTF_KEYUP, 0 ); + + keybd_event( VK_RETURN, 0x1c, 0, 0 ); + flush_events(); + keybd_event( VK_RETURN, 0x1c, KEYEVENTF_KEYUP, 0 ); + + flush_events(); + todo_wine ok_seq( complete_seq ); + + ignore_WM_IME_REQUEST = FALSE; + ignore_WM_IME_NOTIFY = FALSE; + + /* Japanese IME doesn't take input from ImmProcessKey */ + + ok_ret( 0, ImmProcessKey( hwnd, default_hkl, 'N', MAKELONG(1, 0x31), 0 ) ); + ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, 'N', MAKELONG(1, 0x31) ) ); + flush_events(); + ok_ret( 0, ImmProcessKey( hwnd, default_hkl, 'N', MAKELONG(1, 0xc031), 0 ) ); + ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYUP, 'N', MAKELONG(1, 0xc031) ) ); + flush_events(); + ok_seq( empty_sequence ); + + + ok_ret( 1, ImmSetConversionStatus( himc, 0, IME_SMODE_PHRASEPREDICT ) ); + ok_ret( 1, ImmSetOpenStatus( himc, FALSE ) ); + + ok_ret( 1, ImmUnlockIMC( himc ) ); + ok_ret( 1, ImmDestroyContext( himc ) ); + + ok_ret( 1, DestroyWindow( hwnd ) ); + process_messages(); + + memset( ime_calls, 0, sizeof(ime_calls) ); + ime_call_count = 0; +} + +START_TEST(imm32) +{ + default_hkl = GetKeyboardLayout( 0 ); + + test_class.hInstance = GetModuleHandleW( NULL ); + RegisterClassExW( &test_class ); + + if (!is_ime_enabled()) + { + win_skip("IME support not implemented\n"); + return; + } + + test_com_initialization(); + + test_ImmEnumInputContext(); + + test_ImmInstallIME(); + wineime_hkl = ime_install(); + + test_ImmGetDescription(); + test_ImmGetIMEFileName(); + test_ImmIsIME(); + test_ImmGetProperty(); + + test_ImmEscape( FALSE ); + test_ImmEscape( TRUE ); + test_ImmEnumRegisterWord( FALSE ); + test_ImmEnumRegisterWord( TRUE ); + test_ImmRegisterWord( FALSE ); + test_ImmRegisterWord( TRUE ); + test_ImmGetRegisterWordStyle( FALSE ); + test_ImmGetRegisterWordStyle( TRUE ); + test_ImmUnregisterWord( FALSE ); + test_ImmUnregisterWord( TRUE ); + + /* test these first to sanitize conversion / open statuses */ + test_ImmSetConversionStatus(); + test_ImmSetOpenStatus(); + ImeSelect_init_status = TRUE; + + test_ImmActivateLayout(); + test_ImmCreateInputContext(); + test_ImmProcessKey(); + test_DefWindowProc(); + test_ImmSetActiveContext(); + test_ImmRequestMessage(); + + test_ImmGetCandidateList( TRUE ); + test_ImmGetCandidateList( FALSE ); + test_ImmGetCandidateListCount( TRUE ); + test_ImmGetCandidateListCount( FALSE ); + test_ImmGetCandidateWindow(); + + test_ImmGetCompositionString( TRUE ); + test_ImmGetCompositionString( FALSE ); + test_ImmSetCompositionWindow(); + test_ImmSetStatusWindowPos(); + test_ImmSetCompositionFont( TRUE ); + test_ImmSetCompositionFont( FALSE ); + test_ImmSetCandidateWindow(); + + test_ImmGenerateMessage(); + test_ImmTranslateMessage( FALSE ); + test_ImmTranslateMessage( TRUE ); + + if (wineime_hkl) ime_cleanup( wineime_hkl, TRUE ); + + test_ga_na_da(); + test_nihongo_no(); if (init()) { test_ImmNotifyIME(); - test_ImmGetCompositionString(); - test_ImmSetCompositionString(); + test_SCS_SETSTR(); test_ImmIME(); test_ImmAssociateContextEx(); test_NtUserAssociateInputContext(); - test_ImmThreads(); + test_cross_thread_himc(); test_ImmIsUIMessage(); test_ImmGetContext(); - test_ImmGetDescription(); test_ImmDefaultHwnd(); test_default_ime_window_creation(); test_ImmGetIMCLockCount(); @@ -2480,4 +7759,6 @@ START_TEST(imm32) { test_ImmDisableIME(); } cleanup(); + + UnregisterClassW( test_class.lpszClassName, test_class.hInstance ); } diff --git a/dlls/ir50_32/Makefile.in b/dlls/ir50_32/Makefile.in new file mode 100644 index 00000000000..2db9c8076c9 --- /dev/null +++ b/dlls/ir50_32/Makefile.in @@ -0,0 +1,8 @@ +MODULE = ir50_32.dll +IMPORTS = user32 mfplat mfuuid +DELAYIMPORTS = winegstreamer + +C_SRCS = \ + ir50.c + +RC_SRCS = ir50_32.rc diff --git a/dlls/ir50_32/ir50.c b/dlls/ir50_32/ir50.c new file mode 100644 index 00000000000..280983e58d8 --- /dev/null +++ b/dlls/ir50_32/ir50.c @@ -0,0 +1,415 @@ +/* + * Intel Indeo 5 Video Decoder + * Copyright 2023 Shaun Ren for CodeWeavers + * + * 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 + * + */ + +#include +#include +#include "windef.h" +#include "winbase.h" +#include "wingdi.h" +#include "winuser.h" +#include "commdlg.h" +#include "vfw.h" +#include "initguid.h" +#include "ir50_private.h" + +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(ir50_32); + +static HINSTANCE IR50_32_hModule; + +#define IV50_MAGIC mmioFOURCC('I','V','5','0') +#define compare_fourcc(fcc1, fcc2) (((fcc1)^(fcc2))&~0x20202020) + +DEFINE_MEDIATYPE_GUID(MFVideoFormat_IV50, MAKEFOURCC('I','V','5','0')); + +static inline UINT64 +make_uint64( UINT32 high, UINT32 low ) +{ + return ((UINT64)high << 32) | low; +} + + +static LRESULT +IV50_Open( const ICINFO *icinfo ) +{ + IMFTransform *decoder = NULL; + + TRACE("DRV_OPEN %p\n", icinfo); + + if ( icinfo && compare_fourcc( icinfo->fccType, ICTYPE_VIDEO ) ) + return 0; + + if ( FAILED(winegstreamer_create_video_decoder( &decoder )) ) + return 0; + + return (LRESULT)decoder; +} + +static LRESULT +IV50_DecompressQuery( LPBITMAPINFO in, LPBITMAPINFO out ) +{ + TRACE("ICM_DECOMPRESS_QUERY %p %p\n", in, out); + + TRACE("in->planes = %d\n", in->bmiHeader.biPlanes); + TRACE("in->bpp = %d\n", in->bmiHeader.biBitCount); + TRACE("in->height = %ld\n", in->bmiHeader.biHeight); + TRACE("in->width = %ld\n", in->bmiHeader.biWidth); + TRACE("in->compr = %#lx\n", in->bmiHeader.biCompression); + + if ( in->bmiHeader.biCompression != IV50_MAGIC ) + { + TRACE("can't do %#lx compression\n", in->bmiHeader.biCompression); + return ICERR_BADFORMAT; + } + + /* output must be same dimensions as input */ + if ( out ) + { + TRACE("out->planes = %d\n", out->bmiHeader.biPlanes); + TRACE("out->bpp = %d\n", out->bmiHeader.biBitCount); + TRACE("out->height = %ld\n", out->bmiHeader.biHeight); + TRACE("out->width = %ld\n", out->bmiHeader.biWidth); + TRACE("out->compr = %#lx\n", out->bmiHeader.biCompression); + + if ( out->bmiHeader.biCompression != BI_RGB ) + { + TRACE("incompatible compression requested\n"); + return ICERR_BADFORMAT; + } + + if ( out->bmiHeader.biBitCount != 32 && out->bmiHeader.biBitCount != 16 ) + { + TRACE("incompatible depth requested\n"); + return ICERR_BADFORMAT; + } + + if ( in->bmiHeader.biPlanes != out->bmiHeader.biPlanes || + in->bmiHeader.biHeight != abs(out->bmiHeader.biHeight) || + in->bmiHeader.biWidth != out->bmiHeader.biWidth ) + { + TRACE("incompatible output dimensions requested\n"); + return ICERR_BADFORMAT; + } + } + + return ICERR_OK; +} + +static LRESULT +IV50_DecompressGetFormat( LPBITMAPINFO in, LPBITMAPINFO out ) +{ + DWORD size; + + TRACE("ICM_DECOMPRESS_GETFORMAT %p %p\n", in, out); + + if ( !in ) + return ICERR_BADPARAM; + + if ( in->bmiHeader.biCompression != IV50_MAGIC ) + return ICERR_BADFORMAT; + + size = in->bmiHeader.biSize; + if ( out ) + { + memcpy( out, in, size ); + out->bmiHeader.biHeight = abs(in->bmiHeader.biHeight); + out->bmiHeader.biCompression = BI_RGB; + out->bmiHeader.biBitCount = 32; + out->bmiHeader.biSizeImage = out->bmiHeader.biWidth * out->bmiHeader.biHeight * 4; + return ICERR_OK; + } + + return size; +} + +static LRESULT IV50_DecompressBegin( IMFTransform *decoder, LPBITMAPINFO in, LPBITMAPINFO out ) +{ + IMFMediaType *input_type, *output_type; + const GUID *output_subtype; + LRESULT r = ICERR_INTERNAL; + unsigned int stride; + + TRACE("ICM_DECOMPRESS_BEGIN %p %p %p\n", decoder, in, out); + + if ( !decoder ) + return ICERR_BADPARAM; + + if ( out->bmiHeader.biBitCount == 32 ) + output_subtype = &MFVideoFormat_RGB32; + else if ( out->bmiHeader.biBitCount == 16 ) + output_subtype = &MFVideoFormat_RGB555; + else + return ICERR_BADFORMAT; + + stride = (out->bmiHeader.biWidth + 3) & ~3; + if (out->bmiHeader.biHeight >= 0) + stride = -stride; + + if ( FAILED(MFCreateMediaType( &input_type )) ) + return ICERR_INTERNAL; + + if ( FAILED(MFCreateMediaType( &output_type )) ) + { + IMFMediaType_Release( input_type ); + return ICERR_INTERNAL; + } + + if ( FAILED(IMFMediaType_SetGUID( input_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Video )) || + FAILED(IMFMediaType_SetGUID( input_type, &MF_MT_SUBTYPE, &MFVideoFormat_IV50 )) ) + goto done; + if ( FAILED(IMFMediaType_SetUINT64( + input_type, &MF_MT_FRAME_SIZE, + make_uint64( in->bmiHeader.biWidth, in->bmiHeader.biHeight ) )) ) + goto done; + + if ( FAILED(IMFMediaType_SetGUID( output_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Video )) || + FAILED(IMFMediaType_SetGUID( output_type, &MF_MT_SUBTYPE, output_subtype )) ) + goto done; + if ( FAILED(IMFMediaType_SetUINT64( + output_type, &MF_MT_FRAME_SIZE, + make_uint64( out->bmiHeader.biWidth, abs(out->bmiHeader.biHeight) ) )) ) + goto done; + if ( FAILED(IMFMediaType_SetUINT32( output_type, &MF_MT_DEFAULT_STRIDE, stride)) ) + goto done; + + if ( FAILED(IMFTransform_SetInputType( decoder, 0, input_type, 0 )) || + FAILED(IMFTransform_SetOutputType( decoder, 0, output_type, 0 )) ) + goto done; + + r = ICERR_OK; + +done: + IMFMediaType_Release( input_type ); + IMFMediaType_Release( output_type ); + return r; +} + +static LRESULT IV50_Decompress( IMFTransform *decoder, ICDECOMPRESS *icd, DWORD size ) +{ + IMFSample *in_sample = NULL, *out_sample = NULL; + IMFMediaBuffer *in_buf = NULL, *out_buf = NULL; + MFT_OUTPUT_DATA_BUFFER mft_buf; + DWORD mft_status; + BYTE *data; + HRESULT hr; + LRESULT r = ICERR_INTERNAL; + + TRACE("ICM_DECOMPRESS %p %p %lu\n", decoder, icd, size); + + if ( FAILED(MFCreateSample( &in_sample )) ) + return ICERR_INTERNAL; + + if ( FAILED(MFCreateMemoryBuffer( icd->lpbiInput->biSizeImage, &in_buf )) ) + goto done; + + if ( FAILED(IMFSample_AddBuffer( in_sample, in_buf )) ) + goto done; + + if ( FAILED(MFCreateSample( &out_sample )) ) + goto done; + + if ( FAILED(MFCreateMemoryBuffer( icd->lpbiOutput->biSizeImage, &out_buf )) ) + goto done; + + if ( FAILED(IMFSample_AddBuffer( out_sample, out_buf )) ) + goto done; + + if ( FAILED(IMFMediaBuffer_Lock( in_buf, &data, NULL, NULL ))) + goto done; + + memcpy( data, icd->lpInput, icd->lpbiInput->biSizeImage ); + + if ( FAILED(IMFMediaBuffer_Unlock( in_buf )) ) + goto done; + + if ( FAILED(IMFMediaBuffer_SetCurrentLength( in_buf, icd->lpbiInput->biSizeImage )) ) + goto done; + + if ( FAILED(IMFTransform_ProcessInput( decoder, 0, in_sample, 0 )) ) + goto done; + + memset( &mft_buf, 0, sizeof(mft_buf) ); + mft_buf.pSample = out_sample; + + hr = IMFTransform_ProcessOutput( decoder, 0, 1, &mft_buf, &mft_status ); + if ( SUCCEEDED(hr) && (mft_status & MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE) ) + hr = IMFTransform_ProcessOutput( decoder, 0, 1, &mft_buf, &mft_status ); + + if ( SUCCEEDED(hr) ) + { + LONG width = icd->lpbiOutput->biWidth * (icd->lpbiOutput->biBitCount / 8); + LONG height = abs( icd->lpbiOutput->biHeight ); + LONG stride = (width + 3) & ~3; + BYTE *output = (BYTE *)icd->lpOutput; + + if ( FAILED(IMFMediaBuffer_Lock( out_buf, &data, NULL, NULL ))) + goto done; + + MFCopyImage( output, stride, data, stride, width, height ); + + IMFMediaBuffer_Unlock( out_buf ); + r = ICERR_OK; + } + else if ( hr == MF_E_TRANSFORM_NEED_MORE_INPUT ) + { + TRACE("no output received.\n"); + r = ICERR_OK; + } + +done: + if ( in_buf ) + IMFMediaBuffer_Release( in_buf ); + if ( in_sample ) + IMFSample_Release( in_sample ); + if ( out_buf ) + IMFMediaBuffer_Release( out_buf ); + if ( out_sample ) + IMFSample_Release( out_sample ); + + return r; +} + +static LRESULT IV50_GetInfo( ICINFO *icinfo, DWORD dwSize ) +{ + TRACE("ICM_GETINFO %p %lu\n", icinfo, dwSize); + + if ( !icinfo ) return sizeof(ICINFO); + if ( dwSize < sizeof(ICINFO) ) return 0; + + icinfo->dwSize = sizeof(ICINFO); + icinfo->fccType = ICTYPE_VIDEO; + icinfo->fccHandler = IV50_MAGIC; + icinfo->dwFlags = 0; + icinfo->dwVersion = ICVERSION; + icinfo->dwVersionICM = ICVERSION; + + LoadStringW( IR50_32_hModule, IDS_NAME, icinfo->szName, ARRAY_SIZE(icinfo->szName) ); + LoadStringW( IR50_32_hModule, IDS_DESCRIPTION, icinfo->szDescription, ARRAY_SIZE(icinfo->szDescription) ); + /* msvfw32 will fill icinfo->szDriver for us */ + + return sizeof(ICINFO); +} + +/*********************************************************************** + * DriverProc (IR50_32.@) + */ +LRESULT WINAPI IV50_DriverProc( DWORD_PTR dwDriverId, HDRVR hdrvr, UINT msg, + LPARAM lParam1, LPARAM lParam2 ) +{ + IMFTransform *decoder = (IMFTransform *) dwDriverId; + LRESULT r = ICERR_UNSUPPORTED; + + TRACE("%Id %p %04x %08Ix %08Ix\n", dwDriverId, hdrvr, msg, lParam1, lParam2); + + switch( msg ) + { + case DRV_LOAD: + TRACE("DRV_LOAD\n"); + r = 1; + break; + + case DRV_OPEN: + r = IV50_Open((ICINFO *)lParam2); + break; + + case DRV_CLOSE: + TRACE("DRV_CLOSE\n"); + if ( decoder ) + IMFTransform_Release( decoder ); + r = 1; + break; + + case DRV_ENABLE: + case DRV_DISABLE: + case DRV_FREE: + break; + + case ICM_GETINFO: + r = IV50_GetInfo( (ICINFO *) lParam1, (DWORD) lParam2 ); + break; + + case ICM_DECOMPRESS_QUERY: + r = IV50_DecompressQuery( (LPBITMAPINFO) lParam1, (LPBITMAPINFO) lParam2 ); + break; + + case ICM_DECOMPRESS_GET_FORMAT: + r = IV50_DecompressGetFormat( (LPBITMAPINFO) lParam1, (LPBITMAPINFO) lParam2 ); + break; + + case ICM_DECOMPRESS_GET_PALETTE: + FIXME("ICM_DECOMPRESS_GET_PALETTE\n"); + break; + + case ICM_DECOMPRESS: + r = IV50_Decompress( decoder, (ICDECOMPRESS *) lParam1, (DWORD) lParam2 ); + break; + + case ICM_DECOMPRESS_BEGIN: + r = IV50_DecompressBegin( decoder, (LPBITMAPINFO) lParam1, (LPBITMAPINFO) lParam2 ); + break; + + case ICM_DECOMPRESS_END: + r = ICERR_UNSUPPORTED; + break; + + case ICM_DECOMPRESSEX_QUERY: + FIXME("ICM_DECOMPRESSEX_QUERY\n"); + break; + + case ICM_DECOMPRESSEX: + FIXME("ICM_DECOMPRESSEX\n"); + break; + + case ICM_COMPRESS_QUERY: + r = ICERR_BADFORMAT; + /* fall through */ + case ICM_COMPRESS_GET_FORMAT: + case ICM_COMPRESS_END: + case ICM_COMPRESS: + FIXME("compression not implemented\n"); + break; + + case ICM_CONFIGURE: + break; + + default: + FIXME("Unknown message: %04x %Id %Id\n", msg, lParam1, lParam2); + } + + return r; +} + +/*********************************************************************** + * DllMain + */ +BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID lpReserved) +{ + TRACE("(%p,%lu,%p)\n", hModule, dwReason, lpReserved); + + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hModule); + IR50_32_hModule = hModule; + break; + } + return TRUE; +} diff --git a/dlls/ir50_32/ir50_32.rc b/dlls/ir50_32/ir50_32.rc new file mode 100644 index 00000000000..fd98f76fbf6 --- /dev/null +++ b/dlls/ir50_32/ir50_32.rc @@ -0,0 +1,29 @@ +/* + * Copyright 2023 Shaun Ren for CodeWeavers + * + * 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 + */ + +#include "ir50_private.h" + +#pragma makedep po + +LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT + +STRINGTABLE +{ + IDS_NAME "Indeo5" + IDS_DESCRIPTION "Indeo Video Interactive version 5 video codec" +} diff --git a/dlls/ir50_32/ir50_32.spec b/dlls/ir50_32/ir50_32.spec new file mode 100644 index 00000000000..e5c54ef9c56 --- /dev/null +++ b/dlls/ir50_32/ir50_32.spec @@ -0,0 +1 @@ +@ stdcall -private DriverProc(long long long long long) IV50_DriverProc diff --git a/dlls/ir50_32/ir50_private.h b/dlls/ir50_32/ir50_private.h new file mode 100644 index 00000000000..c0b96bc17e4 --- /dev/null +++ b/dlls/ir50_32/ir50_private.h @@ -0,0 +1,36 @@ +/* + * Copyright 2023 Shaun Ren for CodeWeavers + * + * 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 + */ + +#ifndef __IR50_PRIVATE_H +#define __IR50_PRIVATE_H + +#include + +#define COBJMACROS +#include "mfapi.h" +#include "mferror.h" +#include "mfobjects.h" +#include "mfidl.h" +#include "mftransform.h" + +#define IDS_NAME 100 +#define IDS_DESCRIPTION 101 + +HRESULT WINAPI winegstreamer_create_video_decoder(IMFTransform **out); + +#endif /* __IR50_PRIVATE_H */ diff --git a/dlls/kernel32/kernel32.spec b/dlls/kernel32/kernel32.spec index 1d6dcc11f77..c9172e8022e 100644 --- a/dlls/kernel32/kernel32.spec +++ b/dlls/kernel32/kernel32.spec @@ -1606,6 +1606,7 @@ @ stdcall WTSGetActiveConsoleSessionId() @ stdcall -import WaitCommEvent(long ptr ptr) @ stdcall -import WaitForDebugEvent(ptr long) +@ stdcall -import WaitForDebugEventEx(ptr long) @ stdcall -import WaitForMultipleObjects(long ptr long long) @ stdcall -import WaitForMultipleObjectsEx(long ptr long long long) @ stdcall -import WaitForSingleObject(long long) diff --git a/dlls/kernel32/tests/actctx.c b/dlls/kernel32/tests/actctx.c index bec04b7f066..fe4b24cf20d 100644 --- a/dlls/kernel32/tests/actctx.c +++ b/dlls/kernel32/tests/actctx.c @@ -506,6 +506,23 @@ static const char settings_manifest3[] = " " ""; +static const char settings_manifest4[] = +"" +" " +" " +" " +" " +" true" +" " +" " +" " +" " +" " +" true" +" " +" " +""; + static const char two_dll_manifest_dll[] = "" " " @@ -3302,6 +3319,23 @@ static void test_settings(void) ok( !ret, "QueryActCtxSettingsW succeeded\n" ); ok( GetLastError() == ERROR_SXS_KEY_NOT_FOUND, "wrong error %lu\n", GetLastError() ); ReleaseActCtx(handle); + + /* lookup occurs in first non empty node */ + create_manifest_file( "manifest_settings4.manifest", settings_manifest4, -1, NULL, NULL ); + handle = test_create("manifest_settings4.manifest"); + ok( handle != INVALID_HANDLE_VALUE, "handle == INVALID_HANDLE_VALUE, error %lu\n", GetLastError() ); + DeleteFileA( "manifest_settings3.manifest" ); + SetLastError( 0xdeadbeef ); + size = 0xdead; + memset( buffer, 0xcc, sizeof(buffer) ); + ret = pQueryActCtxSettingsW( 0, handle, NULL, dpiAwareW, buffer, 80, &size ); + ok( ret, "QueryActCtxSettingsW failed\n" ); + SetLastError( 0xdeadbeef ); + size = 0xdead; + memset( buffer, 0xcc, sizeof(buffer) ); + ret = pQueryActCtxSettingsW( 0, handle, NULL, dpiAwarenessW, buffer, 80, &size ); + ok( !ret, "QueryActCtxSettingsW succeeded\n" ); + ReleaseActCtx(handle); } typedef struct diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index 73ae75d52af..66be69409b0 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -2963,38 +2963,43 @@ static void test_FindFirstFile_wildcards(void) int i; static const char* files[] = { "..a", "..a.a", ".a", ".a..a", ".a.a", ".aaa", - "a", "a..a", "a.a", "a.a.a", "aa", "aaa", "aaaa" + "a", "a..a", "a.a", "a.a.a", "aa", "aaa", "aaaa", " .a" }; static const struct { int todo; const char *pattern, *result; } tests[] = { - {0, "*.*.*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, - {0, "*.*.", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, + {0, "*.*.*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, + {0, "*.*.", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, {0, ".*.*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa'"}, - {0, "*.*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, + {0, "*.*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, {0, ".*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa'"}, - {1, "*.", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, - {0, "*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, + {0, "*.", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, + {0, "*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, {1, "*..*", ", '.', '..', '..a', '..a.a', '.a..a', 'a..a'"}, - {1, "*..", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, + {0, "*..", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, {1, ".*.", ", '.', '..', '.a', '.aaa'"}, {0, "..*", ", '.', '..', '..a', '..a.a'"}, - {0, "**", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, - {0, "**.", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, - {0, "*. ", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, - {1, "* .", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, - {0, "* . ", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, - {0, "*.. ", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, - {1, "*. .", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, - {1, "* ..", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, + {0, "**", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, + {0, "**.", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, + {0, "*. ", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, + {0, "* .", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, + {0, "* . ", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, + {0, "*.. ", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, + {0, "*. .", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, + {0, "* ..", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, {0, " *..", ""}, {0, "..* ", ", '.', '..', '..a', '..a.a'"}, + {1, "a*.", ", '..a', '.a', '.aaa', 'a', 'aa', 'aaa', 'aaaa'"}, + {0, "*a ", ", '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, + + /* a.a.a not found due to short name mismatch, a.a.a -> "AA6BF5~1.A on Windows. */ + {1, "*aa*", ", '.aaa', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, {1, "<.<.<", ", '..a', '..a.a', '.a..a', '.a.a', 'a..a', 'a.a.a'"}, - {1, "<.<.", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a..a', 'a.a', 'a.a.a'"}, + {1, "<.<.", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a..a', 'a.a', 'a.a.a', ' .a'"}, {1, ".<.<", ", '..a', '..a.a', '.a..a', '.a.a'"}, - {1, "<.<", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a..a', 'a.a', 'a.a.a'"}, + {1, "<.<", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a..a', 'a.a', 'a.a.a', ' .a'"}, {1, ".<", ", '.', '..', '.a', '.aaa'"}, {1, "<.", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, {1, "<", ", '.', '..', '..a', '.a', '.aaa', 'a', 'aa', 'aaa', 'aaaa'"}, @@ -3002,8 +3007,8 @@ static void test_FindFirstFile_wildcards(void) {1, "<..", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, {1, ".<.", ", '.', '..', '.a', '.aaa'"}, {1, "..<", ", '..a'"}, - {1, "<<", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, - {1, "<<.", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, + {1, "<<", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, + {1, "<<.", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, {1, "<. ", ", '.', '..', '..a', '.a', '.aaa', 'a', 'aa', 'aaa', 'aaaa'"}, {1, "< .", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, {1, "< . ", ", '.', '..', '..a', '.a', '.aaa', 'a', 'aa', 'aaa', 'aaaa'"}, @@ -3014,12 +3019,12 @@ static void test_FindFirstFile_wildcards(void) {1, "..< ", ", '..a'"}, {1, "?", ", '.', '..', 'a'"}, - {1, "?.", ", '.', '..', 'a'"}, - {1, "?. ", ", '.', '..', 'a'"}, + {0, "?.", ", '.', '..', 'a'"}, + {0, "?. ", ", '.', '..', 'a'"}, {1, "??.", ", '.', '..', 'a', 'aa'"}, {1, "??. ", ", '.', '..', 'a', 'aa'"}, {1, "???.", ", '.', '..', 'a', 'aa', 'aaa'"}, - {1, "?.??.", ", '.', '..', '.a', 'a', 'a.a'"}, + {1, "?.??.", ", '.', '..', '.a', 'a', 'a.a', ' .a'"}, {1, ">", ", '.', '..', 'a'"}, {1, ">.", ", '.', '..', 'a'"}, @@ -3027,7 +3032,7 @@ static void test_FindFirstFile_wildcards(void) {1, ">>.", ", '.', '..', 'a', 'aa'"}, {1, ">>. ", ", '.', '..', 'a', 'aa'"}, {1, ">>>.", ", '.', '..', 'a', 'aa', 'aaa'"}, - {1, ">.>>.", ", '.', '..', '.a', 'a.a'"}, + {1, ">.>>.", ", '.', '..', '.a', 'a.a', ' .a'"}, }; CreateDirectoryA("test-dir", NULL); diff --git a/dlls/kernelbase/debug.c b/dlls/kernelbase/debug.c index 652f1cf8809..8af51dd7d71 100644 --- a/dlls/kernelbase/debug.c +++ b/dlls/kernelbase/debug.c @@ -262,6 +262,11 @@ void WINAPI DECLSPEC_HOTPATCH OutputDebugStringA( LPCSTR str ) } } +static LONG WINAPI debug_exception_handler_wide( EXCEPTION_POINTERS *eptr ) +{ + EXCEPTION_RECORD *rec = eptr->ExceptionRecord; + return (rec->ExceptionCode == DBG_PRINTEXCEPTION_WIDE_C) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH; +} /*********************************************************************** * OutputDebugStringW (kernelbase.@) @@ -271,10 +276,32 @@ void WINAPI DECLSPEC_HOTPATCH OutputDebugStringW( LPCWSTR str ) UNICODE_STRING strW; STRING strA; + WARN( "%s\n", debugstr_w(str) ); + RtlInitUnicodeString( &strW, str ); if (!RtlUnicodeStringToAnsiString( &strA, &strW, TRUE )) { - OutputDebugStringA( strA.Buffer ); + BOOL exc_handled; + + __TRY + { + ULONG_PTR args[4]; + args[0] = wcslen(str) + 1; + args[1] = (ULONG_PTR)str; + args[2] = strlen(strA.Buffer) + 1; + args[3] = (ULONG_PTR)strA.Buffer; + RaiseException( DBG_PRINTEXCEPTION_WIDE_C, 0, 4, args ); + exc_handled = TRUE; + } + __EXCEPT(debug_exception_handler_wide) + { + exc_handled = FALSE; + } + __ENDTRY + + if (!exc_handled) + OutputDebugStringA( strA.Buffer ); + RtlFreeAnsiString( &strA ); } } diff --git a/dlls/kernelbase/file.c b/dlls/kernelbase/file.c index 0088bc8747b..a497904b9d3 100644 --- a/dlls/kernelbase/file.c +++ b/dlls/kernelbase/file.c @@ -60,6 +60,7 @@ typedef struct UINT data_pos; /* current position in dir data */ UINT data_len; /* length of dir data */ UINT data_size; /* size of data buffer, or 0 when everything has been read */ + WCHAR *mask; /* mask string to match if wildcards are used */ BYTE data[1]; /* directory data */ } FIND_FIRST_INFO; @@ -1127,7 +1128,7 @@ HANDLE WINAPI DECLSPEC_HOTPATCH FindFirstFileExW( LPCWSTR filename, FINDEX_INFO_ OBJECT_ATTRIBUTES attr; IO_STATUS_BLOCK io; NTSTATUS status; - DWORD size, device = 0; + DWORD size, mask_size = 0, device = 0; TRACE( "%s %d %p %d %p %lx\n", debugstr_w(filename), level, data, search_op, filter, flags ); @@ -1189,10 +1190,16 @@ HANDLE WINAPI DECLSPEC_HOTPATCH FindFirstFileExW( LPCWSTR filename, FINDEX_INFO_ { nt_name.Length = (mask - nt_name.Buffer) * sizeof(WCHAR); has_wildcard = wcspbrk( mask, L"*?" ) != NULL; - size = has_wildcard ? 8192 : max_entry_size; + if (has_wildcard) + { + size = 8192; + mask = PathFindFileNameW( filename ); + mask_size = (lstrlenW( mask ) + 1) * sizeof(*mask); + } + else size = max_entry_size; } - if (!(info = HeapAlloc( GetProcessHeap(), 0, offsetof( FIND_FIRST_INFO, data[size] )))) + if (!(info = HeapAlloc( GetProcessHeap(), 0, offsetof( FIND_FIRST_INFO, data[size + mask_size] )))) { SetLastError( ERROR_NOT_ENOUGH_MEMORY ); goto error; @@ -1236,6 +1243,13 @@ HANDLE WINAPI DECLSPEC_HOTPATCH FindFirstFileExW( LPCWSTR filename, FINDEX_INFO_ info->data_size = size; info->search_op = search_op; info->level = level; + if (mask_size) + { + info->mask = (WCHAR *)(info->data + size); + memcpy( info->mask, mask, mask_size ); + mask = NULL; + } + else info->mask = NULL; if (device) { @@ -1253,7 +1267,7 @@ HANDLE WINAPI DECLSPEC_HOTPATCH FindFirstFileExW( LPCWSTR filename, FINDEX_INFO_ RtlInitUnicodeString( &mask_str, mask ); status = NtQueryDirectoryFile( info->handle, 0, NULL, NULL, &io, info->data, info->data_size, - FileBothDirectoryInformation, FALSE, &mask_str, TRUE ); + FileBothDirectoryInformation, FALSE, has_wildcard ? NULL : &mask_str, TRUE ); if (status) { FindClose( info ); @@ -1336,6 +1350,95 @@ BOOL WINAPI DECLSPEC_HOTPATCH FindNextFileA( HANDLE handle, WIN32_FIND_DATAA *da } +/*********************************************************************** + * name_has_ext + * + * Check if the file name has extension (skipping leading dots). + */ +static BOOL name_has_ext( const WCHAR *name, const WCHAR *name_end ) +{ + while (name != name_end && *name == '.') ++name; + while (name != name_end && *name != '.') ++name; + return name != name_end; +} + + +/*********************************************************************** + * match_filename + * + * Check if the file name matches mask containing wildcards. + */ +static BOOL match_filename( const WCHAR *name, int length, const WCHAR *mask ) +{ + BOOL mismatch; + const WCHAR *name_end = name + length; + const WCHAR *mask_end = mask + lstrlenW( mask ); + const WCHAR *lastjoker = NULL; + const WCHAR *next_to_retry = NULL; + const WCHAR *asterisk; + + if (mask != mask_end && mask_end[-1] == '.' && (asterisk = wcschr( mask, '*' )) && asterisk == wcsrchr( mask, '*' ) + && name_has_ext( name, name_end )) + { + /* Single '*' mask ending with '.' only matches files without extension. */ + return FALSE; + } + + while (name < name_end && mask < mask_end) + { + switch(*mask) + { + case '*': + mask++; + while (mask < mask_end && *mask == '*') mask++; + if (mask == mask_end) return TRUE; /* end of mask is all '*', so match */ + lastjoker = mask; + + /* skip to the next match after the joker(s) */ + while (name < name_end && towupper( *name ) != towupper( *mask )) name++; + next_to_retry = name; + break; + case '?': + case '>': + mask++; + name++; + break; + default: + mismatch = towupper( *mask ) != towupper( *name ); + + if (!mismatch) + { + mask++; + name++; + if (mask == mask_end) + { + if (name == name_end) return TRUE; + if (lastjoker) mask = lastjoker; + } + } + else /* mismatch ! */ + { + if (lastjoker) /* we had an '*', so we can try unlimitedly */ + { + mask = lastjoker; + + /* this scan sequence was a mismatch, so restart + * 1 char after the first char we checked last time */ + next_to_retry++; + name = next_to_retry; + } + else return FALSE; + } + break; + } + } + + while (mask < mask_end && (*mask == ' ' || *mask == '.' || *mask == '*')) + mask++; + return (name == name_end && mask == mask_end); +} + + /****************************************************************************** * FindNextFileW (kernelbase.@) */ @@ -1395,6 +1498,14 @@ BOOL WINAPI DECLSPEC_HOTPATCH FindNextFileW( HANDLE handle, WIN32_FIND_DATAW *da dir_info->FileName[0] == '.' && dir_info->FileName[1] == '.') continue; } + if (info->mask) + { + if (!match_filename( dir_info->FileName, dir_info->FileNameLength / sizeof(WCHAR), info->mask ) + && (!dir_info->ShortNameLength + || !match_filename( dir_info->ShortName, dir_info->ShortNameLength / sizeof(WCHAR), info->mask ))) + continue; + } + data->dwFileAttributes = dir_info->FileAttributes; data->ftCreationTime = *(FILETIME *)&dir_info->CreationTime; data->ftLastAccessTime = *(FILETIME *)&dir_info->LastAccessTime; diff --git a/dlls/kernelbase/kernelbase.spec b/dlls/kernelbase/kernelbase.spec index c14f2a87472..9c3571260ab 100644 --- a/dlls/kernelbase/kernelbase.spec +++ b/dlls/kernelbase/kernelbase.spec @@ -1730,7 +1730,7 @@ # @ stub WTSIsServerContainer @ stdcall WaitCommEvent(long ptr ptr) @ stdcall WaitForDebugEvent(ptr long) -# @ stub WaitForDebugEventEx +@ stdcall WaitForDebugEventEx(ptr long) # @ stub WaitForMachinePolicyForegroundProcessingInternal @ stdcall WaitForMultipleObjects(long ptr long long) @ stdcall WaitForMultipleObjectsEx(long ptr long long long) diff --git a/dlls/kernelbase/process.c b/dlls/kernelbase/process.c index 7afd5f3a38e..3c52ef6fa3c 100644 --- a/dlls/kernelbase/process.c +++ b/dlls/kernelbase/process.c @@ -594,6 +594,7 @@ static const WCHAR *hack_append_command_line( const WCHAR *cmd ) {L"PaladinLias\\Game.exe", L" --use-gl=desktop"}, {L"EverQuest 2\\LaunchPad.exe", L" --use-gl=swiftshader"}, {L"Everquest F2P\\LaunchPad.exe", L" --use-gl=swiftshader"}, + {L"Red Tie Runner.exe", L" --use-angle=gl"}, }; unsigned int i; diff --git a/dlls/kernelbase/sync.c b/dlls/kernelbase/sync.c index 4c366f859c6..270ce137a02 100644 --- a/dlls/kernelbase/sync.c +++ b/dlls/kernelbase/sync.c @@ -358,6 +358,45 @@ BOOL WINAPI DECLSPEC_HOTPATCH WaitForDebugEvent( DEBUG_EVENT *event, DWORD timeo LARGE_INTEGER time; DBGUI_WAIT_STATE_CHANGE state; + for (;;) + { + status = DbgUiWaitStateChange( &state, get_nt_timeout( &time, timeout ) ); + switch (status) + { + case STATUS_SUCCESS: + /* continue on wide print exceptions to force resending an ANSI one. */ + if (state.NewState == DbgExceptionStateChange) + { + DBGKM_EXCEPTION *info = &state.StateInfo.Exception; + DWORD code = info->ExceptionRecord.ExceptionCode; + if (code == DBG_PRINTEXCEPTION_WIDE_C && info->ExceptionRecord.NumberParameters >= 2) + { + DbgUiContinue( &state.AppClientId, DBG_EXCEPTION_NOT_HANDLED ); + break; + } + } + DbgUiConvertStateChangeStructure( &state, event ); + return TRUE; + case STATUS_USER_APC: + continue; + case STATUS_TIMEOUT: + SetLastError( ERROR_SEM_TIMEOUT ); + return FALSE; + default: + return set_ntstatus( status ); + } + } +} + +/****************************************************************************** + * WaitForDebugEventEx (kernelbase.@) + */ +BOOL WINAPI DECLSPEC_HOTPATCH WaitForDebugEventEx( DEBUG_EVENT *event, DWORD timeout ) +{ + NTSTATUS status; + LARGE_INTEGER time; + DBGUI_WAIT_STATE_CHANGE state; + for (;;) { status = DbgUiWaitStateChange( &state, get_nt_timeout( &time, timeout ) ); diff --git a/dlls/mf/session.c b/dlls/mf/session.c index 004dcc88b98..25e1c474b44 100644 --- a/dlls/mf/session.c +++ b/dlls/mf/session.c @@ -800,6 +800,7 @@ static void session_clear_presentation(struct media_session *session) IMFTopology_Clear(session->presentation.current_topology); session->presentation.topo_status = MF_TOPOSTATUS_INVALID; + session->presentation.flags = 0; LIST_FOR_EACH_ENTRY_SAFE(source, source2, &session->presentation.sources, struct media_source, entry) { @@ -866,13 +867,13 @@ static void session_command_complete_with_event(struct media_session *session, M session_command_complete(session); } -static void session_subscribe_sources(struct media_session *session) +static HRESULT session_subscribe_sources(struct media_session *session) { struct media_source *source; - HRESULT hr; + HRESULT hr = S_OK; if (session->presentation.flags & SESSION_FLAG_SOURCES_SUBSCRIBED) - return; + return hr; LIST_FOR_EACH_ENTRY(source, &session->presentation.sources, struct media_source, entry) { @@ -880,10 +881,12 @@ static void session_subscribe_sources(struct media_session *session) source->object))) { WARN("Failed to subscribe to source events, hr %#lx.\n", hr); + return hr; } } session->presentation.flags |= SESSION_FLAG_SOURCES_SUBSCRIBED; + return hr; } static void session_start(struct media_session *session, const GUID *time_format, const PROPVARIANT *start_position) @@ -909,12 +912,20 @@ static void session_start(struct media_session *session, const GUID *time_format session->presentation.start_position.vt = VT_EMPTY; PropVariantCopy(&session->presentation.start_position, start_position); - session_subscribe_sources(session); + if (FAILED(hr = session_subscribe_sources(session))) + { + session_command_complete_with_event(session, MESessionStarted, hr, NULL); + return; + } LIST_FOR_EACH_ENTRY(source, &session->presentation.sources, struct media_source, entry) { if (FAILED(hr = IMFMediaSource_Start(source->source, source->pd, &GUID_NULL, start_position))) + { WARN("Failed to start media source %p, hr %#lx.\n", source->source, hr); + session_command_complete_with_event(session, MESessionStarted, hr, NULL); + return; + } } session->state = SESSION_STATE_STARTING_SOURCES; @@ -1308,10 +1319,8 @@ static void session_set_rate(struct media_session *session, BOOL thin, float rat if (SUCCEEDED(hr)) hr = IMFRateControl_GetRate(session->clock_rate_control, NULL, &clock_rate); - if (SUCCEEDED(hr) && (rate != clock_rate)) + if (SUCCEEDED(hr) && (rate != clock_rate) && SUCCEEDED(hr = session_subscribe_sources(session))) { - session_subscribe_sources(session); - LIST_FOR_EACH_ENTRY(source, &session->presentation.sources, struct media_source, entry) { if (SUCCEEDED(hr = MFGetService(source->object, &MF_RATE_CONTROL_SERVICE, &IID_IMFRateControl, @@ -2561,7 +2570,8 @@ static HRESULT WINAPI session_commands_callback_Invoke(IMFAsyncCallback *iface, case SESSION_CMD_STOP: if (session->presentation.flags & SESSION_FLAG_END_OF_PRESENTATION) session_set_topo_status(session, S_OK, MF_TOPOSTATUS_ENDED); - session_clear_end_of_presentation(session); + if (session->presentation.topo_status == MF_TOPOSTATUS_READY) + session_clear_end_of_presentation(session); session_stop(session); break; case SESSION_CMD_CLOSE: diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index 399f983983f..cc28786cf65 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -41,6 +41,39 @@ #include "initguid.h" #include "evr9.h" +#define DEFINE_EXPECT(func) \ + static BOOL expect_ ## func = FALSE, called_ ## func = FALSE + +#define SET_EXPECT(func) \ + expect_ ## func = TRUE + +#define CHECK_EXPECT2(func) \ + do { \ + ok(expect_ ##func, "unexpected call " #func "\n"); \ + called_ ## func = TRUE; \ + }while(0) + +#define CHECK_EXPECT(func) \ + do { \ + CHECK_EXPECT2(func); \ + expect_ ## func = FALSE; \ + }while(0) + +#define CHECK_CALLED(func) \ + do { \ + ok(called_ ## func, "expected " #func "\n"); \ + expect_ ## func = called_ ## func = FALSE; \ + }while(0) + +#define CHECK_NOT_CALLED(func) \ + do { \ + ok(!called_ ## func, "unexpected " #func "\n"); \ + expect_ ## func = called_ ## func = FALSE; \ + }while(0) + +#define CLEAR_CALLED(func) \ + expect_ ## func = called_ ## func = FALSE + extern GUID DMOVideoFormat_RGB32; HRESULT (WINAPI *pMFCreateSampleCopierMFT)(IMFTransform **copier); @@ -236,10 +269,15 @@ static void init_sink_node(IMFStreamSink *stream_sink, MF_CONNECT_METHOD method, } } +DEFINE_EXPECT(test_source_BeginGetEvent); +DEFINE_EXPECT(test_source_QueueEvent); +DEFINE_EXPECT(test_source_Start); + struct test_source { IMFMediaSource IMFMediaSource_iface; LONG refcount; + HRESULT begin_get_event_res; IMFPresentationDescriptor *pd; }; @@ -294,8 +332,9 @@ static HRESULT WINAPI test_source_GetEvent(IMFMediaSource *iface, DWORD flags, I static HRESULT WINAPI test_source_BeginGetEvent(IMFMediaSource *iface, IMFAsyncCallback *callback, IUnknown *state) { - ok(0, "Unexpected call.\n"); - return E_NOTIMPL; + struct test_source *source = impl_from_IMFMediaSource(iface); + CHECK_EXPECT(test_source_BeginGetEvent); + return source->begin_get_event_res; } static HRESULT WINAPI test_source_EndGetEvent(IMFMediaSource *iface, IMFAsyncResult *result, IMFMediaEvent **event) @@ -307,7 +346,7 @@ static HRESULT WINAPI test_source_EndGetEvent(IMFMediaSource *iface, IMFAsyncRes static HRESULT WINAPI test_source_QueueEvent(IMFMediaSource *iface, MediaEventType event_type, REFGUID ext_type, HRESULT hr, const PROPVARIANT *value) { - ok(0, "Unexpected call.\n"); + CHECK_EXPECT(test_source_QueueEvent); return E_NOTIMPL; } @@ -326,7 +365,7 @@ static HRESULT WINAPI test_source_CreatePresentationDescriptor(IMFMediaSource *i static HRESULT WINAPI test_source_Start(IMFMediaSource *iface, IMFPresentationDescriptor *pd, const GUID *time_format, const PROPVARIANT *start_position) { - ok(0, "Unexpected call.\n"); + CHECK_EXPECT(test_source_Start); return E_NOTIMPL; } @@ -372,6 +411,7 @@ static IMFMediaSource *create_test_source(IMFPresentationDescriptor *pd) source = calloc(1, sizeof(*source)); source->IMFMediaSource_iface.lpVtbl = &test_source_vtbl; source->refcount = 1; + source->begin_get_event_res = E_NOTIMPL; IMFPresentationDescriptor_AddRef((source->pd = pd)); return &source->IMFMediaSource_iface; @@ -1914,6 +1954,41 @@ static HRESULT wait_media_event_(int line, IMFMediaSession *session, IMFAsyncCal return status; } +#define wait_media_event_until_blocking(a, b, c, d, e) wait_media_event_until_blocking_(__LINE__, a, b, c, d, e) +static HRESULT wait_media_event_until_blocking_(int line, IMFMediaSession *session, IMFAsyncCallback *callback, + MediaEventType expect_type, DWORD timeout, PROPVARIANT *value) +{ + struct test_callback *impl = impl_from_IMFAsyncCallback(callback); + MediaEventType type; + HRESULT hr, status; + DWORD ret; + GUID guid; + + do + { + hr = IMFMediaSession_BeginGetEvent(session, &impl->IMFAsyncCallback_iface, (IUnknown *)session); + ok_(__FILE__, line)(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ret = WaitForSingleObject(impl->event, timeout); + if (ret == WAIT_TIMEOUT) return WAIT_TIMEOUT; + hr = IMFMediaEvent_GetType(impl->media_event, &type); + ok_(__FILE__, line)(hr == S_OK, "Unexpected hr %#lx.\n", hr); + } while (type != expect_type); + + ok_(__FILE__, line)(type == expect_type, "got type %lu\n", type); + + hr = IMFMediaEvent_GetExtendedType(impl->media_event, &guid); + ok_(__FILE__, line)(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok_(__FILE__, line)(IsEqualGUID(&guid, &GUID_NULL), "got extended type %s\n", debugstr_guid(&guid)); + + hr = IMFMediaEvent_GetValue(impl->media_event, value); + ok_(__FILE__, line)(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaEvent_GetStatus(impl->media_event, &status); + ok_(__FILE__, line)(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + return status; +} + static IMFMediaSource *create_media_source(const WCHAR *name, const WCHAR *mime) { IMFSourceResolver *resolver; @@ -1985,6 +2060,7 @@ static void test_media_session_events(void) struct test_stream_sink stream_sink = test_stream_sink; struct test_media_sink media_sink = test_media_sink; struct test_handler handler = test_handler; + struct test_source *source_impl; IMFAsyncCallback *callback, *callback2; IMFMediaType *input_type, *output_type; IMFTopologyNode *src_node, *sink_node; @@ -2363,6 +2439,105 @@ static void test_media_session_events(void) /* sometimes briefly leaking */ IMFMediaSession_Release(session); + + /* test IMFMediaSession_Start with source returning an error in BeginGetEvent */ + source_impl = impl_from_IMFMediaSource(source); + + hr = MFCreateMediaSession(NULL, &session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaSession_SetTopology(session, 0, topology); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = wait_media_event(session, callback, MESessionTopologySet, 1000, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(propvar.vt == VT_UNKNOWN, "got vt %u\n", propvar.vt); + ok(propvar.punkVal != (IUnknown *)topology, "got punkVal %p\n", propvar.punkVal); + PropVariantClear(&propvar); + + source_impl->begin_get_event_res = 0x80001234; + + SET_EXPECT(test_source_BeginGetEvent); + SET_EXPECT(test_source_QueueEvent); + SET_EXPECT(test_source_Start); + + propvar.vt = VT_EMPTY; + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = wait_media_event_until_blocking(session, callback, MESessionStarted, 1000, &propvar); + ok(hr == 0x80001234, "Unexpected hr %#lx.\n", hr); + ok(propvar.vt == VT_EMPTY, "got vt %u\n", propvar.vt); + ok(propvar.punkVal != (IUnknown *)topology, "got punkVal %p\n", propvar.punkVal); + PropVariantClear(&propvar); + + CHECK_CALLED(test_source_BeginGetEvent); + CHECK_NOT_CALLED(test_source_Start); + + hr = IMFMediaSession_ClearTopologies(session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaSession_Shutdown(session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(media_sink.shutdown, "media sink didn't shutdown.\n"); + media_sink.shutdown = FALSE; + + source_impl->begin_get_event_res = E_NOTIMPL; + + CLEAR_CALLED(test_source_BeginGetEvent); + CLEAR_CALLED(test_source_QueueEvent); + CLEAR_CALLED(test_source_Start); + + /* sometimes briefly leaking */ + IMFMediaSession_Release(session); + + + /* test IMFMediaSession_Start when test source BeginGetEvent returns S_OK */ + hr = MFCreateMediaSession(NULL, &session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaSession_SetTopology(session, 0, topology); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = wait_media_event(session, callback, MESessionTopologySet, 1000, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(propvar.vt == VT_UNKNOWN, "got vt %u\n", propvar.vt); + ok(propvar.punkVal != (IUnknown *)topology, "got punkVal %p\n", propvar.punkVal); + PropVariantClear(&propvar); + + source_impl = impl_from_IMFMediaSource(source); + source_impl->begin_get_event_res = S_OK; + + SET_EXPECT(test_source_BeginGetEvent); + SET_EXPECT(test_source_QueueEvent); + SET_EXPECT(test_source_Start); + + propvar.vt = VT_EMPTY; + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = wait_media_event_until_blocking(session, callback, MESessionStarted, 1000, &propvar); + ok(hr == E_NOTIMPL, "Unexpected hr %#lx.\n", hr); + ok(propvar.vt == VT_EMPTY, "got vt %u\n", propvar.vt); + ok(propvar.punkVal != (IUnknown *)topology, "got punkVal %p\n", propvar.punkVal); + PropVariantClear(&propvar); + + CHECK_CALLED(test_source_BeginGetEvent); + CHECK_CALLED(test_source_Start); + + hr = IMFMediaSession_ClearTopologies(session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaSession_Shutdown(session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(media_sink.shutdown, "media sink didn't shutdown.\n"); + media_sink.shutdown = FALSE; + + source_impl->begin_get_event_res = E_NOTIMPL; + + CLEAR_CALLED(test_source_BeginGetEvent); + CLEAR_CALLED(test_source_QueueEvent); + CLEAR_CALLED(test_source_Start); + + /* sometimes briefly leaking */ + IMFMediaSession_Release(session); + IMFAsyncCallback_Release(callback); if (handler.current_type) diff --git a/dlls/mf/tests/resource.rc b/dlls/mf/tests/resource.rc index 58654b93c33..25768d21983 100644 --- a/dlls/mf/tests/resource.rc +++ b/dlls/mf/tests/resource.rc @@ -63,25 +63,6 @@ mp3decdata.bin RCDATA mp3decdata.bin /* @makedep: h264data.bin */ h264data.bin RCDATA h264data.bin - -/* Generated with: - * gst-launch-1.0 videotestsrc num-buffers=60 pattern=smpte100 ! \ - * video/x-raw,format=I420,width=1920,height=1080,framerate=30000/1001 ! \ - * videoflip method=clockwise ! videoconvert ! \ - * x264enc ! filesink location=dlls/mf/tests/h264data.bin - */ -/* @makedep: stream1.bin */ -stream1.bin RCDATA stream1.bin - -/* Generated with: - * gst-launch-1.0 videotestsrc num-buffers=60 pattern=smpte100 ! \ - * video/x-raw,format=I420,width=1280,height=720,framerate=30000/1001 ! \ - * videoflip method=clockwise ! videoconvert ! \ - * x264enc ! filesink location=dlls/mf/tests/h264data.bin - */ -/* @makedep: stream2.bin */ -stream2.bin RCDATA stream2.bin - /* Generated from running the tests on Windows */ /* @makedep: nv12frame.bmp */ nv12frame.bmp RCDATA nv12frame.bmp diff --git a/dlls/mf/tests/stream1.bin b/dlls/mf/tests/stream1.bin deleted file mode 100644 index f16bdaec31c..00000000000 Binary files a/dlls/mf/tests/stream1.bin and /dev/null differ diff --git a/dlls/mf/tests/stream2.bin b/dlls/mf/tests/stream2.bin deleted file mode 100644 index 265878cced8..00000000000 Binary files a/dlls/mf/tests/stream2.bin and /dev/null differ diff --git a/dlls/mf/tests/transform.c b/dlls/mf/tests/transform.c index 2cf36bf4b38..a0df24bab6e 100644 --- a/dlls/mf/tests/transform.c +++ b/dlls/mf/tests/transform.c @@ -35,6 +35,8 @@ #include "wmcodecdsp.h" #include "mediaerr.h" #include "amvideo.h" +#include "ks.h" +#include "ksmedia.h" #include "mf_test.h" @@ -2470,10 +2472,6 @@ static void test_aac_decoder_subtype(const struct attribute_desc *input_type_des hr = IMFTransform_ProcessMessage(transform, MFT_MESSAGE_COMMAND_DRAIN, 0); ok(hr == S_OK, "ProcessMessage returned %#lx\n", hr); - flags = 0xdeadbeef; - hr = IMFTransform_GetInputStatus(transform, 0, &flags); - ok(hr == S_OK, "Got %#lx\n", hr); - ok(!flags, "Got flags %#lx.\n", flags); if (0) { /* This is fine on Windows but currently MFT_MESSAGE_COMMAND_DRAIN removes input sample from the queue @@ -2534,6 +2532,180 @@ static void test_aac_decoder_subtype(const struct attribute_desc *input_type_des CoUninitialize(); } +static void test_aac_decoder_channels(const struct attribute_desc *input_type_desc) +{ + static const struct attribute_desc expect_output_attributes[] = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio), + ATTR_UINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, 44100), + ATTR_UINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1), + {0}, + }; + static const media_type_desc expect_available_outputs[] = + { + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio), + ATTR_GUID(MF_MT_SUBTYPE, MFAudioFormat_Float), + ATTR_UINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 32), + }, + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio), + ATTR_GUID(MF_MT_SUBTYPE, MFAudioFormat_PCM), + ATTR_UINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 16), + }, + }; + static const UINT32 expected_mask[7] = + { + 0, + 0, + 0, + SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_CENTER, + KSAUDIO_SPEAKER_QUAD, + KSAUDIO_SPEAKER_QUAD | SPEAKER_FRONT_CENTER, + KSAUDIO_SPEAKER_5POINT1, + }; + + UINT32 value, num_channels, expected_chans, format_index, sample_size; + unsigned int num_channels_index = ~0u; + struct attribute_desc input_desc[64]; + IMFTransform *transform; + IMFAttributes *attrs; + IMFMediaType *type; + BOOL many_channels; + ULONG i, ret; + HRESULT hr; + + for (i = 0; i < ARRAY_SIZE(input_desc); i++) + { + input_desc[i] = input_type_desc[i]; + if (!input_desc[i].key) + break; + if (IsEqualGUID(input_desc[i].key, &MF_MT_AUDIO_NUM_CHANNELS)) + num_channels_index = i; + } + + ok(num_channels_index != ~0u, "Could not find MF_MT_AUDIO_NUM_CHANNELS.\n"); + ok(i < ARRAY_SIZE(input_desc), "Too many attributes.\n"); + + hr = CoInitialize(NULL); + ok(hr == S_OK, "got %#lx.\n", hr); + winetest_push_context("aacdec channels"); + + if (FAILED(hr = CoCreateInstance(&CLSID_MSAACDecMFT, NULL, CLSCTX_INPROC_SERVER, + &IID_IMFTransform, (void **)&transform))) + { + win_skip("AAC decoder transform is not available.\n"); + goto failed; + } + + hr = MFCreateMediaType(&type); + ok(hr == S_OK, "got %#lx.\n", hr); + input_desc[num_channels_index].value.vt = VT_UI8; + input_desc[num_channels_index].value.ulVal = 1; + init_media_type(type, input_desc, -1); + hr = IMFTransform_SetInputType(transform, 0, type, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + IMFMediaType_Release(type); + hr = IMFTransform_GetOutputAvailableType(transform, 0, 0, &type); + ok(hr == S_OK, "got %#lx.\n", hr); + hr = IMFAttributes_GetUINT32((IMFAttributes *)type, &MF_MT_AUDIO_NUM_CHANNELS, &value); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(value == 2, "got %u.\n", value); + IMFMediaType_Release(type); + input_desc[num_channels_index].value.vt = VT_UI4; + + for (num_channels = 0; num_channels < 16; ++num_channels) + { + many_channels = num_channels > 2; + winetest_push_context("chans %u", num_channels); + input_desc[num_channels_index].value.ulVal = num_channels; + + hr = MFCreateMediaType(&type); + ok(hr == S_OK, "got %#lx.\n", hr); + init_media_type(type, input_desc, -1); + hr = IMFTransform_SetInputType(transform, 0, type, 0); + IMFMediaType_Release(type); + if (num_channels <= 6) + ok(hr == S_OK, "got %#lx.\n", hr); + else + { + ok(hr == MF_E_INVALIDMEDIATYPE, "got %#lx.\n", hr); + winetest_pop_context(); + continue; + } + + i = -1; + while (SUCCEEDED(hr = IMFTransform_GetOutputAvailableType(transform, 0, ++i, &type))) + { + winetest_push_context("out %lu", i); + ok(hr == S_OK, "got %#lx.\n", hr); + check_media_type(type, expect_output_attributes, -1); + format_index = i % 2; + sample_size = format_index ? 2 : 4; + check_media_type(type, expect_available_outputs[format_index], -1); + attrs = (IMFAttributes *)type; + + hr = IMFAttributes_GetUINT32(attrs, &MF_MT_AUDIO_NUM_CHANNELS, &value); + ok(hr == S_OK, "got %#lx.\n", hr); + if (!num_channels || i >= ARRAY_SIZE(expect_available_outputs)) + expected_chans = 2; + else + expected_chans = num_channels; + ok(value == expected_chans, "got %u, expected %u.\n", value, expected_chans); + + hr = IMFAttributes_GetUINT32(attrs, &MF_MT_AUDIO_AVG_BYTES_PER_SECOND, &value); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(value == sample_size * 44100 * expected_chans, "got %u, expected %u.\n", + value, sample_size * 44100 * expected_chans); + + hr = IMFAttributes_GetUINT32(attrs, &MF_MT_AUDIO_BLOCK_ALIGNMENT, &value); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(value == sample_size * expected_chans, "got %u, expected %u.\n", value, sample_size * expected_chans); + + hr = IMFAttributes_GetUINT32(attrs, &MF_MT_AUDIO_PREFER_WAVEFORMATEX, &value); + if (many_channels && i < ARRAY_SIZE(expect_available_outputs)) + { + ok(hr == MF_E_ATTRIBUTENOTFOUND, "got %#lx.\n", hr); + } + else + { + ok(hr == S_OK, "got %#lx.\n", hr); + ok(value == 1, "got %u.\n", value); + } + + value = 0xdeadbeef; + hr = IMFMediaType_GetUINT32(type, &MF_MT_AUDIO_CHANNEL_MASK, &value); + if (expected_chans <= 2) + { + ok(hr == MF_E_ATTRIBUTENOTFOUND, "got %#lx.\n", hr); + } + else + { + ok(hr == S_OK, "got %#lx.\n", hr); + ok(value == expected_mask[expected_chans], "got %#x, expected %#x.\n", + value, expected_mask[expected_chans]); + } + + ret = IMFMediaType_Release(type); + ok(ret <= 1, "got %lu.\n", ret); + winetest_pop_context(); + } + ok(hr == MF_E_NO_MORE_TYPES, "got %#lx.\n", hr); + if (many_channels) + ok(i == ARRAY_SIZE(expect_available_outputs) * 2, "got %lu media types.\n", i); + else + ok(i == ARRAY_SIZE(expect_available_outputs), "got %lu media types.\n", i); + winetest_pop_context(); + } + + ret = IMFTransform_Release(transform); + ok(!ret, "got %lu.\n", ret); + +failed: + winetest_pop_context(); + CoUninitialize(); +} + static void test_aac_decoder(void) { static const BYTE aac_raw_codec_data[] = {0x12, 0x08}; @@ -2562,6 +2734,9 @@ static void test_aac_decoder(void) test_aac_decoder_subtype(aac_input_type_desc); test_aac_decoder_subtype(raw_aac_input_type_desc); + + test_aac_decoder_channels(aac_input_type_desc); + test_aac_decoder_channels(raw_aac_input_type_desc); } static const BYTE wma_codec_data[10] = {0, 0x44, 0, 0, 0x17, 0, 0, 0, 0, 0}; @@ -3709,6 +3884,7 @@ static void test_h264_decoder(void) ok(hr == S_OK, "ProcessMessage returned %#lx\n", hr); } ok(i == 2, "got %lu iterations\n", i); + todo_wine ok(h264_encoded_data_len == 1180, "got h264_encoded_data_len %lu\n", h264_encoded_data_len); ok(hr == MF_E_TRANSFORM_STREAM_CHANGE, "ProcessOutput returned %#lx\n", hr); ok(output_status == MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE, "got output[0].dwStatus %#lx\n", output_status); @@ -5839,8 +6015,8 @@ static void test_video_processor(void) && !IsEqualGUID(&guid, &MEDIASUBTYPE_Y42T)) { hr = MFCalculateImageSize(&guid, 16, 16, (UINT32 *)&input_info.cbSize); - todo_wine_if(IsEqualGUID(&guid, &MFVideoFormat_NV11) || IsEqualGUID(&guid, &MFVideoFormat_YVYU) - || IsEqualGUID(&guid, &MFVideoFormat_Y216) || IsEqualGUID(&guid, &MFVideoFormat_v410) + todo_wine_if(IsEqualGUID(&guid, &MFVideoFormat_Y216) + || IsEqualGUID(&guid, &MFVideoFormat_v410) || IsEqualGUID(&guid, &MFVideoFormat_Y41P)) ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); } @@ -6467,160 +6643,6 @@ static void test_mp3_decoder(void) CoUninitialize(); } -static void test_h264_reuse(void) -{ - const BYTE *data1; - ULONG data1_len; - IMFTransform *transform; - IMFAttributes *attribs; - IMFMediaType *type; - IMFSample *input_sample, *output_sample; - DWORD length, output_status; - UINT64 frame_size; - unsigned int i, width, height; - HRESULT hr; - - hr = CoInitialize(NULL); - ok(hr == S_OK, "Failed to initialize, hr %#lx.\n", hr); - - if (FAILED(hr = CoCreateInstance(&CLSID_MSH264DecoderMFT, NULL, CLSCTX_INPROC_SERVER, - &IID_IMFTransform, (void **)&transform))) - goto failed; - - hr = MFCreateMediaType(&type); - ok(hr == S_OK, "got %#lx\n", hr); - - hr = IMFMediaType_SetGUID(type, &MF_MT_MAJOR_TYPE, &MFMediaType_Video); - ok(hr == S_OK, "got %#lx\n", hr); - hr = IMFMediaType_SetGUID(type, &MF_MT_SUBTYPE, &MFVideoFormat_H264); - ok(hr == S_OK, "got %#lx\n", hr); - - hr = IMFTransform_SetInputType(transform, 0, type, 0); - ok(hr == S_OK, "got %#lx\n", hr); - IMFMediaType_Release(type); - - hr = IMFTransform_GetOutputAvailableType(transform, 0, 0, &type); - ok(hr == S_OK, "got %#lx\n", hr); - hr = IMFMediaType_SetGUID(type, &MF_MT_MAJOR_TYPE, &MFMediaType_Video); - ok(hr == S_OK, "got %#lx\n", hr); - hr = IMFMediaType_SetGUID(type, &MF_MT_SUBTYPE, &MFVideoFormat_NV12); - ok(hr == S_OK, "got %#lx\n", hr); - hr = IMFTransform_SetOutputType(transform, 0, type, 0); - ok(hr == S_OK, "got %#lx\n", hr); - IMFMediaType_Release(type); - - hr = IMFTransform_GetAttributes(transform, &attribs); - ok(hr == S_OK, "got %#lx\n", hr); - hr = IMFAttributes_SetUINT32(attribs, &MF_LOW_LATENCY, 1); - ok(hr == S_OK, "got %#lx\n", hr); - IMFAttributes_Release(attribs); - - load_resource(L"stream1.bin", &data1, &data1_len); - - i = 0; - input_sample = next_h264_sample(&data1, &data1_len); - while (1) - { - output_sample = create_sample(NULL, 0x1000); - hr = check_mft_process_output(transform, output_sample, &output_status); - if (hr != MF_E_TRANSFORM_NEED_MORE_INPUT) break; - - ok(output_status == 0, "got output[0].dwStatus %#lx\n", output_status); - hr = IMFSample_GetTotalLength(output_sample, &length); - ok(hr == S_OK, "GetTotalLength returned %#lx\n", hr); - ok(length == 0, "got length %lu\n", length); - IMFSample_Release(output_sample); - - hr = IMFTransform_ProcessInput(transform, 0, input_sample, 0); - ok(hr == S_OK, "got %#lx\n", hr); - IMFSample_Release(input_sample); - input_sample = next_h264_sample(&data1, &data1_len); - - hr = IMFTransform_ProcessInput(transform, 0, input_sample, 0); - ok(hr == S_OK, "got %#lx\n", hr); - IMFSample_Release(input_sample); - input_sample = next_h264_sample(&data1, &data1_len); - - i++; - } - trace("i %d.\n", i); - ok(hr == MF_E_TRANSFORM_STREAM_CHANGE, "got %#lx\n", hr); - IMFSample_Release(output_sample); - - hr = IMFTransform_GetOutputAvailableType(transform, 0, 0, &type); - ok(hr == S_OK, "got %#lx\n", hr); - IMFMediaType_GetUINT64(type, &MF_MT_FRAME_SIZE, &frame_size); - ok(hr == S_OK, "got %#lx\n", hr); - height = frame_size >> 32; - width = frame_size & 0xffffffff; - ok(width == 1920, "got %u.\n", width); - ok(height == 1088, "got %u.\n", height); - IMFMediaType_Release(type); - - output_sample = create_sample(NULL, 0x1000); - hr = check_mft_process_output(transform, output_sample, &output_status); - ok(hr == E_FAIL, "got %#lx\n", hr); - IMFSample_Release(output_sample); - - output_sample = create_sample(NULL, width * height * 3 / 2); - hr = check_mft_process_output(transform, output_sample, &output_status); - ok(hr == S_OK, "got %#lx\n", hr); - IMFSample_Release(output_sample); - - hr = IMFTransform_ProcessMessage(transform, MFT_MESSAGE_COMMAND_FLUSH, 0); - ok(hr == S_OK, "got %#lx\n", hr); - - IMFSample_Release(input_sample); - - load_resource(L"stream2.bin", &data1, &data1_len); - i = 0; - input_sample = next_h264_sample(&data1, &data1_len); - while (1) - { - output_sample = create_sample(NULL, width * height * 3 / 2); - hr = check_mft_process_output(transform, output_sample, &output_status); - if (hr != MF_E_TRANSFORM_NEED_MORE_INPUT) break; - - ok(output_status == 0, "got output[0].dwStatus %#lx\n", output_status); - hr = IMFSample_GetTotalLength(output_sample, &length); - ok(hr == S_OK, "GetTotalLength returned %#lx\n", hr); - ok(length == 0, "got length %lu\n", length); - IMFSample_Release(output_sample); - - hr = IMFTransform_ProcessInput(transform, 0, input_sample, 0); - ok(hr == S_OK, "got %#lx\n", hr); - IMFSample_Release(input_sample); - input_sample = next_h264_sample(&data1, &data1_len); - - hr = IMFTransform_ProcessInput(transform, 0, input_sample, 0); - ok(hr == S_OK, "got %#lx\n", hr); - IMFSample_Release(input_sample); - input_sample = next_h264_sample(&data1, &data1_len); - - i++; - } - trace("i %d.\n", i); - - ok(hr == MF_E_TRANSFORM_STREAM_CHANGE, "got %#lx\n", hr); - - hr = IMFTransform_GetOutputAvailableType(transform, 0, 0, &type); - ok(hr == S_OK, "got %#lx\n", hr); - IMFMediaType_GetUINT64(type, &MF_MT_FRAME_SIZE, &frame_size); - ok(hr == S_OK, "got %#lx\n", hr); - height = frame_size >> 32; - width = frame_size & 0xffffffff; - ok(width == 1280, "got %u.\n", width); - ok(height == 720, "got %u.\n", height); - IMFMediaType_Release(type); - - IMFSample_Release(output_sample); - IMFSample_Release(input_sample); - - IMFTransform_Release(transform); -failed: - CoUninitialize(); -} - START_TEST(transform) { init_functions(); @@ -6639,5 +6661,4 @@ START_TEST(transform) test_color_convert(); test_video_processor(); test_mp3_decoder(); - test_h264_reuse(); } diff --git a/dlls/mf/topology.c b/dlls/mf/topology.c index 25a00708100..11ddf573f06 100644 --- a/dlls/mf/topology.c +++ b/dlls/mf/topology.c @@ -712,7 +712,11 @@ static HRESULT WINAPI topology_CloneFrom(IMFTopology *iface, IMFTopology *src) for (j = 0; j < outputs->count; ++j) { DWORD input_index = outputs->streams[j].connection_stream; - TOPOID id = outputs->streams[j].connection->id; + TOPOID id; + + if (!outputs->streams[j].connection) + continue; + id = outputs->streams[j].connection->id; /* Skip node lookup in destination topology, assuming same node order. */ if (SUCCEEDED(hr = topology_get_node_by_id(topology, id, &node))) diff --git a/dlls/mfmediaengine/main.c b/dlls/mfmediaengine/main.c index 4f99513e938..bbe7248c145 100644 --- a/dlls/mfmediaengine/main.c +++ b/dlls/mfmediaengine/main.c @@ -113,6 +113,19 @@ struct rect float left, top, right, bottom; }; +struct effect +{ + IUnknown *object; + BOOL optional; +}; + +struct effects +{ + struct effect *effects; + size_t count; + size_t capacity; +}; + struct media_engine { IMFMediaEngineEx IMFMediaEngineEx_iface; @@ -145,6 +158,8 @@ struct media_engine IMFMediaSource *source; IMFPresentationDescriptor *pd; } presentation; + struct effects video_effects; + struct effects audio_effects; struct { LONGLONG pts; @@ -835,6 +850,23 @@ static void media_engine_get_frame_size(struct media_engine *engine, IMFTopology IMFMediaType_Release(media_type); } +static void media_engine_apply_volume(const struct media_engine *engine) +{ + IMFSimpleAudioVolume *sa_volume; + HRESULT hr; + + if (!engine->session) + return; + + if (FAILED(MFGetService((IUnknown *)engine->session, &MR_POLICY_VOLUME_SERVICE, &IID_IMFSimpleAudioVolume, (void **)&sa_volume))) + return; + + if (FAILED(hr = IMFSimpleAudioVolume_SetMasterVolume(sa_volume, engine->volume))) + WARN("Failed to set master volume, hr %#lx.\n", hr); + + IMFSimpleAudioVolume_Release(sa_volume); +} + static HRESULT WINAPI media_engine_callback_QueryInterface(IMFAsyncCallback *iface, REFIID riid, void **obj) { if (IsEqualIID(riid, &IID_IMFAsyncCallback) || @@ -918,6 +950,8 @@ static HRESULT WINAPI media_engine_session_events_Invoke(IMFAsyncCallback *iface EnterCriticalSection(&engine->cs); + media_engine_apply_volume(engine); + engine->ready_state = MF_MEDIA_ENGINE_READY_HAVE_METADATA; media_engine_get_frame_size(engine, topology); @@ -999,6 +1033,46 @@ static HRESULT media_engine_create_source_node(IMFMediaSource *source, IMFPresen return S_OK; } +static HRESULT media_engine_create_effects(struct effect *effects, size_t count, + IMFTopologyNode *src, IMFTopologyNode *sink, IMFTopology *topology) +{ + IMFTopologyNode *last = src; + HRESULT hr = S_OK; + size_t i; + + IMFTopologyNode_AddRef(last); + + for (i = 0; i < count; ++i) + { + IMFTopologyNode *node = NULL; + + if (FAILED(hr = MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &node))) + { + WARN("Failed to create transform node, hr %#lx", hr); + break; + } + + IMFTopologyNode_SetObject(node, (IUnknown *)effects[i].object); + IMFTopologyNode_SetUINT32(node, &MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE); + + if (effects[i].optional) + IMFTopologyNode_SetUINT32(node, &MF_TOPONODE_CONNECT_METHOD, MF_CONNECT_AS_OPTIONAL); + + IMFTopology_AddNode(topology, node); + IMFTopologyNode_ConnectOutput(last, 0, node, 0); + + IMFTopologyNode_Release(last); + last = node; + } + + IMFTopologyNode_Release(last); + + if (SUCCEEDED(hr)) + hr = IMFTopologyNode_ConnectOutput(last, 0, sink, 0); + + return hr; +} + static HRESULT media_engine_create_audio_renderer(struct media_engine *engine, IMFTopologyNode **node) { unsigned int category, role; @@ -1086,6 +1160,20 @@ static void media_engine_clear_presentation(struct media_engine *engine) memset(&engine->presentation, 0, sizeof(engine->presentation)); } +static void media_engine_clear_effects(struct effects *effects) +{ + size_t i; + + for (i = 0; i < effects->count; ++i) + { + if (effects->effects[i].object) + IUnknown_Release(effects->effects[i].object); + } + + free(effects->effects); + memset(effects, 0, sizeof(*effects)); +} + static HRESULT media_engine_create_topology(struct media_engine *engine, IMFMediaSource *source) { IMFStreamDescriptor *sd_audio = NULL, *sd_video = NULL; @@ -1187,7 +1275,10 @@ static HRESULT media_engine_create_topology(struct media_engine *engine, IMFMedi { IMFTopology_AddNode(topology, audio_src); IMFTopology_AddNode(topology, sar_node); - IMFTopologyNode_ConnectOutput(audio_src, 0, sar_node, 0); + + if (FAILED(hr = media_engine_create_effects(engine->audio_effects.effects, engine->audio_effects.count, + audio_src, sar_node, topology))) + WARN("Failed to create audio effect nodes, hr %#lx.\n", hr); } if (sar_node) @@ -1208,7 +1299,10 @@ static HRESULT media_engine_create_topology(struct media_engine *engine, IMFMedi { IMFTopology_AddNode(topology, video_src); IMFTopology_AddNode(topology, grabber_node); - IMFTopologyNode_ConnectOutput(video_src, 0, grabber_node, 0); + + if (FAILED(hr = media_engine_create_effects(engine->video_effects.effects, engine->video_effects.count, + video_src, grabber_node, topology))) + WARN("Failed to create video effect nodes, hr %#lx.\n", hr); } if (SUCCEEDED(hr)) @@ -1363,6 +1457,8 @@ static void free_media_engine(struct media_engine *engine) IMFAttributes_Release(engine->attributes); if (engine->resolver) IMFSourceResolver_Release(engine->resolver); + media_engine_clear_effects(&engine->audio_effects); + media_engine_clear_effects(&engine->video_effects); media_engine_release_video_frame_resources(engine); media_engine_clear_presentation(engine); if (engine->device_manager) @@ -2012,6 +2108,7 @@ static HRESULT WINAPI media_engine_SetVolume(IMFMediaEngineEx *iface, double vol else if (volume != engine->volume) { engine->volume = volume; + media_engine_apply_volume(engine); IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_VOLUMECHANGE, 0, 0); } LeaveCriticalSection(&engine->cs); @@ -2443,7 +2540,7 @@ static HRESULT WINAPI media_engine_SetBalance(IMFMediaEngineEx *iface, double ba { FIXME("%p, %f stub.\n", iface, balance); - return E_NOTIMPL; + return S_OK; } static BOOL WINAPI media_engine_IsPlaybackRateSupported(IMFMediaEngineEx *iface, double rate) @@ -2557,25 +2654,77 @@ static HRESULT WINAPI media_engine_IsProtected(IMFMediaEngineEx *iface, BOOL *pr return E_NOTIMPL; } +static HRESULT media_engine_insert_effect(struct media_engine *engine, struct effects *effects, IUnknown *object, BOOL is_optional) +{ + HRESULT hr = S_OK; + + if (engine->flags & FLAGS_ENGINE_SHUT_DOWN) + hr = MF_E_SHUTDOWN; + else if (!mf_array_reserve((void **)&effects->effects, &effects->capacity, effects->count + 1, sizeof(*effects->effects))) + { + hr = E_OUTOFMEMORY; + } + else + { + effects->effects[effects->count].object = object; + if (object) + { + IUnknown_AddRef(effects->effects[effects->count].object); + } + effects->effects[effects->count].optional = is_optional; + + effects->count++; + } + + return hr; +} + static HRESULT WINAPI media_engine_InsertVideoEffect(IMFMediaEngineEx *iface, IUnknown *effect, BOOL is_optional) { - FIXME("%p, %p, %d stub.\n", iface, effect, is_optional); + struct media_engine *engine = impl_from_IMFMediaEngineEx(iface); + HRESULT hr = S_OK; - return E_NOTIMPL; + TRACE("%p, %p, %d.\n", iface, effect, is_optional); + + EnterCriticalSection(&engine->cs); + hr = media_engine_insert_effect(engine, &engine->video_effects, effect, is_optional); + LeaveCriticalSection(&engine->cs); + + return hr; } static HRESULT WINAPI media_engine_InsertAudioEffect(IMFMediaEngineEx *iface, IUnknown *effect, BOOL is_optional) { - FIXME("%p, %p, %d stub.\n", iface, effect, is_optional); + struct media_engine *engine = impl_from_IMFMediaEngineEx(iface); + HRESULT hr = S_OK; - return E_NOTIMPL; + TRACE("%p, %p, %d.\n", iface, effect, is_optional); + + EnterCriticalSection(&engine->cs); + hr = media_engine_insert_effect(engine, &engine->audio_effects, effect, is_optional); + LeaveCriticalSection(&engine->cs); + + return hr; } static HRESULT WINAPI media_engine_RemoveAllEffects(IMFMediaEngineEx *iface) { - FIXME("%p stub.\n", iface); + struct media_engine *engine = impl_from_IMFMediaEngineEx(iface); + HRESULT hr = S_OK; - return E_NOTIMPL; + TRACE("%p.\n", iface); + + EnterCriticalSection(&engine->cs); + if (engine->flags & FLAGS_ENGINE_SHUT_DOWN) + hr = MF_E_SHUTDOWN; + else + { + media_engine_clear_effects(&engine->audio_effects); + media_engine_clear_effects(&engine->video_effects); + } + LeaveCriticalSection(&engine->cs); + + return hr; } static HRESULT WINAPI media_engine_SetTimelineMarkerTimer(IMFMediaEngineEx *iface, double timeout) diff --git a/dlls/mfmediaengine/tests/mfmediaengine.c b/dlls/mfmediaengine/tests/mfmediaengine.c index 3a5b2bf8253..1a23de5ec86 100644 --- a/dlls/mfmediaengine/tests/mfmediaengine.c +++ b/dlls/mfmediaengine/tests/mfmediaengine.c @@ -146,7 +146,7 @@ static void check_rgb32_data_(int line, const WCHAR *filename, const BYTE *data, expect_data = LockResource(LoadResource(GetModuleHandleW(NULL), resource)); diff = compare_rgb32(data, &length, rect, expect_data); - ok_(__FILE__, line)(diff == 0, "Unexpected %lu%% diff\n", diff); + ok_(__FILE__, line)(diff <= 3 /* small difference in wine */, "Unexpected %lu%% diff\n", diff); } static void init_functions(void) diff --git a/dlls/mfplat/buffer.c b/dlls/mfplat/buffer.c index 6cdc57692a0..3850efa594c 100644 --- a/dlls/mfplat/buffer.c +++ b/dlls/mfplat/buffer.c @@ -32,7 +32,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(mfplat); #define ALIGN_SIZE(size, alignment) (((size) + (alignment)) & ~((alignment))) -typedef HRESULT (*p_copy_image_func)(BYTE *dest, LONG dest_stride, const BYTE *src, LONG src_stride, DWORD width, DWORD lines, DWORD dest_size); +typedef void (*p_copy_image_func)(BYTE *dest, LONG dest_stride, const BYTE *src, LONG src_stride, DWORD width, DWORD lines); struct buffer { @@ -76,51 +76,32 @@ struct buffer CRITICAL_SECTION cs; }; -static HRESULT copy_image(const struct buffer *buffer, BYTE *dest, LONG dest_stride, const BYTE *src, - LONG src_stride, DWORD width, DWORD lines, DWORD dest_size) +static void copy_image(const struct buffer *buffer, BYTE *dest, LONG dest_stride, const BYTE *src, + LONG src_stride, DWORD width, DWORD lines) { - HRESULT hr = S_OK; - - if (width > abs(dest_stride) || abs(dest_stride) * lines > dest_size) - return E_NOT_SUFFICIENT_BUFFER; + MFCopyImage(dest, dest_stride, src, src_stride, width, lines); - hr = MFCopyImage(dest, dest_stride, src, src_stride, width, lines); - - if (SUCCEEDED(hr) && buffer->_2d.copy_image) + if (buffer->_2d.copy_image) { dest += dest_stride * lines; src += src_stride * lines; - hr = buffer->_2d.copy_image(dest, dest_stride, src, src_stride, width, lines, dest_size); + buffer->_2d.copy_image(dest, dest_stride, src, src_stride, width, lines); } - - return hr; } -static HRESULT copy_image_nv12(BYTE *dest, LONG dest_stride, const BYTE *src, - LONG src_stride, DWORD width, DWORD lines, DWORD dest_size) +static void copy_image_nv12(BYTE *dest, LONG dest_stride, const BYTE *src, LONG src_stride, DWORD width, DWORD lines) { - if (abs(dest_stride) * (lines + lines / 2) > dest_size) - return E_NOT_SUFFICIENT_BUFFER; - - return MFCopyImage(dest, dest_stride, src, src_stride, width, lines / 2); + MFCopyImage(dest, dest_stride, src, src_stride, width, lines / 2); } -static HRESULT copy_image_imc1(BYTE *dest, LONG dest_stride, const BYTE *src, - LONG src_stride, DWORD width, DWORD lines, DWORD dest_size) +static void copy_image_imc1(BYTE *dest, LONG dest_stride, const BYTE *src, LONG src_stride, DWORD width, DWORD lines) { - if (abs(dest_stride) * (2 * lines) > dest_size) - return E_NOT_SUFFICIENT_BUFFER; - - return MFCopyImage(dest, dest_stride, src, src_stride, width / 2, lines); + MFCopyImage(dest, dest_stride, src, src_stride, width / 2, lines); } -static HRESULT copy_image_imc2(BYTE *dest, LONG dest_stride, const BYTE *src, - LONG src_stride, DWORD width, DWORD lines, DWORD dest_size) +static void copy_image_imc2(BYTE *dest, LONG dest_stride, const BYTE *src, LONG src_stride, DWORD width, DWORD lines) { - if (abs(dest_stride) * 3 * lines / 2 > dest_size) - return E_NOT_SUFFICIENT_BUFFER; - - return MFCopyImage(dest, dest_stride / 2, src, src_stride / 2, width / 2, lines); + MFCopyImage(dest, dest_stride / 2, src, src_stride / 2, width / 2, lines); } static inline struct buffer *impl_from_IMFMediaBuffer(IMFMediaBuffer *iface) @@ -332,8 +313,14 @@ static HRESULT WINAPI memory_1d_2d_buffer_Lock(IMFMediaBuffer *iface, BYTE **dat hr = E_OUTOFMEMORY; if (SUCCEEDED(hr)) - hr = copy_image(buffer, buffer->_2d.linear_buffer, buffer->_2d.width, buffer->data, buffer->_2d.pitch, - buffer->_2d.width, buffer->_2d.height, buffer->_2d.plane_size); + { + int pitch = buffer->_2d.pitch; + + if (pitch < 0) + pitch = -pitch; + copy_image(buffer, buffer->_2d.linear_buffer, buffer->_2d.width, buffer->data, pitch, + buffer->_2d.width, buffer->_2d.height); + } } if (SUCCEEDED(hr)) @@ -354,7 +341,6 @@ static HRESULT WINAPI memory_1d_2d_buffer_Lock(IMFMediaBuffer *iface, BYTE **dat static HRESULT WINAPI memory_1d_2d_buffer_Unlock(IMFMediaBuffer *iface) { struct buffer *buffer = impl_from_IMFMediaBuffer(iface); - HRESULT hr = S_OK; TRACE("%p.\n", iface); @@ -362,8 +348,12 @@ static HRESULT WINAPI memory_1d_2d_buffer_Unlock(IMFMediaBuffer *iface) if (buffer->_2d.linear_buffer && !--buffer->_2d.locks) { - hr = copy_image(buffer, buffer->data, buffer->_2d.pitch, buffer->_2d.linear_buffer, buffer->_2d.width, - buffer->_2d.width, buffer->_2d.height, buffer->max_length); + int pitch = buffer->_2d.pitch; + + if (pitch < 0) + pitch = -pitch; + copy_image(buffer, buffer->data, pitch, buffer->_2d.linear_buffer, buffer->_2d.width, + buffer->_2d.width, buffer->_2d.height); free(buffer->_2d.linear_buffer); buffer->_2d.linear_buffer = NULL; @@ -371,7 +361,7 @@ static HRESULT WINAPI memory_1d_2d_buffer_Unlock(IMFMediaBuffer *iface) LeaveCriticalSection(&buffer->cs); - return hr; + return S_OK; } static const IMFMediaBufferVtbl memory_1d_2d_buffer_vtbl = @@ -412,8 +402,8 @@ static HRESULT WINAPI d3d9_surface_buffer_Lock(IMFMediaBuffer *iface, BYTE **dat hr = IDirect3DSurface9_LockRect(buffer->d3d9_surface.surface, &rect, NULL, 0); if (SUCCEEDED(hr)) { - hr = copy_image(buffer, buffer->_2d.linear_buffer, buffer->_2d.width, rect.pBits, rect.Pitch, - buffer->_2d.width, buffer->_2d.height, buffer->_2d.plane_size); + copy_image(buffer, buffer->_2d.linear_buffer, buffer->_2d.width, rect.pBits, rect.Pitch, + buffer->_2d.width, buffer->_2d.height); IDirect3DSurface9_UnlockRect(buffer->d3d9_surface.surface); } } @@ -451,8 +441,8 @@ static HRESULT WINAPI d3d9_surface_buffer_Unlock(IMFMediaBuffer *iface) if (SUCCEEDED(hr = IDirect3DSurface9_LockRect(buffer->d3d9_surface.surface, &rect, NULL, 0))) { - hr = copy_image(buffer, rect.pBits, rect.Pitch, buffer->_2d.linear_buffer, buffer->_2d.width, - buffer->_2d.width, buffer->_2d.height, buffer->max_length); + copy_image(buffer, rect.pBits, rect.Pitch, buffer->_2d.linear_buffer, buffer->_2d.width, + buffer->_2d.width, buffer->_2d.height); IDirect3DSurface9_UnlockRect(buffer->d3d9_surface.surface); } @@ -621,9 +611,31 @@ static HRESULT WINAPI memory_2d_buffer_GetContiguousLength(IMF2DBuffer2 *iface, static HRESULT WINAPI memory_2d_buffer_ContiguousCopyTo(IMF2DBuffer2 *iface, BYTE *dest_buffer, DWORD dest_length) { - FIXME("%p, %p, %lu.\n", iface, dest_buffer, dest_length); + struct buffer *buffer = impl_from_IMF2DBuffer2(iface); + BYTE *src_scanline0, *src_buffer_start; + DWORD src_length; + LONG src_pitch; + HRESULT hr; - return E_NOTIMPL; + TRACE("%p, %p, %lu.\n", iface, dest_buffer, dest_length); + + if (dest_length < buffer->_2d.plane_size) + return E_INVALIDARG; + + hr = IMF2DBuffer2_Lock2DSize(iface, MF2DBuffer_LockFlags_Read, &src_scanline0, &src_pitch, &src_buffer_start, &src_length); + + if (SUCCEEDED(hr)) + { + if (src_pitch < 0) + src_pitch = -src_pitch; + copy_image(buffer, dest_buffer, buffer->_2d.width, src_buffer_start, src_pitch, + buffer->_2d.width, buffer->_2d.height); + + if (FAILED(IMF2DBuffer2_Unlock2D(iface))) + WARN("Couldn't unlock source buffer %p, hr %#lx.\n", iface, hr); + } + + return S_OK; } static HRESULT WINAPI memory_2d_buffer_ContiguousCopyFrom(IMF2DBuffer2 *iface, const BYTE *src_buffer, DWORD src_length) @@ -631,29 +643,26 @@ static HRESULT WINAPI memory_2d_buffer_ContiguousCopyFrom(IMF2DBuffer2 *iface, c struct buffer *buffer = impl_from_IMF2DBuffer2(iface); BYTE *dst_scanline0, *dst_buffer_start; DWORD dst_length; - HRESULT hr, hr2; LONG dst_pitch; + HRESULT hr; TRACE("%p, %p, %lu.\n", iface, src_buffer, src_length); - if (src_length != buffer->_2d.plane_size) - { - WARN("truncating contiguous copy not implemented\n"); - return E_NOTIMPL; - } + if (src_length < buffer->_2d.plane_size) + return E_INVALIDARG; hr = IMF2DBuffer2_Lock2DSize(iface, MF2DBuffer_LockFlags_Write, &dst_scanline0, &dst_pitch, &dst_buffer_start, &dst_length); if (SUCCEEDED(hr)) { - hr = copy_image(buffer, dst_scanline0, dst_pitch, src_buffer, buffer->_2d.width, buffer->_2d.width, buffer->_2d.height, buffer->max_length); - } + if (dst_pitch < 0) + dst_pitch = -dst_pitch; + copy_image(buffer, dst_buffer_start, dst_pitch, src_buffer, buffer->_2d.width, + buffer->_2d.width, buffer->_2d.height); - hr2 = IMF2DBuffer2_Unlock2D(iface); - if (FAILED(hr2)) - WARN("Unlocking destination buffer %p failed with hr %#lx.\n", iface, hr2); - if (FAILED(hr2) && SUCCEEDED(hr)) - hr = hr2; + if (FAILED(IMF2DBuffer2_Unlock2D(iface))) + WARN("Couldn't unlock destination buffer %p, hr %#lx.\n", iface, hr); + } return hr; } @@ -678,52 +687,11 @@ static HRESULT WINAPI memory_2d_buffer_Lock2DSize(IMF2DBuffer2 *iface, MF2DBuffe return hr; } -static HRESULT WINAPI memory_2d_buffer_Copy2DTo(IMF2DBuffer2 *iface, IMF2DBuffer2 *dst_buffer) +static HRESULT WINAPI memory_2d_buffer_Copy2DTo(IMF2DBuffer2 *iface, IMF2DBuffer2 *dest_buffer) { - struct buffer *buffer = impl_from_IMF2DBuffer2(iface); - BYTE *src_scanline0, *dst_scanline0, *src_buffer_start, *dst_buffer_start; - DWORD src_length, dst_length; - LONG src_pitch, dst_pitch; - BOOL src_locked = FALSE, dst_locked = FALSE; - HRESULT hr, hr2; - - TRACE("%p, %p.\n", iface, dst_buffer); - - hr = IMF2DBuffer2_Lock2DSize(iface, MF2DBuffer_LockFlags_Read, &src_scanline0, - &src_pitch, &src_buffer_start, &src_length); - if (FAILED(hr)) - goto end; - src_locked = TRUE; - - hr = IMF2DBuffer2_Lock2DSize(dst_buffer, MF2DBuffer_LockFlags_Write, &dst_scanline0, - &dst_pitch, &dst_buffer_start, &dst_length); - if (FAILED(hr)) - goto end; - dst_locked = TRUE; - - hr = copy_image(buffer, dst_scanline0, dst_pitch, src_scanline0, src_pitch, - buffer->_2d.width, buffer->_2d.height, dst_length); - -end: - if (src_locked) - { - hr2 = IMF2DBuffer2_Unlock2D(iface); - if (FAILED(hr2)) - WARN("Unlocking source buffer %p failed with hr %#lx.\n", iface, hr2); - if (FAILED(hr2) && SUCCEEDED(hr)) - hr = hr2; - } + FIXME("%p, %p.\n", iface, dest_buffer); - if (dst_locked) - { - hr2 = IMF2DBuffer2_Unlock2D(dst_buffer); - if (FAILED(hr2)) - WARN("Unlocking destination buffer %p failed with hr %#lx.\n", dst_buffer, hr2); - if (FAILED(hr2) && SUCCEEDED(hr)) - hr = hr2; - } - - return hr; + return E_NOTIMPL; } static const IMF2DBuffer2Vtbl memory_2d_buffer_vtbl = @@ -750,12 +718,14 @@ static HRESULT d3d9_surface_buffer_lock(struct buffer *buffer, MF2DBuffer_LockFl if (buffer->_2d.linear_buffer) hr = MF_E_UNEXPECTED; else if (!buffer->_2d.locks) - { hr = IDirect3DSurface9_LockRect(buffer->d3d9_surface.surface, &buffer->d3d9_surface.rect, NULL, 0); - } + else if (buffer->_2d.lock_flags == MF2DBuffer_LockFlags_Write && flags != MF2DBuffer_LockFlags_Write) + hr = HRESULT_FROM_WIN32(ERROR_WAS_LOCKED); if (SUCCEEDED(hr)) { + if (!buffer->_2d.locks) + buffer->_2d.lock_flags = flags; buffer->_2d.locks++; *scanline0 = buffer->d3d9_surface.rect.pBits; *pitch = buffer->d3d9_surface.rect.Pitch; @@ -802,6 +772,7 @@ static HRESULT WINAPI d3d9_surface_buffer_Unlock2D(IMF2DBuffer2 *iface) { IDirect3DSurface9_UnlockRect(buffer->d3d9_surface.surface); memset(&buffer->d3d9_surface.rect, 0, sizeof(buffer->d3d9_surface.rect)); + buffer->_2d.lock_flags = 0; } } else @@ -967,27 +938,8 @@ static HRESULT dxgi_surface_buffer_create_readback_texture(struct buffer *buffer { D3D11_TEXTURE2D_DESC texture_desc; ID3D11Device *device; - UINT cpu_usage; HRESULT hr; - switch (buffer->_2d.lock_flags) - { - case MF2DBuffer_LockFlags_Read: - cpu_usage = D3D11_CPU_ACCESS_READ; - break; - - case MF2DBuffer_LockFlags_Write: - cpu_usage = D3D11_CPU_ACCESS_WRITE; - break; - - case MF2DBuffer_LockFlags_ReadWrite: - cpu_usage = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE; - break; - - default: - return E_INVALIDARG; - } - if (buffer->dxgi_surface.rb_texture) return S_OK; @@ -996,7 +948,7 @@ static HRESULT dxgi_surface_buffer_create_readback_texture(struct buffer *buffer ID3D11Texture2D_GetDesc(buffer->dxgi_surface.texture, &texture_desc); texture_desc.Usage = D3D11_USAGE_STAGING; texture_desc.BindFlags = 0; - texture_desc.CPUAccessFlags = cpu_usage; + texture_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE; texture_desc.MiscFlags = 0; texture_desc.MipLevels = 1; if (FAILED(hr = ID3D11Device_CreateTexture2D(device, &texture_desc, NULL, &buffer->dxgi_surface.rb_texture))) @@ -1007,34 +959,11 @@ static HRESULT dxgi_surface_buffer_create_readback_texture(struct buffer *buffer return hr; } -static HRESULT dxgi_surface_buffer_map(struct buffer *buffer) +static HRESULT dxgi_surface_buffer_map(struct buffer *buffer, MF2DBuffer_LockFlags flags) { ID3D11DeviceContext *immediate_context; ID3D11Device *device; - D3D11_MAP map_type; HRESULT hr; - BOOL copy; - - switch (buffer->_2d.lock_flags) - { - case MF2DBuffer_LockFlags_Read: - map_type = D3D11_MAP_READ; - copy = TRUE; - break; - - case MF2DBuffer_LockFlags_Write: - map_type = D3D11_MAP_WRITE; - copy = FALSE; - break; - - case MF2DBuffer_LockFlags_ReadWrite: - map_type = D3D11_MAP_READ_WRITE; - copy = TRUE; - break; - - default: - return E_INVALIDARG; - } if (FAILED(hr = dxgi_surface_buffer_create_readback_texture(buffer))) return hr; @@ -1042,7 +971,7 @@ static HRESULT dxgi_surface_buffer_map(struct buffer *buffer) ID3D11Texture2D_GetDevice(buffer->dxgi_surface.texture, &device); ID3D11Device_GetImmediateContext(device, &immediate_context); - if (copy) + if (flags == MF2DBuffer_LockFlags_Read || flags == MF2DBuffer_LockFlags_ReadWrite) { ID3D11DeviceContext_CopySubresourceRegion(immediate_context, (ID3D11Resource *)buffer->dxgi_surface.rb_texture, 0, 0, 0, 0, (ID3D11Resource *)buffer->dxgi_surface.texture, buffer->dxgi_surface.sub_resource_idx, NULL); @@ -1050,7 +979,7 @@ static HRESULT dxgi_surface_buffer_map(struct buffer *buffer) memset(&buffer->dxgi_surface.map_desc, 0, sizeof(buffer->dxgi_surface.map_desc)); if (FAILED(hr = ID3D11DeviceContext_Map(immediate_context, (ID3D11Resource *)buffer->dxgi_surface.rb_texture, - 0, map_type, 0, &buffer->dxgi_surface.map_desc))) + 0, D3D11_MAP_READ_WRITE, 0, &buffer->dxgi_surface.map_desc))) { WARN("Failed to map readback texture, hr %#lx.\n", hr); } @@ -1061,36 +990,17 @@ static HRESULT dxgi_surface_buffer_map(struct buffer *buffer) return hr; } -static void dxgi_surface_buffer_unmap(struct buffer *buffer) +static void dxgi_surface_buffer_unmap(struct buffer *buffer, MF2DBuffer_LockFlags flags) { ID3D11DeviceContext *immediate_context; ID3D11Device *device; - BOOL copy; - - switch (buffer->_2d.lock_flags) - { - case MF2DBuffer_LockFlags_Read: - copy = FALSE; - break; - - case MF2DBuffer_LockFlags_Write: - copy = TRUE; - break; - - case MF2DBuffer_LockFlags_ReadWrite: - copy = TRUE; - break; - - default: - copy = FALSE; - } ID3D11Texture2D_GetDevice(buffer->dxgi_surface.texture, &device); ID3D11Device_GetImmediateContext(device, &immediate_context); ID3D11DeviceContext_Unmap(immediate_context, (ID3D11Resource *)buffer->dxgi_surface.rb_texture, 0); memset(&buffer->dxgi_surface.map_desc, 0, sizeof(buffer->dxgi_surface.map_desc)); - if (copy) + if (flags == MF2DBuffer_LockFlags_Write || flags == MF2DBuffer_LockFlags_ReadWrite) { ID3D11DeviceContext_CopySubresourceRegion(immediate_context, (ID3D11Resource *)buffer->dxgi_surface.texture, buffer->dxgi_surface.sub_resource_idx, 0, 0, 0, (ID3D11Resource *)buffer->dxgi_surface.rb_texture, 0, NULL); @@ -1122,12 +1032,11 @@ static HRESULT WINAPI dxgi_surface_buffer_Lock(IMFMediaBuffer *iface, BYTE **dat if (SUCCEEDED(hr)) { - buffer->_2d.lock_flags = MF2DBuffer_LockFlags_ReadWrite; - hr = dxgi_surface_buffer_map(buffer); + hr = dxgi_surface_buffer_map(buffer, MF2DBuffer_LockFlags_ReadWrite); if (SUCCEEDED(hr)) { - hr = copy_image(buffer, buffer->_2d.linear_buffer, buffer->_2d.width, buffer->dxgi_surface.map_desc.pData, - buffer->dxgi_surface.map_desc.RowPitch, buffer->_2d.width, buffer->_2d.height, buffer->_2d.plane_size); + copy_image(buffer, buffer->_2d.linear_buffer, buffer->_2d.width, buffer->dxgi_surface.map_desc.pData, + buffer->dxgi_surface.map_desc.RowPitch, buffer->_2d.width, buffer->_2d.height); } } } @@ -1160,9 +1069,9 @@ static HRESULT WINAPI dxgi_surface_buffer_Unlock(IMFMediaBuffer *iface) hr = HRESULT_FROM_WIN32(ERROR_WAS_UNLOCKED); else if (!--buffer->_2d.locks) { - hr = copy_image(buffer, buffer->dxgi_surface.map_desc.pData, buffer->dxgi_surface.map_desc.RowPitch, - buffer->_2d.linear_buffer, buffer->_2d.width, buffer->_2d.width, buffer->_2d.height, buffer->max_length); - dxgi_surface_buffer_unmap(buffer); + copy_image(buffer, buffer->dxgi_surface.map_desc.pData, buffer->dxgi_surface.map_desc.RowPitch, + buffer->_2d.linear_buffer, buffer->_2d.width, buffer->_2d.width, buffer->_2d.height); + dxgi_surface_buffer_unmap(buffer, MF2DBuffer_LockFlags_ReadWrite); free(buffer->_2d.linear_buffer); buffer->_2d.linear_buffer = NULL; @@ -1191,24 +1100,24 @@ static HRESULT dxgi_surface_buffer_lock(struct buffer *buffer, MF2DBuffer_LockFl if (buffer->_2d.linear_buffer) hr = MF_E_UNEXPECTED; - else if (!buffer->_2d.locks++) - { - buffer->_2d.lock_flags = flags; - hr = dxgi_surface_buffer_map(buffer); - } - else if (buffer->_2d.lock_flags != flags) - { - WARN("relocking DXGI surface buffer %p with different flags!\n", buffer); - } + else if (!buffer->_2d.locks) + hr = dxgi_surface_buffer_map(buffer, flags); + else if (buffer->_2d.lock_flags == MF2DBuffer_LockFlags_Write && flags != MF2DBuffer_LockFlags_Write) + hr = HRESULT_FROM_WIN32(ERROR_WAS_LOCKED); if (SUCCEEDED(hr)) { + if (!buffer->_2d.locks) + buffer->_2d.lock_flags = flags; + else + buffer->_2d.lock_flags |= flags; + buffer->_2d.locks++; *scanline0 = buffer->dxgi_surface.map_desc.pData; *pitch = buffer->dxgi_surface.map_desc.RowPitch; if (buffer_start) *buffer_start = *scanline0; if (buffer_length) - *buffer_length = buffer->dxgi_surface.map_desc.RowPitch * buffer->_2d.height; + *buffer_length = buffer->dxgi_surface.map_desc.DepthPitch; } return hr; @@ -1245,7 +1154,10 @@ static HRESULT WINAPI dxgi_surface_buffer_Unlock2D(IMF2DBuffer2 *iface) if (buffer->_2d.locks) { if (!--buffer->_2d.locks) - dxgi_surface_buffer_unmap(buffer); + { + dxgi_surface_buffer_unmap(buffer, buffer->_2d.lock_flags); + buffer->_2d.lock_flags = 0; + } } else hr = HRESULT_FROM_WIN32(ERROR_WAS_UNLOCKED); @@ -1478,13 +1390,26 @@ static HRESULT create_1d_buffer(DWORD max_length, DWORD alignment, IMFMediaBuffe static p_copy_image_func get_2d_buffer_copy_func(DWORD fourcc) { - if (fourcc == MAKEFOURCC('N','V','1','2')) - return copy_image_nv12; - if (fourcc == MAKEFOURCC('I','M','C','1') || fourcc == MAKEFOURCC('I','M','C','3')) - return copy_image_imc1; - if (fourcc == MAKEFOURCC('I','M','C','2') || fourcc == MAKEFOURCC('I','M','C','4') || fourcc == MAKEFOURCC('Y','V','1', '2')) - return copy_image_imc2; - return NULL; + switch (fourcc) + { + case MAKEFOURCC('I','M','C','1'): + case MAKEFOURCC('I','M','C','3'): + return copy_image_imc1; + + case MAKEFOURCC('I','M','C','2'): + case MAKEFOURCC('I','M','C','4'): + case MAKEFOURCC('N','V','1','1'): + case MAKEFOURCC('Y','V','1','2'): + case MAKEFOURCC('I','4','2','0'): + case MAKEFOURCC('I','Y','U','V'): + return copy_image_imc2; + + case MAKEFOURCC('N','V','1','2'): + return copy_image_nv12; + + default: + return NULL; + } } static HRESULT create_2d_buffer(DWORD width, DWORD height, DWORD fourcc, BOOL bottom_up, IMFMediaBuffer **buffer) @@ -1520,12 +1445,13 @@ static HRESULT create_2d_buffer(DWORD width, DWORD height, DWORD fourcc, BOOL bo break; case MAKEFOURCC('I','M','C','2'): case MAKEFOURCC('I','M','C','4'): - plane_size = stride * 3 / 2 * height; - break; - case MAKEFOURCC('N','V','1','2'): + case MAKEFOURCC('N','V','1','1'): case MAKEFOURCC('Y','V','1','2'): case MAKEFOURCC('I','4','2','0'): case MAKEFOURCC('I','Y','U','V'): + plane_size = stride * 3 / 2 * height; + break; + case MAKEFOURCC('N','V','1','2'): plane_size = stride * height * 3 / 2; break; default: @@ -1542,6 +1468,9 @@ static HRESULT create_2d_buffer(DWORD width, DWORD height, DWORD fourcc, BOOL bo case MAKEFOURCC('I','M','C','3'): case MAKEFOURCC('I','M','C','4'): case MAKEFOURCC('Y','V','1','2'): + case MAKEFOURCC('N','V','1','1'): + case MAKEFOURCC('I','4','2','0'): + case MAKEFOURCC('I','Y','U','V'): row_alignment = MF_128_BYTE_ALIGNMENT; break; default: @@ -1560,6 +1489,9 @@ static HRESULT create_2d_buffer(DWORD width, DWORD height, DWORD fourcc, BOOL bo case MAKEFOURCC('Y','V','1','2'): case MAKEFOURCC('I','M','C','2'): case MAKEFOURCC('I','M','C','4'): + case MAKEFOURCC('N','V','1','1'): + case MAKEFOURCC('I','4','2','0'): + case MAKEFOURCC('I','Y','U','V'): max_length = pitch * height * 3 / 2; break; default: diff --git a/dlls/mfplat/main.c b/dlls/mfplat/main.c index ed69d501f2a..d8f621d4559 100644 --- a/dlls/mfplat/main.c +++ b/dlls/mfplat/main.c @@ -3826,7 +3826,7 @@ static HRESULT bytestream_create_io_request(struct bytestream *stream, enum asyn &stream->write_callback, NULL, &request))) goto failed; - RtwqPutWorkItem(MFASYNC_CALLBACK_QUEUE_STANDARD, 0, request); + RtwqPutWorkItem(MFASYNC_CALLBACK_QUEUE_IO, 0, request); IRtwqAsyncResult_Release(request); failed: diff --git a/dlls/mfplat/mediatype.c b/dlls/mfplat/mediatype.c index 2e16de96849..374024d8190 100644 --- a/dlls/mfplat/mediatype.c +++ b/dlls/mfplat/mediatype.c @@ -2713,13 +2713,20 @@ static const struct uncompressed_video_format video_formats[] = { &MFVideoFormat_IMC3, 2, 3, 0, 1 }, { &MFVideoFormat_IMC4, 1, 0, 0, 1 }, { &MFVideoFormat_IYUV, 1, 0, 0, 1 }, + { &MFVideoFormat_NV11, 1, 0, 0, 1 }, { &MFVideoFormat_NV12, 1, 0, 0, 1 }, { &MFVideoFormat_D16, 2, 3, 0, 0 }, { &MFVideoFormat_L16, 2, 3, 0, 0 }, { &MFVideoFormat_UYVY, 2, 0, 0, 1 }, { &MFVideoFormat_YUY2, 2, 0, 0, 1 }, { &MFVideoFormat_YV12, 1, 0, 0, 1 }, + { &MFVideoFormat_YVYU, 2, 0, 0, 1 }, { &MFVideoFormat_A16B16G16R16F, 8, 3, 1, 0 }, + { &MEDIASUBTYPE_RGB8, 1, 3, 1, 0 }, + { &MEDIASUBTYPE_RGB565, 2, 3, 1, 0 }, + { &MEDIASUBTYPE_RGB555, 2, 3, 1, 0 }, + { &MEDIASUBTYPE_RGB24, 3, 3, 1, 0 }, + { &MEDIASUBTYPE_RGB32, 4, 3, 1, 0 }, }; static struct uncompressed_video_format *mf_get_video_format(const GUID *subtype) @@ -2799,6 +2806,9 @@ HRESULT WINAPI MFCalculateImageSize(REFGUID subtype, UINT32 width, UINT32 height /* 2 x 2 block, interleaving UV for half the height */ *size = ((width + 1) & ~1) * height * 3 / 2; break; + case MAKEFOURCC('N','V','1','1'): + *size = ((width + 3) & ~3) * height * 3 / 2; + break; case D3DFMT_L8: case D3DFMT_L16: case D3DFMT_D16: @@ -2839,6 +2849,7 @@ HRESULT WINAPI MFGetPlaneSize(DWORD fourcc, DWORD width, DWORD height, DWORD *si case MAKEFOURCC('Y','V','1','2'): case MAKEFOURCC('I','4','2','0'): case MAKEFOURCC('I','Y','U','V'): + case MAKEFOURCC('N','V','1','1'): *size = stride * height * 3 / 2; break; default: diff --git a/dlls/mfplat/sample.c b/dlls/mfplat/sample.c index 6a589e4757b..8e489d22acd 100644 --- a/dlls/mfplat/sample.c +++ b/dlls/mfplat/sample.c @@ -852,8 +852,9 @@ static HRESULT WINAPI sample_CopyToBuffer(IMFSample *iface, IMFMediaBuffer *buff struct sample *sample = impl_from_IMFSample(iface); DWORD total_length, dst_length, dst_current_length, src_max_length, current_length; BYTE *src_ptr, *dst_ptr; - BOOL locked; - HRESULT hr; + IMF2DBuffer *buffer2d; + BOOL locked = FALSE; + HRESULT hr = E_FAIL; size_t i; TRACE("%p, %p.\n", iface, buffer); @@ -872,39 +873,64 @@ static HRESULT WINAPI sample_CopyToBuffer(IMFSample *iface, IMFMediaBuffer *buff total_length = sample_get_total_length(sample); dst_current_length = 0; + if (sample->buffer_count == 1 + && SUCCEEDED(IMFMediaBuffer_QueryInterface(buffer, &IID_IMF2DBuffer, (void **)&buffer2d))) + { + if (SUCCEEDED(IMFMediaBuffer_GetCurrentLength(sample->buffers[0], ¤t_length)) + && SUCCEEDED(IMF2DBuffer_GetContiguousLength(buffer2d, &dst_length)) + && current_length == dst_length + && SUCCEEDED(IMFMediaBuffer_Lock(sample->buffers[0], &src_ptr, &src_max_length, ¤t_length))) + { + hr = IMF2DBuffer_ContiguousCopyFrom(buffer2d, src_ptr, current_length); + IMFMediaBuffer_Unlock(sample->buffers[0]); + } + IMF2DBuffer_Release(buffer2d); + if (SUCCEEDED(hr)) + { + dst_current_length = current_length; + goto done; + } + } + dst_ptr = NULL; dst_length = current_length = 0; locked = SUCCEEDED(hr = IMFMediaBuffer_Lock(buffer, &dst_ptr, &dst_length, ¤t_length)); - if (locked) + if (!locked) + goto done; + + if (dst_length < total_length) { - if (dst_length < total_length) - hr = MF_E_BUFFERTOOSMALL; - else if (dst_ptr) + hr = MF_E_BUFFERTOOSMALL; + goto done; + } + + if (!dst_ptr) + goto done; + + for (i = 0; i < sample->buffer_count && SUCCEEDED(hr); ++i) + { + src_ptr = NULL; + src_max_length = current_length = 0; + + if (FAILED(hr = IMFMediaBuffer_Lock(sample->buffers[i], &src_ptr, &src_max_length, ¤t_length))) + continue; + + if (src_ptr) { - for (i = 0; i < sample->buffer_count && SUCCEEDED(hr); ++i) + if (current_length > dst_length) + hr = MF_E_BUFFERTOOSMALL; + else if (current_length) { - src_ptr = NULL; - src_max_length = current_length = 0; - if (SUCCEEDED(hr = IMFMediaBuffer_Lock(sample->buffers[i], &src_ptr, &src_max_length, ¤t_length))) - { - if (src_ptr) - { - if (current_length > dst_length) - hr = MF_E_BUFFERTOOSMALL; - else if (current_length) - { - memcpy(dst_ptr, src_ptr, current_length); - dst_length -= current_length; - dst_current_length += current_length; - dst_ptr += current_length; - } - } - IMFMediaBuffer_Unlock(sample->buffers[i]); - } + memcpy(dst_ptr, src_ptr, current_length); + dst_length -= current_length; + dst_current_length += current_length; + dst_ptr += current_length; } } + IMFMediaBuffer_Unlock(sample->buffers[i]); } +done: IMFMediaBuffer_SetCurrentLength(buffer, dst_current_length); if (locked) diff --git a/dlls/mfplat/tests/mfplat.c b/dlls/mfplat/tests/mfplat.c index 2dd3e834a0e..7264db7f79b 100644 --- a/dlls/mfplat/tests/mfplat.c +++ b/dlls/mfplat/tests/mfplat.c @@ -4431,6 +4431,42 @@ image_size_tests[] = { &MFVideoFormat_NV11, 3, 2, 12, 9, 384, 8, 128 }, { &MFVideoFormat_NV11, 4, 2, 12, 0, 384, 12, 128 }, { &MFVideoFormat_NV11, 320, 240, 115200, 0, 138240, 115200, 384 }, + + { &MFVideoFormat_YV12, 1, 1, 3, 1, 192, 1, 128 }, + { &MFVideoFormat_YV12, 1, 2, 6, 3, 384, 2, 128 }, + { &MFVideoFormat_YV12, 1, 3, 9, 4, 576, 3, 128 }, + { &MFVideoFormat_YV12, 2, 1, 3, 0, 192, 3, 128 }, + { &MFVideoFormat_YV12, 2, 2, 6, 6, 384, 6, 128 }, + { &MFVideoFormat_YV12, 2, 4, 12, 0, 768, 12, 128 }, + { &MFVideoFormat_YV12, 3, 2, 12, 9, 384, 8, 128 }, + { &MFVideoFormat_YV12, 3, 5, 30, 22, 960, 20, 128 }, + { &MFVideoFormat_YV12, 4, 2, 12, 0, 384, 12, 128 }, + { &MFVideoFormat_YV12, 4, 3, 18, 0, 576, 18, 128 }, + { &MFVideoFormat_YV12, 320, 240, 115200, 0, 138240, 115200, 384 }, + + { &MFVideoFormat_I420, 1, 1, 3, 1, 192, 1, 128 }, + { &MFVideoFormat_I420, 1, 2, 6, 3, 384, 2, 128 }, + { &MFVideoFormat_I420, 1, 3, 9, 4, 576, 3, 128 }, + { &MFVideoFormat_I420, 2, 1, 3, 0, 192, 3, 128 }, + { &MFVideoFormat_I420, 2, 2, 6, 6, 384, 6, 128 }, + { &MFVideoFormat_I420, 2, 4, 12, 0, 768, 12, 128 }, + { &MFVideoFormat_I420, 3, 2, 12, 9, 384, 8, 128 }, + { &MFVideoFormat_I420, 3, 5, 30, 22, 960, 20, 128 }, + { &MFVideoFormat_I420, 4, 2, 12, 0, 384, 12, 128 }, + { &MFVideoFormat_I420, 4, 3, 18, 0, 576, 18, 128 }, + { &MFVideoFormat_I420, 320, 240, 115200, 0, 138240, 115200, 384 }, + + { &MFVideoFormat_IYUV, 1, 1, 3, 1, 192, 1, 128 }, + { &MFVideoFormat_IYUV, 1, 2, 6, 3, 384, 2, 128 }, + { &MFVideoFormat_IYUV, 1, 3, 9, 4, 576, 3, 128 }, + { &MFVideoFormat_IYUV, 2, 1, 3, 0, 192, 3, 128 }, + { &MFVideoFormat_IYUV, 2, 2, 6, 6, 384, 6, 128 }, + { &MFVideoFormat_IYUV, 2, 4, 12, 0, 768, 12, 128 }, + { &MFVideoFormat_IYUV, 3, 2, 12, 9, 384, 8, 128 }, + { &MFVideoFormat_IYUV, 3, 5, 30, 22, 960, 20, 128 }, + { &MFVideoFormat_IYUV, 4, 2, 12, 0, 384, 12, 128 }, + { &MFVideoFormat_IYUV, 4, 3, 18, 0, 576, 18, 128 }, + { &MFVideoFormat_IYUV, 320, 240, 115200, 0, 138240, 115200, 384 }, }; static void test_MFCalculateImageSize(void) @@ -4453,7 +4489,6 @@ static void test_MFCalculateImageSize(void) IsEqualGUID(ptr->subtype, &MFVideoFormat_A2R10G10B10); hr = MFCalculateImageSize(ptr->subtype, ptr->width, ptr->height, &size); - todo_wine_if(is_MEDIASUBTYPE_RGB(ptr->subtype) || IsEqualGUID(ptr->subtype, &MFVideoFormat_NV11)) ok(hr == S_OK || (is_broken && hr == E_INVALIDARG), "%u: failed to calculate image size, hr %#lx.\n", i, hr); todo_wine_if(is_MEDIASUBTYPE_RGB(ptr->subtype) || IsEqualGUID(ptr->subtype, &MFVideoFormat_NV11) @@ -5827,9 +5862,7 @@ static void test_MFGetStrideForBitmapInfoHeader(void) for (i = 0; i < ARRAY_SIZE(stride_tests); ++i) { hr = pMFGetStrideForBitmapInfoHeader(stride_tests[i].subtype->Data1, stride_tests[i].width, &stride); - todo_wine_if(IsEqualGUID(stride_tests[i].subtype, &MFVideoFormat_NV11)) ok(hr == S_OK, "%u: failed to get stride, hr %#lx.\n", i, hr); - todo_wine_if(IsEqualGUID(stride_tests[i].subtype, &MFVideoFormat_NV11)) ok(stride == stride_tests[i].stride, "%u: format %s, unexpected stride %ld, expected %ld.\n", i, wine_dbgstr_an((char *)&stride_tests[i].subtype->Data1, 4), stride, stride_tests[i].stride); } @@ -6041,29 +6074,142 @@ static void test_MFCreate2DMediaBuffer(void) if (is_MEDIASUBTYPE_RGB(ptr->subtype)) continue; + winetest_push_context("%u, %u x %u, format %s", i, ptr->width, ptr->height, wine_dbgstr_guid(ptr->subtype)); + hr = pMFCreate2DMediaBuffer(ptr->width, ptr->height, ptr->subtype->Data1, FALSE, &buffer); - todo_wine_if(IsEqualGUID(ptr->subtype, &MFVideoFormat_NV11)) ok(hr == S_OK, "Failed to create a buffer, hr %#lx.\n", hr); - if (hr != S_OK) - continue; hr = IMFMediaBuffer_GetMaxLength(buffer, &length); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - ok(length == ptr->max_length, "%u: unexpected maximum length %lu for %u x %u, format %s.\n", - i, length, ptr->width, ptr->height, wine_dbgstr_guid(ptr->subtype)); + ok(length == ptr->max_length, "Unexpected maximum length %lu.\n", length); hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMF2DBuffer, (void **)&_2dbuffer); ok(hr == S_OK, "Failed to get interface, hr %#lx.\n", hr); + hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMF2DBuffer2, (void **)&_2dbuffer2); + ok(hr == S_OK, "Failed to get interface, hr %#lx.\n", hr); + hr = IMF2DBuffer_GetContiguousLength(_2dbuffer, &length); ok(hr == S_OK, "Failed to get length, hr %#lx.\n", hr); - todo_wine_if(IsEqualGUID(ptr->subtype, &MFVideoFormat_RGB24) && ptr->width % 4 == 0) - ok(length == ptr->contiguous_length, "%d: unexpected contiguous length %lu for %u x %u, format %s.\n", - i, length, ptr->width, ptr->height, wine_dbgstr_guid(ptr->subtype)); + ok(length == ptr->contiguous_length, "Unexpected contiguous length %lu.\n", length); + + data2 = malloc(ptr->contiguous_length + 16); + ok(!!data2, "Failed to allocate buffer.\n"); + + for (j = 0; j < ptr->contiguous_length + 16; j++) + data2[j] = j & 0x7f; + + hr = IMF2DBuffer2_ContiguousCopyFrom(_2dbuffer2, data2, ptr->contiguous_length - 1); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaBuffer_Lock(buffer, &data, &length2, NULL); + ok(hr == S_OK, "Failed to lock buffer, hr %#lx.\n", hr); + ok(length2 == ptr->contiguous_length, "Unexpected linear buffer length %lu.\n", length2); + + memset(data, 0xff, length2); + + hr = IMFMediaBuffer_Unlock(buffer); + ok(hr == S_OK, "Failed to unlock buffer, hr %#lx.\n", hr); + + hr = IMF2DBuffer2_ContiguousCopyFrom(_2dbuffer2, data2, ptr->contiguous_length + 16); + ok(hr == S_OK, "Failed to copy from contiguous buffer, hr %#lx.\n", hr); + + hr = IMFMediaBuffer_Lock(buffer, &data, &length2, NULL); + ok(hr == S_OK, "Failed to lock buffer, hr %#lx.\n", hr); + ok(length2 == ptr->contiguous_length, "%d: unexpected linear buffer length %lu for %u x %u, format %s.\n", + i, length2, ptr->width, ptr->height, wine_dbgstr_guid(ptr->subtype)); + + for (j = 0; j < ptr->contiguous_length; j++) + { + if (IsEqualGUID(ptr->subtype, &MFVideoFormat_IMC1) || IsEqualGUID(ptr->subtype, &MFVideoFormat_IMC3)) + { + if (j < ptr->height * ptr->pitch && j % ptr->pitch >= ptr->width) + continue; + if (j >= ptr->height * ptr->pitch && j % ptr->pitch >= ptr->width / 2) + continue; + } + if (data[j] != (j & 0x7f)) + break; + } + ok(j == ptr->contiguous_length, "Unexpected byte %02x instead of %02x at position %u.\n", data[j], j & 0x7f, j); + + memset(data, 0xff, length2); + + hr = IMFMediaBuffer_Unlock(buffer); + ok(hr == S_OK, "Failed to unlock buffer, hr %#lx.\n", hr); + + hr = IMF2DBuffer2_ContiguousCopyFrom(_2dbuffer2, data2, ptr->contiguous_length); + ok(hr == S_OK, "Failed to copy from contiguous buffer, hr %#lx.\n", hr); + + free(data2); + + hr = IMFMediaBuffer_Lock(buffer, &data, &length2, NULL); + ok(hr == S_OK, "Failed to lock buffer, hr %#lx.\n", hr); + ok(length2 == ptr->contiguous_length, "%d: unexpected linear buffer length %lu for %u x %u, format %s.\n", + i, length2, ptr->width, ptr->height, wine_dbgstr_guid(ptr->subtype)); + + for (j = 0; j < ptr->contiguous_length; j++) + { + if (IsEqualGUID(ptr->subtype, &MFVideoFormat_IMC1) || IsEqualGUID(ptr->subtype, &MFVideoFormat_IMC3)) + { + if (j < ptr->height * ptr->pitch && j % ptr->pitch >= ptr->width) + continue; + if (j >= ptr->height * ptr->pitch && j % ptr->pitch >= ptr->width / 2) + continue; + } + if (data[j] != (j & 0x7f)) + break; + } + ok(j == ptr->contiguous_length, "Unexpected byte %02x instead of %02x at position %u.\n", data[j], j & 0x7f, j); + + hr = IMFMediaBuffer_Unlock(buffer); + ok(hr == S_OK, "Failed to unlock buffer, hr %#lx.\n", hr); + + hr = IMF2DBuffer2_ContiguousCopyTo(_2dbuffer2, data2, ptr->contiguous_length - 1); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + + memset(data2, 0xff, ptr->contiguous_length + 16); + + hr = IMF2DBuffer2_ContiguousCopyTo(_2dbuffer2, data2, ptr->contiguous_length + 16); + ok(hr == S_OK, "Failed to copy to contiguous buffer, hr %#lx.\n", hr); + + for (j = 0; j < ptr->contiguous_length; j++) + { + if (IsEqualGUID(ptr->subtype, &MFVideoFormat_IMC1) || IsEqualGUID(ptr->subtype, &MFVideoFormat_IMC3)) + { + if (j < ptr->height * ptr->pitch && j % ptr->pitch >= ptr->width) + continue; + if (j >= ptr->height * ptr->pitch && j % ptr->pitch >= ptr->width / 2) + continue; + } + if (data2[j] != (j & 0x7f)) + break; + } + ok(j == ptr->contiguous_length, "Unexpected byte %02x instead of %02x at position %u.\n", data2[j], j & 0x7f, j); + + memset(data2, 0xff, ptr->contiguous_length + 16); + + hr = IMF2DBuffer2_ContiguousCopyTo(_2dbuffer2, data2, ptr->contiguous_length); + ok(hr == S_OK, "Failed to copy to contiguous buffer, hr %#lx.\n", hr); + + for (j = 0; j < ptr->contiguous_length; j++) + { + if (IsEqualGUID(ptr->subtype, &MFVideoFormat_IMC1) || IsEqualGUID(ptr->subtype, &MFVideoFormat_IMC3)) + { + if (j < ptr->height * ptr->pitch && j % ptr->pitch >= ptr->width) + continue; + if (j >= ptr->height * ptr->pitch && j % ptr->pitch >= ptr->width / 2) + continue; + } + if (data2[j] != (j & 0x7f)) + break; + } + ok(j == ptr->contiguous_length, "Unexpected byte %02x instead of %02x at position %u.\n", data2[j], j & 0x7f, j); + + free(data2); hr = IMFMediaBuffer_Lock(buffer, &data, &length2, NULL); ok(hr == S_OK, "Failed to lock buffer, hr %#lx.\n", hr); - todo_wine_if(IsEqualGUID(ptr->subtype, &MFVideoFormat_RGB24) && ptr->width % 4 == 0) ok(length2 == ptr->contiguous_length, "%d: unexpected linear buffer length %lu for %u x %u, format %s.\n", i, length2, ptr->width, ptr->height, wine_dbgstr_guid(ptr->subtype)); @@ -6094,6 +6240,10 @@ static void test_MFCreate2DMediaBuffer(void) case MAKEFOURCC('I','M','C','2'): case MAKEFOURCC('I','M','C','4'): + case MAKEFOURCC('N','V','1','1'): + case MAKEFOURCC('Y','V','1','2'): + case MAKEFOURCC('I','4','2','0'): + case MAKEFOURCC('I','Y','U','V'): ok(stride * 3 / 2 * ptr->height <= length2, "Insufficient buffer space: expected at least %lu bytes, got only %lu\n", stride * 3 / 2 * ptr->height, length2); for (j = 0; j < ptr->height; j++) @@ -6144,6 +6294,10 @@ static void test_MFCreate2DMediaBuffer(void) case MAKEFOURCC('I','M','C','2'): case MAKEFOURCC('I','M','C','4'): + case MAKEFOURCC('N','V','1','1'): + case MAKEFOURCC('Y','V','1','2'): + case MAKEFOURCC('I','4','2','0'): + case MAKEFOURCC('I','Y','U','V'): for (j = 0; j < ptr->height; j++) for (k = 0; k < stride / 2; k++) ok(data[j * (pitch / 2) + k] == (((j + ptr->height) % 16) << 4) + (k % 16), @@ -6188,10 +6342,7 @@ static void test_MFCreate2DMediaBuffer(void) continue; hr = pMFCreate2DMediaBuffer(ptr->width, ptr->height, ptr->subtype->Data1, FALSE, &buffer); - todo_wine_if(IsEqualGUID(ptr->subtype, &MFVideoFormat_NV11)) ok(hr == S_OK, "Failed to create a buffer, hr %#lx.\n", hr); - if (hr != S_OK) - continue; hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMF2DBuffer, (void **)&_2dbuffer); ok(hr == S_OK, "Failed to get interface, hr %#lx.\n", hr); @@ -6715,7 +6866,39 @@ static void test_MFCreateDXSurfaceBuffer(void) hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_ReadWrite, &data, &pitch, &data2, &length); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - IMF2DBuffer2_Unlock2D(_2dbuffer2); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer_Lock2D(_2dbuffer, &data, &pitch); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Except when originally locking for writing. */ + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_ReadWrite, &data, &pitch, &data2, &length); + ok(hr == HRESULT_FROM_WIN32(ERROR_WAS_LOCKED), "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == HRESULT_FROM_WIN32(ERROR_WAS_LOCKED), "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer_Lock2D(_2dbuffer, &data, &pitch); + ok(hr == HRESULT_FROM_WIN32(ERROR_WAS_LOCKED), "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == HRESULT_FROM_WIN32(ERROR_WAS_UNLOCKED), "Unexpected hr %#lx.\n", hr); IMF2DBuffer2_Release(_2dbuffer2); @@ -7105,6 +7288,7 @@ static ID3D12Device *create_d3d12_device(void) static void test_d3d11_surface_buffer(void) { DWORD max_length, cur_length, length, color; + BYTE *data, *data2, *buffer_start; IMFDXGIBuffer *dxgi_buffer; D3D11_TEXTURE2D_DESC desc; ID3D11Texture2D *texture; @@ -7112,7 +7296,6 @@ static void test_d3d11_surface_buffer(void) IMFMediaBuffer *buffer; ID3D11Device *device; BYTE buff[64 * 64 * 4]; - BYTE *data, *data2; LONG pitch, pitch2; UINT index, size; IUnknown *obj; @@ -7276,7 +7459,120 @@ static void test_d3d11_surface_buffer(void) hr = IMF2DBuffer_Unlock2D(_2d_buffer); ok(hr == HRESULT_FROM_WIN32(ERROR_WAS_UNLOCKED), "Unexpected hr %#lx.\n", hr); + hr = IMFMediaBuffer_Lock(buffer, &data, NULL, NULL); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMF2DBuffer_Lock2D(_2d_buffer, &data, &pitch); + ok(hr == MF_E_UNEXPECTED, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaBuffer_Unlock(buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMF2DBuffer_Release(_2d_buffer); + + hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMF2DBuffer2, (void **)&_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Lock flags are honored, so reads and writes are discarded if + * the flags are not correct. */ + put_d3d11_texture_color(texture, 0, 0, 0xcdcdcdcd); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(data == data2, "Unexpected scanline pointer.\n"); + ok(*(DWORD *)data == 0xcdcdcdcd, "Unexpected leading dword %#lx.\n", *(DWORD *)data); + memset(data, 0xab, 4); + IMF2DBuffer2_Unlock2D(_2dbuffer2); + + color = get_d3d11_texture_color(texture, 0, 0); + ok(color == 0xcdcdcdcd, "Unexpected leading dword %#lx.\n", color); + put_d3d11_texture_color(texture, 0, 0, 0xefefefef); + + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(*(DWORD *)data != 0xefefefef, "Unexpected leading dword.\n"); + memset(data, 0x89, 4); + IMF2DBuffer2_Unlock2D(_2dbuffer2); + + color = get_d3d11_texture_color(texture, 0, 0); + ok(color == 0x89898989, "Unexpected leading dword %#lx.\n", color); + + /* When relocking for writing, stores are committed even if they + * were issued before relocking. */ + put_d3d11_texture_color(texture, 0, 0, 0xcdcdcdcd); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + memset(data, 0xab, 4); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMF2DBuffer2_Unlock2D(_2dbuffer2); + IMF2DBuffer2_Unlock2D(_2dbuffer2); + + color = get_d3d11_texture_color(texture, 0, 0); + ok(color == 0xabababab, "Unexpected leading dword %#lx.\n", color); + + /* Flags incompatibilities. */ + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_ReadWrite, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_ReadWrite, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer_Lock2D(_2d_buffer, &data, &pitch); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_ReadWrite, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer_Lock2D(_2d_buffer, &data, &pitch); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Except when originally locking for writing. */ + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_ReadWrite, &data, &pitch, &data2, &length); + ok(hr == HRESULT_FROM_WIN32(ERROR_WAS_LOCKED), "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == HRESULT_FROM_WIN32(ERROR_WAS_LOCKED), "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer_Lock2D(_2d_buffer, &data, &pitch); + ok(hr == HRESULT_FROM_WIN32(ERROR_WAS_LOCKED), "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == HRESULT_FROM_WIN32(ERROR_WAS_UNLOCKED), "Unexpected hr %#lx.\n", hr); + + IMF2DBuffer2_Release(_2dbuffer2); IMFMediaBuffer_Release(buffer); /* Bottom up. */ @@ -7302,7 +7598,54 @@ static void test_d3d11_surface_buffer(void) ID3D11Texture2D_Release(texture); - /* Subresource index 1. */ + memset(&desc, 0, sizeof(desc)); + desc.Width = 64; + desc.Height = 64; + desc.ArraySize = 1; + desc.MipLevels = 1; + desc.Format = DXGI_FORMAT_NV12; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + + hr = ID3D11Device_CreateTexture2D(device, &desc, NULL, &texture); + if (SUCCEEDED(hr)) + { + hr = pMFCreateDXGISurfaceBuffer(&IID_ID3D11Texture2D, (IUnknown *)texture, 0, FALSE, &buffer); + ok(hr == S_OK, "got %#lx.\n", hr); + hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMF2DBuffer2, (void **)&_2dbuffer2); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &buffer_start, &length); + ok(hr == S_OK, "got %#lx.\n", hr); + + ok(pitch >= desc.Width, "got %ld.\n", pitch); + ok(length == pitch * desc.Height * 3 / 2, "got %lu.\n", length); + + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "got %#lx.\n", hr); + + IMF2DBuffer2_Release(_2dbuffer2); + IMFMediaBuffer_Release(buffer); + ID3D11Texture2D_Release(texture); + } + else + { + win_skip("Failed to create NV12 texture, hr %#lx, skipping test.\n", hr); + ID3D11Device_Release(device); + return; + } + + /* Subresource index 1. + * When WARP d3d11 device is used, this test leaves the device in a broken state, so it should + * be kept last. */ + memset(&desc, 0, sizeof(desc)); + desc.Width = 64; + desc.Height = 64; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + hr = ID3D11Device_CreateTexture2D(device, &desc, NULL, &texture); ok(hr == S_OK, "Failed to create a texture, hr %#lx.\n", hr); @@ -7327,7 +7670,6 @@ static void test_d3d11_surface_buffer(void) IMF2DBuffer_Release(_2d_buffer); IMFMediaBuffer_Release(buffer); - ID3D11Texture2D_Release(texture); ID3D11Device_Release(device); @@ -8602,6 +8944,192 @@ static void test_MFInitMediaTypeFromAMMediaType(void) IMFMediaType_Release(media_type); } +#define check_reset_data(a, b, c, d, e) check_reset_data_(__LINE__, a, b, c, d, e) +static void check_reset_data_(unsigned int line, IMF2DBuffer2 *buffer2d, const BYTE *data, BOOL bottom_up, + DWORD width, DWORD height) +{ + BYTE *scanline0, *buffer_start; + DWORD length, max_length; + IMFMediaBuffer *buffer; + LONG pitch; + BYTE *lock; + HRESULT hr; + int i; + + hr = IMF2DBuffer2_QueryInterface(buffer2d, &IID_IMFMediaBuffer, (void **)&buffer); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + hr = IMF2DBuffer2_Lock2DSize(buffer2d, MF2DBuffer_LockFlags_Read, &scanline0, &pitch, &buffer_start, &length); + ok(hr == S_OK, "got hr %#lx.\n", hr); + if (bottom_up) + { + ok(pitch < 0, "got pitch %ld.\n", pitch); + ok(buffer_start == scanline0 + pitch * (LONG)(height - 1), "buffer start mismatch.\n"); + } + else + { + ok(pitch > 0, "got pitch %ld.\n", pitch); + ok(buffer_start == scanline0, "buffer start mismatch.\n"); + } + for (i = 0; i < height; ++i) + ok_(__FILE__,line)(!memcmp(buffer_start + abs(pitch) * i, data + width * i * 4, width * 4), + "2D Data mismatch, scaline %d.\n", i); + hr = IMF2DBuffer2_Unlock2D(buffer2d); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + hr = IMFMediaBuffer_Lock(buffer, &lock, &max_length, &length); + ok(hr == S_OK, "got hr %#lx.\n", hr); + ok_(__FILE__,line)(max_length == width * height * 4, "got max_length %lu.\n", max_length); + ok_(__FILE__,line)(length == width * height * 4, "got length %lu.\n", length); + ok_(__FILE__,line)(!memcmp(lock, data, length), "contiguous data mismatch.\n"); + memset(lock, 0xcc, length); + hr = IMFMediaBuffer_Unlock(buffer); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + IMFMediaBuffer_Release(buffer); +} + +static void test_2dbuffer_copy_(IMFMediaBuffer *buffer, BOOL bottom_up, DWORD width, DWORD height) +{ + static const unsigned int test_data[] = + { + 0x01010101, 0x01010101, + 0x02020202, 0x02020202, + }; + + BYTE data[sizeof(test_data)]; + IMFMediaBuffer *src_buffer; + DWORD length, max_length; + IMF2DBuffer2 *buffer2d; + IMFSample *sample; + BYTE *lock; + HRESULT hr; + ULONG ref; + + hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMF2DBuffer2, (void **)&buffer2d); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + hr = MFCreateSample(&sample); + ok(hr == S_OK, "got hr %#lx.\n", hr); + hr = MFCreateMemoryBuffer(sizeof(test_data) * 2, &src_buffer); + ok(hr == S_OK, "got hr %#lx.\n", hr); + hr = IMFSample_AddBuffer(sample, src_buffer); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + hr = IMFMediaBuffer_Lock(src_buffer, &lock, &max_length, &length); + ok(hr == S_OK, "got hr %#lx.\n", hr); + ok(max_length == sizeof(test_data) * 2, "got %lu.\n", max_length); + memcpy(lock, test_data, sizeof(test_data)); + hr = IMFMediaBuffer_Unlock(src_buffer); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + hr = IMFMediaBuffer_Lock(buffer, &lock, &max_length, &length); + ok(hr == S_OK, "got hr %#lx.\n", hr); + ok(max_length == 16, "got %lu.\n", max_length); + ok(length == 16, "got %lu.\n", length); + memset(lock, 0xcc, length); + hr = IMFMediaBuffer_Unlock(buffer); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + hr = IMFMediaBuffer_SetCurrentLength(src_buffer, 1); + ok(hr == S_OK, "got hr %#lx.\n", hr); + hr = IMFSample_CopyToBuffer(sample, buffer); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + memset(data, 0xcc, sizeof(data)); + data[0] = ((BYTE *)test_data)[0]; + check_reset_data(buffer2d, data, bottom_up, width, height); + + hr = IMF2DBuffer2_ContiguousCopyFrom(buffer2d, (BYTE *)test_data, sizeof(test_data)); + ok(hr == S_OK, "got hr %#lx.\n", hr); + hr = IMF2DBuffer2_ContiguousCopyTo(buffer2d, data, sizeof(data)); + ok(hr == S_OK, "got hr %#lx.\n", hr); + ok(!memcmp(data, test_data, sizeof(data)), "data mismatch.\n"); + + check_reset_data(buffer2d, (const BYTE *)test_data, bottom_up, width, height); + + hr = IMFMediaBuffer_SetCurrentLength(src_buffer, sizeof(test_data) + 1); + ok(hr == S_OK, "got hr %#lx.\n", hr); + hr = IMFSample_CopyToBuffer(sample, buffer); + ok(hr == MF_E_BUFFERTOOSMALL, "got hr %#lx.\n", hr); + + hr = IMFMediaBuffer_SetCurrentLength(src_buffer, sizeof(test_data)); + ok(hr == S_OK, "got hr %#lx.\n", hr); + hr = IMFSample_CopyToBuffer(sample, buffer); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + check_reset_data(buffer2d, (const BYTE *)test_data, bottom_up, width, height); + + IMF2DBuffer2_Release(buffer2d); + ref = IMFSample_Release(sample); + ok(!ref, "got %lu.\n", ref); + ref = IMFMediaBuffer_Release(src_buffer); + ok(!ref, "got %lu.\n", ref); +} + +static void test_2dbuffer_copy(void) +{ + D3D11_TEXTURE2D_DESC desc; + ID3D11Texture2D *texture; + IMFMediaBuffer *buffer; + ID3D11Device *device; + HRESULT hr; + ULONG ref; + + if (!pMFCreate2DMediaBuffer) + { + win_skip("MFCreate2DMediaBuffer() is not available.\n"); + return; + } + + winetest_push_context("top down"); + hr = pMFCreate2DMediaBuffer(2, 2, D3DFMT_A8R8G8B8, FALSE, &buffer); + ok(hr == S_OK, "got hr %#lx.\n", hr); + test_2dbuffer_copy_(buffer, FALSE, 2, 2); + ref = IMFMediaBuffer_Release(buffer); + ok(!ref, "got %lu.\n", ref); + winetest_pop_context(); + + winetest_push_context("bottom up"); + hr = pMFCreate2DMediaBuffer(2, 2, D3DFMT_A8R8G8B8, TRUE, &buffer); + ok(hr == S_OK, "got hr %#lx.\n", hr); + test_2dbuffer_copy_(buffer, TRUE, 2, 2); + ref = IMFMediaBuffer_Release(buffer); + ok(!ref, "got %lu.\n", ref); + winetest_pop_context(); + + if (!pMFCreateDXGISurfaceBuffer) + { + win_skip("MFCreateDXGISurfaceBuffer() is not available.\n"); + return; + } + + if (!(device = create_d3d11_device())) + { + skip("Failed to create a D3D11 device, skipping tests.\n"); + return; + } + + memset(&desc, 0, sizeof(desc)); + desc.Width = 2; + desc.Height = 2; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + hr = ID3D11Device_CreateTexture2D(device, &desc, NULL, &texture); + ok(hr == S_OK, "Failed to create a texture, hr %#lx.\n", hr); + + hr = pMFCreateDXGISurfaceBuffer(&IID_ID3D11Texture2D, (IUnknown *)texture, 0, FALSE, &buffer); + ok(hr == S_OK, "Failed to create a buffer, hr %#lx.\n", hr); + test_2dbuffer_copy_(buffer, FALSE, 2, 2); + + ID3D11Texture2D_Release(texture); + ref = IMFMediaBuffer_Release(buffer); + ok(!ref, "got %lu.\n", ref); + ID3D11Device_Release(device); +} + START_TEST(mfplat) { char **argv; @@ -8685,6 +9213,7 @@ START_TEST(mfplat) test_MFCreateVideoMediaTypeFromVideoInfoHeader(); test_MFInitMediaTypeFromVideoInfoHeader(); test_MFInitMediaTypeFromAMMediaType(); + test_2dbuffer_copy(); CoUninitialize(); } diff --git a/dlls/msmpeg2vdec/Makefile.in b/dlls/msmpeg2vdec/Makefile.in index 609c6a34f9a..d2dbf5adda0 100644 --- a/dlls/msmpeg2vdec/Makefile.in +++ b/dlls/msmpeg2vdec/Makefile.in @@ -1,4 +1 @@ MODULE = msmpeg2vdec.dll - -C_SRCS = \ - main.c diff --git a/dlls/ntdll/actctx.c b/dlls/ntdll/actctx.c index 721a2f339a5..99a5c8f9b8a 100644 --- a/dlls/ntdll/actctx.c +++ b/dlls/ntdll/actctx.c @@ -2565,6 +2565,15 @@ static void parse_application_elem( xmlbuf_t *xmlbuf, struct assembly *assembly, struct actctx_loader *acl, const struct xml_elem *parent ) { struct xml_elem elem; + struct xml_attr attr; + BOOL end = FALSE; + + while (next_xml_attr(xmlbuf, &attr, &end)) + { + if (!is_xmlns_attr( &attr )) WARN( "unknown attr %s\n", debugstr_xml_attr(&attr) ); + } + + if (end) return; while (next_xml_elem( xmlbuf, &elem, parent )) { diff --git a/dlls/ntdll/process.c b/dlls/ntdll/process.c index 0b4245fdd42..dbfcd14060b 100644 --- a/dlls/ntdll/process.c +++ b/dlls/ntdll/process.c @@ -487,6 +487,13 @@ NTSTATUS WINAPI DbgUiConvertStateChangeStructure( DBGUI_WAIT_STATE_CHANGE *state event->u.DebugString.fUnicode = FALSE; event->u.DebugString.nDebugStringLength = info->ExceptionRecord.ExceptionInformation[0]; } + else if (code == DBG_PRINTEXCEPTION_WIDE_C && info->ExceptionRecord.NumberParameters >= 2) + { + event->dwDebugEventCode = OUTPUT_DEBUG_STRING_EVENT; + event->u.DebugString.lpDebugStringData = (void *)info->ExceptionRecord.ExceptionInformation[1]; + event->u.DebugString.fUnicode = TRUE; + event->u.DebugString.nDebugStringLength = info->ExceptionRecord.ExceptionInformation[0]; + } else if (code == DBG_RIPEXCEPTION && info->ExceptionRecord.NumberParameters >= 2) { event->dwDebugEventCode = RIP_EVENT; diff --git a/dlls/ntdll/sync.c b/dlls/ntdll/sync.c index 4f5ee820286..650226b89f4 100644 --- a/dlls/ntdll/sync.c +++ b/dlls/ntdll/sync.c @@ -936,11 +936,14 @@ NTSTATUS WINAPI RtlWaitOnAddress( const void *addr, const void *cmp, SIZE_T size ret = NtWaitForAlertByThreadId( NULL, timeout ); - spin_lock( &queue->lock ); - /* We may have already been removed by a call to RtlWakeAddressSingle(). */ + /* We may have already been removed by a call to RtlWakeAddressSingle() or RtlWakeAddressAll(). */ if (entry.addr) - list_remove( &entry.entry ); - spin_unlock( &queue->lock ); + { + spin_lock( &queue->lock ); + if (entry.addr) + list_remove( &entry.entry ); + spin_unlock( &queue->lock ); + } TRACE("returning %#lx\n", ret); @@ -954,8 +957,8 @@ NTSTATUS WINAPI RtlWaitOnAddress( const void *addr, const void *cmp, SIZE_T size void WINAPI RtlWakeAddressAll( const void *addr ) { struct futex_queue *queue = get_futex_queue( addr ); + struct futex_entry *entry, *next; unsigned int count = 0, i; - struct futex_entry *entry; DWORD tids[256]; TRACE("%p\n", addr); @@ -967,10 +970,12 @@ void WINAPI RtlWakeAddressAll( const void *addr ) if (!queue->queue.next) list_init(&queue->queue); - LIST_FOR_EACH_ENTRY( entry, &queue->queue, struct futex_entry, entry ) + LIST_FOR_EACH_ENTRY_SAFE( entry, next, &queue->queue, struct futex_entry, entry ) { if (entry->addr == addr) { + entry->addr = NULL; + list_remove( &entry->entry ); /* Try to buffer wakes, so that we don't make a system call while * holding a spinlock. */ if (count < ARRAY_SIZE(tids)) diff --git a/dlls/ntdll/tests/directory.c b/dlls/ntdll/tests/directory.c index a5ea7900f8d..9ff5c7a2bc2 100644 --- a/dlls/ntdll/tests/directory.c +++ b/dlls/ntdll/tests/directory.c @@ -70,6 +70,7 @@ static struct testfile_s { { 0, FILE_ATTRIBUTE_NORMAL, {0xe9,'a','.','t','m','p'}, "normal" }, { 0, FILE_ATTRIBUTE_NORMAL, {0xc9,'b','.','t','m','p'}, "normal" }, { 0, FILE_ATTRIBUTE_NORMAL, {'e','a','.','t','m','p'}, "normal" }, + { 0, FILE_ATTRIBUTE_NORMAL, {'e','a'}, "normal" }, { 0, FILE_ATTRIBUTE_DIRECTORY, {'.'}, ". directory" }, { 0, FILE_ATTRIBUTE_DIRECTORY, {'.','.'}, ".. directory" } }; @@ -237,13 +238,13 @@ static void test_flags_NtQueryDirectoryFile(OBJECT_ATTRIBUTES *attr, const char } ok(numfiles < max_test_dir_size, "too many loops\n"); - if (mask) + if (mask && !wcspbrk( mask->Buffer, L"*?" )) for (i = 0; i < test_dir_count; i++) ok(testfiles[i].nfound == (testfiles[i].name == mask->Buffer), "Wrong number %d of %s files found (single_entry=%d,mask=%s)\n", testfiles[i].nfound, testfiles[i].description, single_entry, wine_dbgstr_wn(mask->Buffer, mask->Length/sizeof(WCHAR) )); - else + else if (!mask) for (i = 0; i < test_dir_count; i++) ok(testfiles[i].nfound == 1, "Wrong number %d of %s files found (single_entry=%d,restart=%d)\n", testfiles[i].nfound, testfiles[i].description, single_entry, restart_flag); @@ -448,11 +449,27 @@ static void test_NtQueryDirectoryFile_classes( HANDLE handle, UNICODE_STRING *ma static void test_NtQueryDirectoryFile(void) { + static const struct + { + const WCHAR *mask; + int found[ARRAY_SIZE(testfiles)]; + BOOL todo_missing; + } + mask_tests[] = + { + {L"*.", {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, TRUE}, + {L"*.*", {1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1}, TRUE}, + {L"*.**", {1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1}, TRUE}, + {L"*", {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}, + {L"**", {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}, + {L"??.???", {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0}}, + }; + OBJECT_ATTRIBUTES attr; UNICODE_STRING ntdirname, mask; char testdirA[MAX_PATH], buffer[MAX_PATH]; WCHAR testdirW[MAX_PATH]; - int i; + int i, j; IO_STATUS_BLOCK io; WCHAR short_name[12]; UINT data_size; @@ -494,6 +511,19 @@ static void test_NtQueryDirectoryFile(void) test_flags_NtQueryDirectoryFile(&attr, testdirA, &mask, TRUE, FALSE); } + for (i = 0; i < ARRAY_SIZE(mask_tests); ++i) + { + winetest_push_context("mask %s", debugstr_w(mask_tests[i].mask)); + RtlInitUnicodeString(&mask, mask_tests[i].mask); + test_flags_NtQueryDirectoryFile(&attr, testdirA, &mask, FALSE, TRUE); + for (j = 0; j < test_dir_count; j++) + { + todo_wine_if(mask_tests[i].todo_missing && !mask_tests[i].found[j]) + ok(testfiles[j].nfound == mask_tests[i].found[j], "%S, got %d.\n", testfiles[j].name, testfiles[j].nfound); + } + winetest_pop_context(); + } + /* short path passed as mask */ status = pNtOpenFile(&dirh, SYNCHRONIZE | FILE_LIST_DIRECTORY, &attr, &io, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT | FILE_DIRECTORY_FILE); diff --git a/dlls/ntdll/tests/exception.c b/dlls/ntdll/tests/exception.c index 98624d3b7ac..af215c1c12b 100644 --- a/dlls/ntdll/tests/exception.c +++ b/dlls/ntdll/tests/exception.c @@ -76,6 +76,7 @@ static BOOL (WINAPI *pInitializeContext2)(void *buffer, DWORD context_flags static void * (WINAPI *pLocateXStateFeature)(CONTEXT *context, DWORD feature_id, DWORD *length); static BOOL (WINAPI *pSetXStateFeaturesMask)(CONTEXT *context, DWORD64 feature_mask); static BOOL (WINAPI *pGetXStateFeaturesMask)(CONTEXT *context, DWORD64 *feature_mask); +static BOOL (WINAPI *pWaitForDebugEventEx)(DEBUG_EVENT *, DWORD); #define RTL_UNLOAD_EVENT_TRACE_NUMBER 64 @@ -189,14 +190,36 @@ static VOID (WINAPI *pRtlUnwindEx)(VOID*, VOID*, EXCEPTION_RECORD*, VOID*, static int (CDECL *p_setjmp)(_JUMP_BUFFER*); #endif +enum debugger_stages +{ + STAGE_RTLRAISE_NOT_HANDLED = 1, + STAGE_RTLRAISE_HANDLE_LAST_CHANCE, + STAGE_OUTPUTDEBUGSTRINGA_CONTINUE, + STAGE_OUTPUTDEBUGSTRINGA_NOT_HANDLED, + STAGE_OUTPUTDEBUGSTRINGW_CONTINUE, + STAGE_OUTPUTDEBUGSTRINGW_NOT_HANDLED, + STAGE_RIPEVENT_CONTINUE, + STAGE_RIPEVENT_NOT_HANDLED, + STAGE_SERVICE_CONTINUE, + STAGE_SERVICE_NOT_HANDLED, + STAGE_BREAKPOINT_CONTINUE, + STAGE_BREAKPOINT_NOT_HANDLED, + STAGE_EXCEPTION_INVHANDLE_CONTINUE, + STAGE_EXCEPTION_INVHANDLE_NOT_HANDLED, + STAGE_NO_EXCEPTION_INVHANDLE_NOT_HANDLED, + STAGE_XSTATE, + STAGE_XSTATE_LEGACY_SSE, + STAGE_SEGMENTS, +}; + static int my_argc; static char** my_argv; static BOOL is_wow64; static BOOL have_vectored_api; -static int test_stage; +static enum debugger_stages test_stage; #if defined(__i386__) || defined(__x86_64__) -static void test_debugger_xstate(HANDLE thread, CONTEXT *ctx, int stage) +static void test_debugger_xstate(HANDLE thread, CONTEXT *ctx, enum debugger_stages stage) { char context_buffer[sizeof(CONTEXT) + sizeof(CONTEXT_EX) + sizeof(XSTATE) + 63]; CONTEXT_EX *c_ex; @@ -211,7 +234,7 @@ static void test_debugger_xstate(HANDLE thread, CONTEXT *ctx, int stage) if (!pRtlGetEnabledExtendedFeatures || !pRtlGetEnabledExtendedFeatures(1 << XSTATE_AVX)) return; - if (stage == 14) + if (stage == STAGE_XSTATE) return; length = sizeof(context_buffer); @@ -524,7 +547,7 @@ static DWORD rtlraiseexception_handler( EXCEPTION_RECORD *rec, EXCEPTION_REGISTR /* give the debugger a chance to examine the state a second time */ /* without the exception handler changing Eip */ - if (test_stage == 2) + if (test_stage == STAGE_RTLRAISE_HANDLE_LAST_CHANCE) return ExceptionContinueSearch; /* Eip in context is decreased by 1 @@ -1056,7 +1079,7 @@ static void test_exceptions(void) ok( res == STATUS_SUCCESS, "NtSetContextThread failed with %lx\n", res ); } -static void test_debugger(DWORD cont_status) +static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) { char cmdline[MAX_PATH]; PROCESS_INFORMATION pi; @@ -1076,6 +1099,12 @@ static void test_debugger(DWORD cont_status) return; } + if (with_WaitForDebugEventEx && !pWaitForDebugEventEx) + { + skip("WaitForDebugEventEx not found, skipping unicode strings in OutputDebugStringW\n"); + return; + } + sprintf(cmdline, "%s %s %s %p", my_argv[0], my_argv[1], "debuggee", &test_stage); ret = CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &si, &pi); ok(ret, "could not create child process error: %lu\n", GetLastError()); @@ -1085,7 +1114,8 @@ static void test_debugger(DWORD cont_status) do { continuestatus = cont_status; - ok(WaitForDebugEvent(&de, INFINITE), "reading debug event\n"); + ret = with_WaitForDebugEventEx ? pWaitForDebugEventEx(&de, INFINITE) : WaitForDebugEvent(&de, INFINITE); + ok(ret, "reading debug event\n"); ret = ContinueDebugEvent(de.dwProcessId, de.dwThreadId, 0xdeadbeef); ok(!ret, "ContinueDebugEvent unexpectedly succeeded\n"); @@ -1109,7 +1139,7 @@ static void test_debugger(DWORD cont_status) else if (de.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) { CONTEXT ctx; - int stage; + enum debugger_stages stage; counter++; status = pNtReadVirtualMemory(pi.hProcess, &code_mem, &code_mem_address, @@ -1145,7 +1175,7 @@ static void test_debugger(DWORD cont_status) } else { - if (stage == 1) + if (stage == STAGE_RTLRAISE_NOT_HANDLED) { ok((char *)ctx.Eip == (char *)code_mem_address + 0xb, "Eip at %lx instead of %p\n", ctx.Eip, (char *)code_mem_address + 0xb); @@ -1156,7 +1186,7 @@ static void test_debugger(DWORD cont_status) /* let the debuggee handle the exception */ continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 2) + else if (stage == STAGE_RTLRAISE_HANDLE_LAST_CHANCE) { if (de.u.Exception.dwFirstChance) { @@ -1192,25 +1222,25 @@ static void test_debugger(DWORD cont_status) /* here we handle exception */ } } - else if (stage == 7 || stage == 8) + else if (stage == STAGE_SERVICE_CONTINUE || stage == STAGE_SERVICE_NOT_HANDLED) { ok(de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT, "expected EXCEPTION_BREAKPOINT, got %08lx\n", de.u.Exception.ExceptionRecord.ExceptionCode); ok((char *)ctx.Eip == (char *)code_mem_address + 0x1d, "expected Eip = %p, got 0x%lx\n", (char *)code_mem_address + 0x1d, ctx.Eip); - if (stage == 8) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_SERVICE_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 9 || stage == 10) + else if (stage == STAGE_BREAKPOINT_CONTINUE || stage == STAGE_BREAKPOINT_NOT_HANDLED) { ok(de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT, "expected EXCEPTION_BREAKPOINT, got %08lx\n", de.u.Exception.ExceptionRecord.ExceptionCode); ok((char *)ctx.Eip == (char *)code_mem_address + 2, "expected Eip = %p, got 0x%lx\n", (char *)code_mem_address + 2, ctx.Eip); - if (stage == 10) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_BREAKPOINT_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 11 || stage == 12) + else if (stage == STAGE_EXCEPTION_INVHANDLE_CONTINUE || stage == STAGE_EXCEPTION_INVHANDLE_NOT_HANDLED) { ok(de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_INVALID_HANDLE, "unexpected exception code %08lx, expected %08lx\n", de.u.Exception.ExceptionRecord.ExceptionCode, @@ -1218,18 +1248,18 @@ static void test_debugger(DWORD cont_status) ok(de.u.Exception.ExceptionRecord.NumberParameters == 0, "unexpected number of parameters %ld, expected 0\n", de.u.Exception.ExceptionRecord.NumberParameters); - if (stage == 12) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_EXCEPTION_INVHANDLE_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 13) + else if (stage == STAGE_NO_EXCEPTION_INVHANDLE_NOT_HANDLED) { ok(FALSE || broken(TRUE) /* < Win10 */, "should not throw exception\n"); continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 14 || stage == 15) + else if (stage == STAGE_XSTATE || stage == STAGE_XSTATE_LEGACY_SSE) { test_debugger_xstate(pi.hThread, &ctx, stage); } - else if (stage == 16) + else if (stage == STAGE_SEGMENTS) { USHORT ss; __asm__( "movw %%ss,%0" : "=r" (ss) ); @@ -1257,38 +1287,54 @@ static void test_debugger(DWORD cont_status) } else if (de.dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT) { - int stage; - char buffer[64]; + enum debugger_stages stage; + char buffer[64 * sizeof(WCHAR)]; + unsigned char_size = de.u.DebugString.fUnicode ? sizeof(WCHAR) : sizeof(char); status = pNtReadVirtualMemory(pi.hProcess, &test_stage, &stage, sizeof(stage), &size_read); ok(!status,"NtReadVirtualMemory failed with 0x%lx\n", status); - ok(!de.u.DebugString.fUnicode, "unexpected unicode debug string event\n"); - ok(de.u.DebugString.nDebugStringLength < sizeof(buffer) - 1, "buffer not large enough to hold %d bytes\n", - de.u.DebugString.nDebugStringLength); + if (de.u.DebugString.fUnicode) + ok(with_WaitForDebugEventEx && + (stage == STAGE_OUTPUTDEBUGSTRINGW_CONTINUE || stage == STAGE_OUTPUTDEBUGSTRINGW_NOT_HANDLED), + "unexpected unicode debug string event\n"); + else + ok(!with_WaitForDebugEventEx || stage != STAGE_OUTPUTDEBUGSTRINGW_CONTINUE || cont_status != DBG_CONTINUE, + "unexpected ansi debug string event %u %s %lx\n", + stage, with_WaitForDebugEventEx ? "with" : "without", cont_status); + + ok(de.u.DebugString.nDebugStringLength < sizeof(buffer) / char_size - 1, + "buffer not large enough to hold %d bytes\n", de.u.DebugString.nDebugStringLength); memset(buffer, 0, sizeof(buffer)); status = pNtReadVirtualMemory(pi.hProcess, de.u.DebugString.lpDebugStringData, buffer, - de.u.DebugString.nDebugStringLength, &size_read); + de.u.DebugString.nDebugStringLength * char_size, &size_read); ok(!status,"NtReadVirtualMemory failed with 0x%lx\n", status); - if (stage == 3 || stage == 4) - ok(!strcmp(buffer, "Hello World"), "got unexpected debug string '%s'\n", buffer); + if (stage == STAGE_OUTPUTDEBUGSTRINGA_CONTINUE || stage == STAGE_OUTPUTDEBUGSTRINGA_NOT_HANDLED || + stage == STAGE_OUTPUTDEBUGSTRINGW_CONTINUE || stage == STAGE_OUTPUTDEBUGSTRINGW_NOT_HANDLED) + { + if (de.u.DebugString.fUnicode) + ok(!wcscmp((WCHAR*)buffer, L"Hello World"), "got unexpected debug string '%ls'\n", (WCHAR*)buffer); + else + ok(!strcmp(buffer, "Hello World"), "got unexpected debug string '%s'\n", buffer); + } else /* ignore unrelated debug strings like 'SHIMVIEW: ShimInfo(Complete)' */ ok(strstr(buffer, "SHIMVIEW") != NULL, "unexpected stage %x, got debug string event '%s'\n", stage, buffer); - if (stage == 4) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_OUTPUTDEBUGSTRINGA_NOT_HANDLED || stage == STAGE_OUTPUTDEBUGSTRINGW_NOT_HANDLED) + continuestatus = DBG_EXCEPTION_NOT_HANDLED; } else if (de.dwDebugEventCode == RIP_EVENT) { - int stage; + enum debugger_stages stage; status = pNtReadVirtualMemory(pi.hProcess, &test_stage, &stage, sizeof(stage), &size_read); ok(!status,"NtReadVirtualMemory failed with 0x%lx\n", status); - if (stage == 5 || stage == 6) + if (stage == STAGE_RIPEVENT_CONTINUE || stage == STAGE_RIPEVENT_NOT_HANDLED) { ok(de.u.RipInfo.dwError == 0x11223344, "got unexpected rip error code %08lx, expected %08x\n", de.u.RipInfo.dwError, 0x11223344); @@ -1298,7 +1344,7 @@ static void test_debugger(DWORD cont_status) else ok(FALSE, "unexpected stage %x\n", stage); - if (stage == 6) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_RIPEVENT_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; } ContinueDebugEvent(de.dwProcessId, de.dwThreadId, continuestatus); @@ -3652,7 +3698,7 @@ static void rtlraiseexception_handler_( EXCEPTION_RECORD *rec, EXCEPTION_REGISTR /* give the debugger a chance to examine the state a second time */ /* without the exception handler changing pc */ - if (test_stage == 2) + if (test_stage == STAGE_RTLRAISE_HANDLE_LAST_CHANCE) return; /* pc in context is decreased by 1 @@ -3666,7 +3712,7 @@ static LONG CALLBACK rtlraiseexception_unhandled_handler(EXCEPTION_POINTERS *Exc PCONTEXT context = ExceptionInfo->ContextRecord; PEXCEPTION_RECORD rec = ExceptionInfo->ExceptionRecord; rtlraiseexception_handler_(rec, NULL, context, NULL, TRUE); - if (test_stage == 2) return EXCEPTION_CONTINUE_SEARCH; + if (test_stage == STAGE_RTLRAISE_HANDLE_LAST_CHANCE) return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_EXECUTION; } @@ -3674,7 +3720,7 @@ static DWORD rtlraiseexception_handler( EXCEPTION_RECORD *rec, EXCEPTION_REGISTR CONTEXT *context, EXCEPTION_REGISTRATION_RECORD **dispatcher ) { rtlraiseexception_handler_(rec, frame, context, dispatcher, FALSE); - if (test_stage == 2) return ExceptionContinueSearch; + if (test_stage == STAGE_RTLRAISE_HANDLE_LAST_CHANCE) return ExceptionContinueSearch; return ExceptionContinueExecution; } @@ -3740,7 +3786,7 @@ static void run_rtlraiseexception_test(DWORD exceptioncode) todo_wine ok( !rtlraiseexception_handler_called, "Frame handler called\n" ); - todo_wine_if (test_stage != 2) + todo_wine_if (test_stage != STAGE_RTLRAISE_HANDLE_LAST_CHANCE) ok( rtlraiseexception_unhandled_handler_called, "UnhandledExceptionFilter wasn't called\n" ); if (have_vectored_api) @@ -3764,7 +3810,7 @@ static void test_rtlraiseexception(void) run_rtlraiseexception_test(EXCEPTION_INVALID_HANDLE); } -static void test_debugger(DWORD cont_status) +static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) { char cmdline[MAX_PATH]; PROCESS_INFORMATION pi; @@ -3784,6 +3830,12 @@ static void test_debugger(DWORD cont_status) return; } + if (with_WaitForDebugEventEx && !pWaitForDebugEventEx) + { + skip("WaitForDebugEventEx not found, skipping unicode strings in OutputDebugStringW\n"); + return; + } + sprintf(cmdline, "%s %s %s %p", my_argv[0], my_argv[1], "debuggee", &test_stage); ret = CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &si, &pi); ok(ret, "could not create child process error: %lu\n", GetLastError()); @@ -3793,7 +3845,8 @@ static void test_debugger(DWORD cont_status) do { continuestatus = cont_status; - ok(WaitForDebugEvent(&de, INFINITE), "reading debug event\n"); + ret = with_WaitForDebugEventEx ? pWaitForDebugEventEx(&de, INFINITE) : WaitForDebugEvent(&de, INFINITE); + ok(ret, "reading debug event\n"); ret = ContinueDebugEvent(de.dwProcessId, de.dwThreadId, 0xdeadbeef); ok(!ret, "ContinueDebugEvent unexpectedly succeeded\n"); @@ -3817,7 +3870,7 @@ static void test_debugger(DWORD cont_status) else if (de.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) { CONTEXT ctx; - int stage; + enum debugger_stages stage; counter++; status = pNtReadVirtualMemory(pi.hProcess, &code_mem, &code_mem_address, @@ -3854,7 +3907,7 @@ static void test_debugger(DWORD cont_status) } else { - if (stage == 1) + if (stage == STAGE_RTLRAISE_NOT_HANDLED) { ok((char *)ctx.Rip == (char *)code_mem_address + 0x10, "Rip at %p instead of %p\n", (char *)ctx.Rip, (char *)code_mem_address + 0x10); @@ -3865,7 +3918,7 @@ static void test_debugger(DWORD cont_status) /* let the debuggee handle the exception */ continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 2) + else if (stage == STAGE_RTLRAISE_HANDLE_LAST_CHANCE) { if (de.u.Exception.dwFirstChance) { @@ -3893,23 +3946,23 @@ static void test_debugger(DWORD cont_status) /* here we handle exception */ } } - else if (stage == 7 || stage == 8) + else if (stage == STAGE_SERVICE_CONTINUE || stage == STAGE_SERVICE_NOT_HANDLED) { ok(de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT, "expected EXCEPTION_BREAKPOINT, got %08lx\n", de.u.Exception.ExceptionRecord.ExceptionCode); ok((char *)ctx.Rip == (char *)code_mem_address + 0x30, "expected Rip = %p, got %p\n", (char *)code_mem_address + 0x30, (char *)ctx.Rip); - if (stage == 8) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_SERVICE_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 9 || stage == 10) + else if (stage == STAGE_BREAKPOINT_CONTINUE || stage == STAGE_BREAKPOINT_NOT_HANDLED) { ok(de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT, "expected EXCEPTION_BREAKPOINT, got %08lx\n", de.u.Exception.ExceptionRecord.ExceptionCode); ok((char *)ctx.Rip == (char *)code_mem_address + 2, "expected Rip = %p, got %p\n", (char *)code_mem_address + 2, (char *)ctx.Rip); - if (stage == 10) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_BREAKPOINT_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 11 || stage == 12) + else if (stage == STAGE_EXCEPTION_INVHANDLE_CONTINUE || stage == STAGE_EXCEPTION_INVHANDLE_NOT_HANDLED) { ok(de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_INVALID_HANDLE, "unexpected exception code %08lx, expected %08lx\n", de.u.Exception.ExceptionRecord.ExceptionCode, @@ -3917,18 +3970,18 @@ static void test_debugger(DWORD cont_status) ok(de.u.Exception.ExceptionRecord.NumberParameters == 0, "unexpected number of parameters %ld, expected 0\n", de.u.Exception.ExceptionRecord.NumberParameters); - if (stage == 12) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_EXCEPTION_INVHANDLE_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 13) + else if (stage == STAGE_NO_EXCEPTION_INVHANDLE_NOT_HANDLED) { ok(FALSE || broken(TRUE) /* < Win10 */, "should not throw exception\n"); continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 14 || stage == 15) + else if (stage == STAGE_XSTATE || stage == STAGE_XSTATE_LEGACY_SSE) { test_debugger_xstate(pi.hThread, &ctx, stage); } - else if (stage == 16) + else if (stage == STAGE_SEGMENTS) { USHORT ss; __asm__( "movw %%ss,%0" : "=r" (ss) ); @@ -3960,38 +4013,54 @@ static void test_debugger(DWORD cont_status) } else if (de.dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT) { - int stage; - char buffer[64]; + enum debugger_stages stage; + char buffer[64 * sizeof(WCHAR)]; + unsigned char_size = de.u.DebugString.fUnicode ? sizeof(WCHAR) : sizeof(char); status = pNtReadVirtualMemory(pi.hProcess, &test_stage, &stage, sizeof(stage), &size_read); ok(!status,"NtReadVirtualMemory failed with 0x%lx\n", status); - ok(!de.u.DebugString.fUnicode, "unexpected unicode debug string event\n"); - ok(de.u.DebugString.nDebugStringLength < sizeof(buffer) - 1, "buffer not large enough to hold %d bytes\n", - de.u.DebugString.nDebugStringLength); + if (de.u.DebugString.fUnicode) + ok(with_WaitForDebugEventEx && + (stage == STAGE_OUTPUTDEBUGSTRINGW_CONTINUE || stage == STAGE_OUTPUTDEBUGSTRINGW_NOT_HANDLED), + "unexpected unicode debug string event\n"); + else + ok(!with_WaitForDebugEventEx || stage != STAGE_OUTPUTDEBUGSTRINGW_CONTINUE || cont_status != DBG_CONTINUE, + "unexpected ansi debug string event %u %s %lx\n", + stage, with_WaitForDebugEventEx ? "with" : "without", cont_status); + + ok(de.u.DebugString.nDebugStringLength < sizeof(buffer) / char_size - 1, + "buffer not large enough to hold %d bytes\n", de.u.DebugString.nDebugStringLength); memset(buffer, 0, sizeof(buffer)); status = pNtReadVirtualMemory(pi.hProcess, de.u.DebugString.lpDebugStringData, buffer, - de.u.DebugString.nDebugStringLength, &size_read); + de.u.DebugString.nDebugStringLength * char_size, &size_read); ok(!status,"NtReadVirtualMemory failed with 0x%lx\n", status); - if (stage == 3 || stage == 4) - ok(!strcmp(buffer, "Hello World"), "got unexpected debug string '%s'\n", buffer); + if (stage == STAGE_OUTPUTDEBUGSTRINGA_CONTINUE || stage == STAGE_OUTPUTDEBUGSTRINGA_NOT_HANDLED || + stage == STAGE_OUTPUTDEBUGSTRINGW_CONTINUE || stage == STAGE_OUTPUTDEBUGSTRINGW_NOT_HANDLED) + { + if (de.u.DebugString.fUnicode) + ok(!wcscmp((WCHAR*)buffer, L"Hello World"), "got unexpected debug string '%ls'\n", (WCHAR*)buffer); + else + ok(!strcmp(buffer, "Hello World"), "got unexpected debug string '%s'\n", buffer); + } else /* ignore unrelated debug strings like 'SHIMVIEW: ShimInfo(Complete)' */ ok(strstr(buffer, "SHIMVIEW") != NULL, "unexpected stage %x, got debug string event '%s'\n", stage, buffer); - if (stage == 4) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_OUTPUTDEBUGSTRINGA_NOT_HANDLED || stage == STAGE_OUTPUTDEBUGSTRINGW_NOT_HANDLED) + continuestatus = DBG_EXCEPTION_NOT_HANDLED; } else if (de.dwDebugEventCode == RIP_EVENT) { - int stage; + enum debugger_stages stage; status = pNtReadVirtualMemory(pi.hProcess, &test_stage, &stage, sizeof(stage), &size_read); ok(!status,"NtReadVirtualMemory failed with 0x%lx\n", status); - if (stage == 5 || stage == 6) + if (stage == STAGE_RIPEVENT_CONTINUE || stage == STAGE_RIPEVENT_NOT_HANDLED) { ok(de.u.RipInfo.dwError == 0x11223344, "got unexpected rip error code %08lx, expected %08x\n", de.u.RipInfo.dwError, 0x11223344); @@ -4001,7 +4070,7 @@ static void test_debugger(DWORD cont_status) else ok(FALSE, "unexpected stage %x\n", stage); - if (stage == 6) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_RIPEVENT_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; } ContinueDebugEvent(de.dwProcessId, de.dwThreadId, continuestatus); @@ -6582,7 +6651,7 @@ static void test_thread_context(void) #undef COMPARE } -static void test_debugger(DWORD cont_status) +static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) { char cmdline[MAX_PATH]; PROCESS_INFORMATION pi; @@ -6602,6 +6671,12 @@ static void test_debugger(DWORD cont_status) return; } + if (with_WaitForDebugEventEx && !pWaitForDebugEventEx) + { + skip("WaitForDebugEventEx not found, skipping unicode strings in OutputDebugStringW\n"); + return; + } + sprintf(cmdline, "%s %s %s %p", my_argv[0], my_argv[1], "debuggee", &test_stage); ret = CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &si, &pi); ok(ret, "could not create child process error: %lu\n", GetLastError()); @@ -6611,7 +6686,8 @@ static void test_debugger(DWORD cont_status) do { continuestatus = cont_status; - ok(WaitForDebugEvent(&de, INFINITE), "reading debug event\n"); + ret = with_WaitForDebugEventEx ? pWaitForDebugEventEx(&de, INFINITE) : WaitForDebugEvent(&de, INFINITE); + ok(ret, "reading debug event\n"); ret = ContinueDebugEvent(de.dwProcessId, de.dwThreadId, 0xdeadbeef); ok(!ret, "ContinueDebugEvent unexpectedly succeeded\n"); @@ -6635,7 +6711,7 @@ static void test_debugger(DWORD cont_status) else if (de.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) { CONTEXT ctx; - int stage; + enum debugger_stages stage; counter++; status = pNtReadVirtualMemory(pi.hProcess, &code_mem, &code_mem_address, @@ -6672,7 +6748,7 @@ static void test_debugger(DWORD cont_status) } else { - if (stage == 1) + if (stage == STAGE_RTLRAISE_NOT_HANDLED) { ok((char *)ctx.Pc == (char *)code_mem_address + 0xb, "Pc at %lx instead of %p\n", ctx.Pc, (char *)code_mem_address + 0xb); @@ -6683,7 +6759,7 @@ static void test_debugger(DWORD cont_status) /* let the debuggee handle the exception */ continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 2) + else if (stage == STAGE_RTLRAISE_HANDLE_LAST_CHANCE) { if (de.u.Exception.dwFirstChance) { @@ -6712,23 +6788,23 @@ static void test_debugger(DWORD cont_status) /* here we handle exception */ } } - else if (stage == 7 || stage == 8) + else if (stage == STAGE_SERVICE_CONTINUE || stage == STAGE_SERVICE_NOT_HANDLED) { ok(de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT, "expected EXCEPTION_BREAKPOINT, got %08lx\n", de.u.Exception.ExceptionRecord.ExceptionCode); ok((char *)ctx.Pc == (char *)code_mem_address + 0x1d, "expected Pc = %p, got 0x%lx\n", (char *)code_mem_address + 0x1d, ctx.Pc); - if (stage == 8) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_SERVICE_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 9 || stage == 10) + else if (stage == STAGE_BREAKPOINT_CONTINUE || stage == STAGE_BREAKPOINT_NOT_HANDLED) { ok(de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT, "expected EXCEPTION_BREAKPOINT, got %08lx\n", de.u.Exception.ExceptionRecord.ExceptionCode); ok((char *)ctx.Pc == (char *)code_mem_address + 3, "expected Pc = %p, got 0x%lx\n", (char *)code_mem_address + 3, ctx.Pc); - if (stage == 10) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_BREAKPOINT_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 11 || stage == 12) + else if (stage == STAGE_EXCEPTION_INVHANDLE_CONTINUE || stage == STAGE_EXCEPTION_INVHANDLE_NOT_HANDLED) { ok(de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_INVALID_HANDLE, "unexpected exception code %08lx, expected %08lx\n", de.u.Exception.ExceptionRecord.ExceptionCode, @@ -6736,9 +6812,9 @@ static void test_debugger(DWORD cont_status) ok(de.u.Exception.ExceptionRecord.NumberParameters == 0, "unexpected number of parameters %ld, expected 0\n", de.u.Exception.ExceptionRecord.NumberParameters); - if (stage == 12) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_EXCEPTION_INVHANDLE_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 13) + else if (stage == STAGE_NO_EXCEPTION_INVHANDLE_NOT_HANDLED) { ok(FALSE || broken(TRUE) /* < Win10 */, "should not throw exception\n"); continuestatus = DBG_EXCEPTION_NOT_HANDLED; @@ -6752,38 +6828,54 @@ static void test_debugger(DWORD cont_status) } else if (de.dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT) { - int stage; - char buffer[64]; + enum debugger_stages stage; + char buffer[64 * sizeof(WCHAR)]; + unsigned char_size = de.u.DebugString.fUnicode ? sizeof(WCHAR) : sizeof(char); status = pNtReadVirtualMemory(pi.hProcess, &test_stage, &stage, sizeof(stage), &size_read); ok(!status,"NtReadVirtualMemory failed with 0x%lx\n", status); - ok(!de.u.DebugString.fUnicode, "unexpected unicode debug string event\n"); - ok(de.u.DebugString.nDebugStringLength < sizeof(buffer) - 1, "buffer not large enough to hold %d bytes\n", - de.u.DebugString.nDebugStringLength); + if (de.u.DebugString.fUnicode) + ok(with_WaitForDebugEventEx && + (stage == STAGE_OUTPUTDEBUGSTRINGW_CONTINUE || stage == STAGE_OUTPUTDEBUGSTRINGW_NOT_HANDLED), + "unexpected unicode debug string event\n"); + else + ok(!with_WaitForDebugEventEx || stage != STAGE_OUTPUTDEBUGSTRINGW_CONTINUE || cont_status != DBG_CONTINUE, + "unexpected ansi debug string event %u %s %lx\n", + stage, with_WaitForDebugEventEx ? "with" : "without", cont_status); + + ok(de.u.DebugString.nDebugStringLength < sizeof(buffer) / char_size - 1, + "buffer not large enough to hold %d bytes\n", de.u.DebugString.nDebugStringLength); memset(buffer, 0, sizeof(buffer)); status = pNtReadVirtualMemory(pi.hProcess, de.u.DebugString.lpDebugStringData, buffer, - de.u.DebugString.nDebugStringLength, &size_read); + de.u.DebugString.nDebugStringLength * char_size, &size_read); ok(!status,"NtReadVirtualMemory failed with 0x%lx\n", status); - if (stage == 3 || stage == 4) - ok(!strcmp(buffer, "Hello World"), "got unexpected debug string '%s'\n", buffer); + if (stage == STAGE_OUTPUTDEBUGSTRINGA_CONTINUE || stage == STAGE_OUTPUTDEBUGSTRINGA_NOT_HANDLED || + stage == STAGE_OUTPUTDEBUGSTRINGW_CONTINUE || stage == STAGE_OUTPUTDEBUGSTRINGW_NOT_HANDLED) + { + if (de.u.DebugString.fUnicode) + ok(!wcscmp((WCHAR*)buffer, L"Hello World"), "got unexpected debug string '%ls'\n", (WCHAR*)buffer); + else + ok(!strcmp(buffer, "Hello World"), "got unexpected debug string '%s'\n", buffer); + } else /* ignore unrelated debug strings like 'SHIMVIEW: ShimInfo(Complete)' */ ok(strstr(buffer, "SHIMVIEW") != NULL, "unexpected stage %x, got debug string event '%s'\n", stage, buffer); - if (stage == 4) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_OUTPUTDEBUGSTRINGA_NOT_HANDLED || stage == STAGE_OUTPUTDEBUGSTRINGW_NOT_HANDLED) + continuestatus = DBG_EXCEPTION_NOT_HANDLED; } else if (de.dwDebugEventCode == RIP_EVENT) { - int stage; + enum debugger_stages stage; status = pNtReadVirtualMemory(pi.hProcess, &test_stage, &stage, sizeof(stage), &size_read); ok(!status,"NtReadVirtualMemory failed with 0x%lx\n", status); - if (stage == 5 || stage == 6) + if (stage == STAGE_RIPEVENT_CONTINUE || stage == STAGE_RIPEVENT_NOT_HANDLED) { ok(de.u.RipInfo.dwError == 0x11223344, "got unexpected rip error code %08lx, expected %08x\n", de.u.RipInfo.dwError, 0x11223344); @@ -6793,7 +6885,7 @@ static void test_debugger(DWORD cont_status) else ok(FALSE, "unexpected stage %x\n", stage); - if (stage == 6) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_RIPEVENT_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; } ContinueDebugEvent(de.dwProcessId, de.dwThreadId, continuestatus); @@ -7837,7 +7929,7 @@ static void test_thread_context(void) #undef COMPARE } -static void test_debugger(DWORD cont_status) +static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) { char cmdline[MAX_PATH]; PROCESS_INFORMATION pi; @@ -7857,6 +7949,12 @@ static void test_debugger(DWORD cont_status) return; } + if (with_WaitForDebugEventEx && !pWaitForDebugEventEx) + { + skip("WaitForDebugEventEx not found, skipping unicode strings in OutputDebugStringW\n"); + return; + } + sprintf(cmdline, "%s %s %s %p", my_argv[0], my_argv[1], "debuggee", &test_stage); ret = CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &si, &pi); ok(ret, "could not create child process error: %lu\n", GetLastError()); @@ -7866,7 +7964,8 @@ static void test_debugger(DWORD cont_status) do { continuestatus = cont_status; - ok(WaitForDebugEvent(&de, INFINITE), "reading debug event\n"); + ret = with_WaitForDebugEventEx ? pWaitForDebugEventEx(&de, INFINITE) : WaitForDebugEvent(&de, INFINITE); + ok(ret, "reading debug event\n"); ret = ContinueDebugEvent(de.dwProcessId, de.dwThreadId, 0xdeadbeef); ok(!ret, "ContinueDebugEvent unexpectedly succeeded\n"); @@ -7890,7 +7989,7 @@ static void test_debugger(DWORD cont_status) else if (de.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) { CONTEXT ctx; - int stage; + enum debugger_stages stage; counter++; status = pNtReadVirtualMemory(pi.hProcess, &code_mem, &code_mem_address, @@ -7927,7 +8026,7 @@ static void test_debugger(DWORD cont_status) } else { - if (stage == 1) + if (stage == STAGE_RTLRAISE_NOT_HANDLED) { ok((char *)ctx.Pc == (char *)code_mem_address + 0xb, "Pc at %p instead of %p\n", (char *)ctx.Pc, (char *)code_mem_address + 0xb); @@ -7938,7 +8037,7 @@ static void test_debugger(DWORD cont_status) /* let the debuggee handle the exception */ continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 2) + else if (stage == STAGE_RTLRAISE_HANDLE_LAST_CHANCE) { if (de.u.Exception.dwFirstChance) { @@ -7967,23 +8066,23 @@ static void test_debugger(DWORD cont_status) /* here we handle exception */ } } - else if (stage == 7 || stage == 8) + else if (stage == STAGE_SERVICE_CONTINUE || stage == STAGE_SERVICE_NOT_HANDLED) { ok(de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT, "expected EXCEPTION_BREAKPOINT, got %08lx\n", de.u.Exception.ExceptionRecord.ExceptionCode); ok((char *)ctx.Pc == (char *)code_mem_address + 0x1d, "expected Pc = %p, got %p\n", (char *)code_mem_address + 0x1d, (char *)ctx.Pc); - if (stage == 8) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_SERVICE_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 9 || stage == 10) + else if (stage == STAGE_BREAKPOINT_CONTINUE || stage == STAGE_BREAKPOINT_NOT_HANDLED) { ok(de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT, "expected EXCEPTION_BREAKPOINT, got %08lx\n", de.u.Exception.ExceptionRecord.ExceptionCode); ok((char *)ctx.Pc == (char *)code_mem_address + 4, "expected Pc = %p, got %p\n", (char *)code_mem_address + 4, (char *)ctx.Pc); - if (stage == 10) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_BREAKPOINT_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 11 || stage == 12) + else if (stage == STAGE_EXCEPTION_INVHANDLE_CONTINUE || stage == STAGE_EXCEPTION_INVHANDLE_NOT_HANDLED) { ok(de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_INVALID_HANDLE, "unexpected exception code %08lx, expected %08lx\n", de.u.Exception.ExceptionRecord.ExceptionCode, @@ -7991,9 +8090,9 @@ static void test_debugger(DWORD cont_status) ok(de.u.Exception.ExceptionRecord.NumberParameters == 0, "unexpected number of parameters %ld, expected 0\n", de.u.Exception.ExceptionRecord.NumberParameters); - if (stage == 12) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_EXCEPTION_INVHANDLE_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; } - else if (stage == 13) + else if (stage == STAGE_NO_EXCEPTION_INVHANDLE_NOT_HANDLED) { ok(FALSE || broken(TRUE) /* < Win10 */, "should not throw exception\n"); continuestatus = DBG_EXCEPTION_NOT_HANDLED; @@ -8007,39 +8106,55 @@ static void test_debugger(DWORD cont_status) } else if (de.dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT) { - int stage; - char buffer[128]; + enum debugger_stages stage; + char buffer[128 * sizeof(WCHAR)]; + unsigned char_size = de.u.DebugString.fUnicode ? sizeof(WCHAR) : sizeof(char); status = pNtReadVirtualMemory(pi.hProcess, &test_stage, &stage, sizeof(stage), &size_read); ok(!status,"NtReadVirtualMemory failed with 0x%lx\n", status); - ok(!de.u.DebugString.fUnicode, "unexpected unicode debug string event\n"); - ok(de.u.DebugString.nDebugStringLength < sizeof(buffer) - 1, "buffer not large enough to hold %d bytes\n", - de.u.DebugString.nDebugStringLength); + if (de.u.DebugString.fUnicode) + ok(with_WaitForDebugEventEx && + (stage == STAGE_OUTPUTDEBUGSTRINGW_CONTINUE || stage == STAGE_OUTPUTDEBUGSTRINGW_NOT_HANDLED), + "unexpected unicode debug string event\n"); + else + ok(!with_WaitForDebugEventEx || stage != STAGE_OUTPUTDEBUGSTRINGW_CONTINUE || cont_status != DBG_CONTINUE, + "unexpected ansi debug string event %u %s %lx\n", + stage, with_WaitForDebugEventEx ? "with" : "without", cont_status); + + ok(de.u.DebugString.nDebugStringLength < sizeof(buffer) / char_size - 1, + "buffer not large enough to hold %d bytes\n", de.u.DebugString.nDebugStringLength); memset(buffer, 0, sizeof(buffer)); status = pNtReadVirtualMemory(pi.hProcess, de.u.DebugString.lpDebugStringData, buffer, - de.u.DebugString.nDebugStringLength, &size_read); + de.u.DebugString.nDebugStringLength * char_size, &size_read); ok(!status,"NtReadVirtualMemory failed with 0x%lx\n", status); - if (stage == 3 || stage == 4) - ok(!strcmp(buffer, "Hello World"), "got unexpected debug string '%s'\n", buffer); + if (stage == STAGE_OUTPUTDEBUGSTRINGA_CONTINUE || stage == STAGE_OUTPUTDEBUGSTRINGA_NOT_HANDLED || + stage == STAGE_OUTPUTDEBUGSTRINGW_CONTINUE || stage == STAGE_OUTPUTDEBUGSTRINGW_NOT_HANDLED) + { + if (de.u.DebugString.fUnicode) + ok(!wcscmp((WCHAR*)buffer, L"Hello World"), "got unexpected debug string '%ls'\n", (WCHAR*)buffer); + else + ok(!strcmp(buffer, "Hello World"), "got unexpected debug string '%s'\n", buffer); + } else /* ignore unrelated debug strings like 'SHIMVIEW: ShimInfo(Complete)' */ ok(strstr(buffer, "SHIMVIEW") || !strncmp(buffer, "RTL:", 4), - "unexpected stage %x, got debug string event '%s'\n", stage, buffer); + "unexpected stage %x, got debug string event '%s'\n", stage, buffer); - if (stage == 4) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_OUTPUTDEBUGSTRINGA_NOT_HANDLED || stage == STAGE_OUTPUTDEBUGSTRINGW_NOT_HANDLED) + continuestatus = DBG_EXCEPTION_NOT_HANDLED; } else if (de.dwDebugEventCode == RIP_EVENT) { - int stage; + enum debugger_stages stage; status = pNtReadVirtualMemory(pi.hProcess, &test_stage, &stage, sizeof(stage), &size_read); ok(!status,"NtReadVirtualMemory failed with 0x%lx\n", status); - if (stage == 5 || stage == 6) + if (stage == STAGE_RIPEVENT_CONTINUE || stage == STAGE_RIPEVENT_NOT_HANDLED) { ok(de.u.RipInfo.dwError == 0x11223344, "got unexpected rip error code %08lx, expected %08x\n", de.u.RipInfo.dwError, 0x11223344); @@ -8049,7 +8164,7 @@ static void test_debugger(DWORD cont_status) else ok(FALSE, "unexpected stage %x\n", stage); - if (stage == 6) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + if (stage == STAGE_RIPEVENT_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; } ContinueDebugEvent(de.dwProcessId, de.dwThreadId, continuestatus); @@ -8445,25 +8560,44 @@ static void test_debug_service(DWORD numexc) } #endif /* defined(__i386__) || defined(__x86_64__) */ -static DWORD outputdebugstring_exceptions; +static DWORD outputdebugstring_exceptions_ansi; +static DWORD outputdebugstring_exceptions_unicode; static LONG CALLBACK outputdebugstring_vectored_handler(EXCEPTION_POINTERS *ExceptionInfo) { PEXCEPTION_RECORD rec = ExceptionInfo->ExceptionRecord; trace("vect. handler %08lx addr:%p\n", rec->ExceptionCode, rec->ExceptionAddress); - ok(rec->ExceptionCode == DBG_PRINTEXCEPTION_C, "ExceptionCode is %08lx instead of %08lx\n", - rec->ExceptionCode, DBG_PRINTEXCEPTION_C); - ok(rec->NumberParameters == 2, "ExceptionParameters is %ld instead of 2\n", rec->NumberParameters); - ok(rec->ExceptionInformation[0] == 12, "ExceptionInformation[0] = %ld instead of 12\n", (DWORD)rec->ExceptionInformation[0]); - ok(!strcmp((char *)rec->ExceptionInformation[1], "Hello World"), - "ExceptionInformation[1] = '%s' instead of 'Hello World'\n", (char *)rec->ExceptionInformation[1]); + switch (rec->ExceptionCode) + { + case DBG_PRINTEXCEPTION_C: + ok(rec->NumberParameters == 2, "ExceptionParameters is %ld instead of 2\n", rec->NumberParameters); + ok(rec->ExceptionInformation[0] == 12, "ExceptionInformation[0] = %ld instead of 12\n", (DWORD)rec->ExceptionInformation[0]); + ok(!strcmp((char *)rec->ExceptionInformation[1], "Hello World"), + "ExceptionInformation[1] = '%s' instead of 'Hello World'\n", (char *)rec->ExceptionInformation[1]); + outputdebugstring_exceptions_ansi++; + break; + case DBG_PRINTEXCEPTION_WIDE_C: + ok(outputdebugstring_exceptions_ansi == 0, "Unicode exception should come first\n"); + ok(rec->NumberParameters == 4, "ExceptionParameters is %ld instead of 4\n", rec->NumberParameters); + ok(rec->ExceptionInformation[0] == 12, "ExceptionInformation[0] = %ld instead of 12\n", (DWORD)rec->ExceptionInformation[0]); + ok(!wcscmp((WCHAR *)rec->ExceptionInformation[1], L"Hello World"), + "ExceptionInformation[1] = '%s' instead of 'Hello World'\n", (char *)rec->ExceptionInformation[1]); + ok(rec->ExceptionInformation[2] == 12, "ExceptionInformation[2] = %ld instead of 12\n", (DWORD)rec->ExceptionInformation[2]); + ok(!strcmp((char *)rec->ExceptionInformation[3], "Hello World"), + "ExceptionInformation[3] = '%s' instead of 'Hello World'\n", (char *)rec->ExceptionInformation[3]); + outputdebugstring_exceptions_unicode++; + break; + default: + ok(0, "ExceptionCode is %08lx unexpected\n", rec->ExceptionCode); + break; + } - outputdebugstring_exceptions++; return EXCEPTION_CONTINUE_SEARCH; } -static void test_outputdebugstring(DWORD numexc, BOOL todo) +static void test_outputdebugstring(BOOL unicode, DWORD numexc_ansi, BOOL todo_ansi, + DWORD numexc_unicode_low, DWORD numexc_unicode_high) { PVOID vectored_handler; @@ -8476,12 +8610,106 @@ static void test_outputdebugstring(DWORD numexc, BOOL todo) vectored_handler = pRtlAddVectoredExceptionHandler(TRUE, &outputdebugstring_vectored_handler); ok(vectored_handler != 0, "RtlAddVectoredExceptionHandler failed\n"); - outputdebugstring_exceptions = 0; - OutputDebugStringA("Hello World"); + outputdebugstring_exceptions_ansi = outputdebugstring_exceptions_unicode = 0; + + if (unicode) + OutputDebugStringW(L"Hello World"); + else + OutputDebugStringA("Hello World"); + + todo_wine_if(todo_ansi) + ok(outputdebugstring_exceptions_ansi == numexc_ansi, + "OutputDebugString%c generated %ld ansi exceptions, expected %ld\n", + unicode ? 'W' : 'A', outputdebugstring_exceptions_ansi, numexc_ansi); + ok(outputdebugstring_exceptions_unicode >= numexc_unicode_low && + outputdebugstring_exceptions_unicode <= numexc_unicode_high, + "OutputDebugString%c generated %lu unicode exceptions, expected %ld-%ld\n", + unicode ? 'W' : 'A', outputdebugstring_exceptions_unicode, numexc_unicode_low, numexc_unicode_high); + + pRtlRemoveVectoredExceptionHandler(vectored_handler); +} + +static DWORD outputdebugstring_exceptions_newmodel_order; +static DWORD outputdebugstring_newmodel_return; + +static LONG CALLBACK outputdebugstring_new_model_vectored_handler(EXCEPTION_POINTERS *ExceptionInfo) +{ + PEXCEPTION_RECORD rec = ExceptionInfo->ExceptionRecord; + trace("vect. handler %08lx addr:%p\n", rec->ExceptionCode, rec->ExceptionAddress); + + switch (rec->ExceptionCode) + { + case DBG_PRINTEXCEPTION_C: + ok(rec->NumberParameters == 2, "ExceptionParameters is %ld instead of 2\n", rec->NumberParameters); + ok(rec->ExceptionInformation[0] == 12, "ExceptionInformation[0] = %ld instead of 12\n", (DWORD)rec->ExceptionInformation[0]); + ok(!strcmp((char *)rec->ExceptionInformation[1], "Hello World"), + "ExceptionInformation[1] = '%s' instead of 'Hello World'\n", (char *)rec->ExceptionInformation[1]); + outputdebugstring_exceptions_newmodel_order = + (outputdebugstring_exceptions_newmodel_order << 8) | 'A'; + break; + case DBG_PRINTEXCEPTION_WIDE_C: + ok(rec->NumberParameters == 4, "ExceptionParameters is %ld instead of 4\n", rec->NumberParameters); + ok(rec->ExceptionInformation[0] == 12, "ExceptionInformation[0] = %ld instead of 12\n", (DWORD)rec->ExceptionInformation[0]); + ok(!wcscmp((WCHAR *)rec->ExceptionInformation[1], L"Hello World"), + "ExceptionInformation[1] = '%s' instead of 'Hello World'\n", (char *)rec->ExceptionInformation[1]); + ok(rec->ExceptionInformation[2] == 12, "ExceptionInformation[2] = %ld instead of 12\n", (DWORD)rec->ExceptionInformation[2]); + ok(!strcmp((char *)rec->ExceptionInformation[3], "Hello World"), + "ExceptionInformation[3] = '%s' instead of 'Hello World'\n", (char *)rec->ExceptionInformation[3]); + outputdebugstring_exceptions_newmodel_order = + (outputdebugstring_exceptions_newmodel_order << 8) | 'W'; + break; + default: + ok(0, "ExceptionCode is %08lx unexpected\n", rec->ExceptionCode); + break; + } + + return outputdebugstring_newmodel_return; +} + +static void test_outputdebugstring_newmodel(void) +{ + PVOID vectored_handler; + struct + { + /* input */ + BOOL unicode; + DWORD ret_code; + /* expected output */ + DWORD exceptions_order; + } + tests[] = + { + {FALSE, EXCEPTION_CONTINUE_EXECUTION, 'A'}, + {FALSE, EXCEPTION_CONTINUE_SEARCH, 'A'}, + {TRUE, EXCEPTION_CONTINUE_EXECUTION, 'W'}, + {TRUE, EXCEPTION_CONTINUE_SEARCH, ('W' << 8) | 'A'}, + }; + int i; + + if (!pRtlAddVectoredExceptionHandler || !pRtlRemoveVectoredExceptionHandler) + { + skip("RtlAddVectoredExceptionHandler or RtlRemoveVectoredExceptionHandler not found\n"); + return; + } + + vectored_handler = pRtlAddVectoredExceptionHandler(TRUE, &outputdebugstring_new_model_vectored_handler); + ok(vectored_handler != 0, "RtlAddVectoredExceptionHandler failed\n"); + + for (i = 0; i < ARRAY_SIZE(tests); i++) + { + outputdebugstring_exceptions_newmodel_order = 0; + outputdebugstring_newmodel_return = tests[i].ret_code; + + if (tests[i].unicode) + OutputDebugStringW(L"Hello World"); + else + OutputDebugStringA("Hello World"); - todo_wine_if(todo) - ok(outputdebugstring_exceptions == numexc, "OutputDebugStringA generated %ld exceptions, expected %ld\n", - outputdebugstring_exceptions, numexc); + ok(outputdebugstring_exceptions_newmodel_order == tests[i].exceptions_order, + "OutputDebugString%c/%u generated exceptions %04lxs, expected %04lx\n", + tests[i].unicode ? 'W' : 'A', i, + outputdebugstring_exceptions_newmodel_order, tests[i].exceptions_order); + } pRtlRemoveVectoredExceptionHandler(vectored_handler); } @@ -8736,12 +8964,12 @@ static void test_debuggee_xstate(void) func(); for (i = 0; i < 4; ++i) - ok(data[i] == (test_stage == 14 ? i + 1 : 0x28282828), + ok(data[i] == (test_stage == STAGE_XSTATE ? i + 1 : 0x28282828), "Got unexpected data %#x, test_stage %u, i %u.\n", data[i], test_stage, i); for ( ; i < ARRAY_SIZE(data); ++i) - ok(data[i] == (test_stage == 14 ? i + 1 : 0x48484848) - || broken(test_stage == 15 && data[i] == i + 1) /* Win7 */, + ok(data[i] == (test_stage == STAGE_XSTATE ? i + 1 : 0x48484848) + || broken(test_stage == STAGE_XSTATE_LEGACY_SSE && data[i] == i + 1) /* Win7 */, "Got unexpected data %#x, test_stage %u, i %u.\n", data[i], test_stage, i); } @@ -10907,6 +11135,7 @@ START_TEST(exception) X(LocateXStateFeature); X(SetXStateFeaturesMask); X(GetXStateFeaturesMask); + X(WaitForDebugEventEx); #undef X if (pRtlAddVectoredExceptionHandler && pRtlRemoveVectoredExceptionHandler) @@ -10943,40 +11172,48 @@ START_TEST(exception) #if defined(__i386__) || defined(__x86_64__) if (pRtlRaiseException) { - test_stage = 1; + test_stage = STAGE_RTLRAISE_NOT_HANDLED; run_rtlraiseexception_test(0x12345); run_rtlraiseexception_test(EXCEPTION_BREAKPOINT); run_rtlraiseexception_test(EXCEPTION_INVALID_HANDLE); - test_stage = 2; + test_stage = STAGE_RTLRAISE_HANDLE_LAST_CHANCE; run_rtlraiseexception_test(0x12345); run_rtlraiseexception_test(EXCEPTION_BREAKPOINT); run_rtlraiseexception_test(EXCEPTION_INVALID_HANDLE); } else skip( "RtlRaiseException not found\n" ); #endif - test_stage = 3; - test_outputdebugstring(0, FALSE); - test_stage = 4; - test_outputdebugstring(2, TRUE); /* is this a Windows bug? */ - test_stage = 5; + + test_stage = STAGE_OUTPUTDEBUGSTRINGA_CONTINUE; + + test_outputdebugstring(FALSE, 0, FALSE, 0, 0); + test_stage = STAGE_OUTPUTDEBUGSTRINGA_NOT_HANDLED; + test_outputdebugstring(FALSE, 2, TRUE, 0, 0); /* is 2 a Windows bug? */ + test_stage = STAGE_OUTPUTDEBUGSTRINGW_CONTINUE; + /* depending on value passed DebugContinue we can get the unicode exception or not */ + test_outputdebugstring(TRUE, 0, FALSE, 0, 1); + test_stage = STAGE_OUTPUTDEBUGSTRINGW_NOT_HANDLED; + /* depending on value passed DebugContinue we can get the unicode exception or not */ + test_outputdebugstring(TRUE, 2, TRUE, 0, 1); /* is 2 a Windows bug? */ + test_stage = STAGE_RIPEVENT_CONTINUE; test_ripevent(0); - test_stage = 6; + test_stage = STAGE_RIPEVENT_NOT_HANDLED; test_ripevent(1); - test_stage = 7; + test_stage = STAGE_SERVICE_CONTINUE; test_debug_service(0); - test_stage = 8; + test_stage = STAGE_SERVICE_NOT_HANDLED; test_debug_service(1); - test_stage = 9; + test_stage = STAGE_BREAKPOINT_CONTINUE; test_breakpoint(0); - test_stage = 10; + test_stage = STAGE_BREAKPOINT_NOT_HANDLED; test_breakpoint(1); - test_stage = 11; + test_stage = STAGE_EXCEPTION_INVHANDLE_CONTINUE; test_closehandle(0, (HANDLE)0xdeadbeef); test_closehandle(0, (HANDLE)0x7fffffff); - test_stage = 12; + test_stage = STAGE_EXCEPTION_INVHANDLE_NOT_HANDLED; test_closehandle(1, (HANDLE)0xdeadbeef); test_closehandle(1, (HANDLE)~(ULONG_PTR)6); - test_stage = 13; /* special cases */ + test_stage = STAGE_NO_EXCEPTION_INVHANDLE_NOT_HANDLED; /* special cases */ test_closehandle(0, 0); test_closehandle(0, INVALID_HANDLE_VALUE); test_closehandle(0, GetCurrentProcess()); @@ -10986,11 +11223,11 @@ START_TEST(exception) test_closehandle(0, GetCurrentThreadToken()); test_closehandle(0, GetCurrentThreadEffectiveToken()); #if defined(__i386__) || defined(__x86_64__) - test_stage = 14; + test_stage = STAGE_XSTATE; test_debuggee_xstate(); - test_stage = 15; + test_stage = STAGE_XSTATE_LEGACY_SSE; test_debuggee_xstate(); - test_stage = 16; + test_stage = STAGE_SEGMENTS; test_debuggee_segments(); #endif @@ -11068,10 +11305,20 @@ START_TEST(exception) #endif - test_debugger(DBG_EXCEPTION_HANDLED); - test_debugger(DBG_CONTINUE); + test_debugger(DBG_EXCEPTION_HANDLED, FALSE); + test_debugger(DBG_CONTINUE, FALSE); + test_debugger(DBG_EXCEPTION_HANDLED, TRUE); + test_debugger(DBG_CONTINUE, TRUE); test_thread_context(); - test_outputdebugstring(1, FALSE); + test_outputdebugstring(FALSE, 1, FALSE, 0, 0); + if (pWaitForDebugEventEx) + { + test_outputdebugstring(TRUE, 1, FALSE, 1, 1); + test_outputdebugstring_newmodel(); + } + else + skip("Unsupported new unicode debug string model\n"); + test_ripevent(1); test_fastfail(); test_breakpoint(1); diff --git a/dlls/ntdll/tests/path.c b/dlls/ntdll/tests/path.c index 4732f6a6aa8..64bc92a4f72 100644 --- a/dlls/ntdll/tests/path.c +++ b/dlls/ntdll/tests/path.c @@ -306,6 +306,9 @@ static void test_RtlGetFullPathName_U(void) { "c:/test../file", "c:\\test.\\file", "file", "c:\\test..\\file", "file"}, /* vista */ { "c:\\test", "c:\\test", "test"}, + { "c:\\test\\*.", "c:\\test\\*", "*"}, + { "c:\\test\\a*b.*", "c:\\test\\a*b.*", "a*b.*"}, + { "c:\\test\\a*b*.", "c:\\test\\a*b*", "a*b*"}, { "C:\\test", "C:\\test", "test"}, { "c:/", "c:\\", NULL}, { "c:.", "C:\\windows", "windows"}, @@ -440,6 +443,7 @@ static void test_RtlDosPathNameToNtPathName_U(void) tests[] = { {L"c:\\", L"\\??\\c:\\", -1}, + {L"c:\\test\\*.", L"\\??\\c:\\test\\*", 12}, {L"c:/", L"\\??\\c:\\", -1}, {L"c:/foo", L"\\??\\c:\\foo", 7}, {L"c:/foo.", L"\\??\\c:\\foo", 7}, diff --git a/dlls/ntdll/unix/loader.c b/dlls/ntdll/unix/loader.c index 9dc11f74fb2..b9b6f66c52f 100644 --- a/dlls/ntdll/unix/loader.c +++ b/dlls/ntdll/unix/loader.c @@ -2284,12 +2284,18 @@ BOOL localsystem_sid; BOOL high_dll_addresses; BOOL simulate_writecopy; SIZE_T kernel_stack_size = 0x100000; +long long ram_reporting_bias; static void hacks_init(void) { static const char upc_exe[] = "Ubisoft Game Launcher\\upc.exe"; const char *env_str, *sgi; + if ((env_str = getenv("WINE_RAM_REPORTING_BIAS"))) + { + ram_reporting_bias = atoll(env_str) * 1024 * 1024; + ERR( "HACK: ram_reporting_bias %lldMB.\n", ram_reporting_bias / (1024 * 1024) ); + } env_str = getenv("WINE_SIMULATE_ASYNC_READ"); if (env_str) @@ -2357,6 +2363,7 @@ static void hacks_init(void) || !strcmp(sgi, "1680700") /* Purgo box */ || !strcmp(sgi, "2095300") /* Breakout 13 */ || !strcmp(sgi, "2053940") /* Idol Hands 2 */ + || !strcmp(sgi, "391150") /* Red Tie Runner */ || !strcmp(sgi, "2176450"); /* Mr. Hopp's Playhouse 3 */ if (main_argc > 1 && strstr(main_argv[1], "MicrosoftEdgeUpdate.exe")) diff --git a/dlls/ntdll/unix/system.c b/dlls/ntdll/unix/system.c index 16fde91ea7c..76a47f1932a 100644 --- a/dlls/ntdll/unix/system.c +++ b/dlls/ntdll/unix/system.c @@ -2042,7 +2042,15 @@ static void get_performance_info( SYSTEM_PERFORMANCE_INFORMATION *info ) mem_available = value * 1024; } fclose(fp); + totalram -= min( totalram, ram_reporting_bias ); if (mem_available) freeram = mem_available; + if ((long long)freeram >= ram_reporting_bias) freeram -= ram_reporting_bias; + else + { + long long bias = ram_reporting_bias - freeram; + freeswap -= min( bias, freeswap ); + freeram = 0; + } } } #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__) || \ diff --git a/dlls/ntdll/unix/unix_private.h b/dlls/ntdll/unix/unix_private.h index d54d802e0cb..759483ca3ba 100644 --- a/dlls/ntdll/unix/unix_private.h +++ b/dlls/ntdll/unix/unix_private.h @@ -159,6 +159,7 @@ extern BOOL no_priv_elevation DECLSPEC_HIDDEN; extern BOOL localsystem_sid DECLSPEC_HIDDEN; extern BOOL high_dll_addresses DECLSPEC_HIDDEN; extern BOOL simulate_writecopy DECLSPEC_HIDDEN; +extern long long ram_reporting_bias; extern void init_environment( int argc, char *argv[], char *envp[] ) DECLSPEC_HIDDEN; extern void init_startup_info(void) DECLSPEC_HIDDEN; diff --git a/dlls/ntdll/unix/virtual.c b/dlls/ntdll/unix/virtual.c index ea314dbe1d5..b62968cb26d 100644 --- a/dlls/ntdll/unix/virtual.c +++ b/dlls/ntdll/unix/virtual.c @@ -80,6 +80,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(virtual); WINE_DECLARE_DEBUG_CHANNEL(module); WINE_DECLARE_DEBUG_CHANNEL(virtual_ranges); +WINE_DECLARE_DEBUG_CHANNEL(virtstat); struct preload_info { @@ -1172,6 +1173,53 @@ static void VIRTUAL_Dump(void) #endif +static void dump_memory_statistics(void) +{ + struct file_view *view; + SIZE_T anon_reserved = 0, anon_committed = 0, mapped = 0, mapped_committed = 0, marked_native = 0; + SIZE_T size, c_size; + char *base; + BYTE vprot; + + if (!TRACE_ON(virtstat)) return; + + WINE_RB_FOR_EACH_ENTRY( view, &views_tree, struct file_view, entry ) + { + if (view->protect & VPROT_NATIVE) + { + marked_native += view->size; + continue; + } + base = view->base; + c_size = 0; + while (base != (char *)view->base + view->size) + { + size = get_vprot_range_size( base, (char *)view->base + view->size - base, VPROT_COMMITTED, &vprot ); + if (vprot & VPROT_COMMITTED) c_size += size; + base += size; + } + if (is_view_valloc( view )) + { + anon_reserved += view->size; + anon_committed += c_size; + } + else + { + mapped += view->size; + mapped_committed += c_size; + } + } + + anon_reserved /= 1024 * 1024; + anon_committed /= 1024 * 1024; + mapped /= 1024 * 1024; + mapped_committed /= 1024 * 1024; + marked_native /= 1024 * 1024; + TRACE_(virtstat)( "Total: res %lu, comm %lu; Anon: res %lu, comm %lu, marked Unix %lu.\n", + anon_reserved + mapped, anon_committed + mapped_committed, anon_reserved, anon_committed, + marked_native ); +} + /*********************************************************************** * find_view * @@ -2987,6 +3035,7 @@ void virtual_get_system_info( SYSTEM_BASIC_INFORMATION *info, BOOL wow64 ) if (!sysinfo(&sinfo)) { ULONG64 total = (ULONG64)sinfo.totalram * sinfo.mem_unit; + total -= min(total, ram_reporting_bias); info->MmHighestPhysicalPage = max(1, total / page_size); } #elif defined(_SC_PHYS_PAGES) @@ -4146,7 +4195,11 @@ static NTSTATUS allocate_virtual_memory( void **ret, SIZE_T *size_ptr, ULONG typ } } - if (!status) VIRTUAL_DEBUG_DUMP_VIEW( view ); + if (!status) + { + VIRTUAL_DEBUG_DUMP_VIEW( view ); + dump_memory_statistics(); + } server_leave_uninterrupted_section( &virtual_mutex, &sigset ); @@ -4513,6 +4566,8 @@ NTSTATUS WINAPI NtFreeVirtualMemory( HANDLE process, PVOID *addr_ptr, SIZE_T *si status = STATUS_INVALID_PARAMETER; } + dump_memory_statistics(); + server_leave_uninterrupted_section( &virtual_mutex, &sigset ); return status; } diff --git a/dlls/ole32/compobj_private.h b/dlls/ole32/compobj_private.h index 34f5a8ec485..ad6b70d7a44 100644 --- a/dlls/ole32/compobj_private.h +++ b/dlls/ole32/compobj_private.h @@ -63,6 +63,7 @@ struct oletls struct list spies; /* Spies installed with CoRegisterInitializeSpy */ DWORD spies_lock; DWORD cancelcount; + struct apartment *implicit_mta; /* mta referenced by roapi from sta thread */ }; /* Global Interface Table Functions */ diff --git a/dlls/oleaut32/usrmarshal.c b/dlls/oleaut32/usrmarshal.c index aa54a2b092b..4a874a23413 100644 --- a/dlls/oleaut32/usrmarshal.c +++ b/dlls/oleaut32/usrmarshal.c @@ -506,7 +506,10 @@ unsigned char * WINAPI VARIANT_UserUnmarshal(ULONG *pFlags, unsigned char *Buffe ULONG mem_size; Pos += 4; - switch (header->vt & ~VT_BYREF) + /* byref array needs to allocate a SAFEARRAY pointer */ + if (header->vt & VT_ARRAY) + mem_size = sizeof(void *); + else switch (header->vt & ~VT_BYREF) { /* these types have a different memory size compared to wire size */ case VT_UNKNOWN: diff --git a/dlls/riched20/editor.c b/dlls/riched20/editor.c index c4b4d7e72be..e993646f5a3 100644 --- a/dlls/riched20/editor.c +++ b/dlls/riched20/editor.c @@ -4145,6 +4145,8 @@ LRESULT editor_handle_message( ME_TextEditor *editor, UINT msg, WPARAM wParam, if (dwIndex == GCS_COMPSTR) set_selection_cursors(editor,editor->imeStartIndex, editor->imeStartIndex + dwBufLen/sizeof(WCHAR)); + else + editor->imeStartIndex = ME_GetCursorOfs(&editor->pCursors[0]); } ME_ReleaseStyle(style); ME_CommitUndo(editor); diff --git a/dlls/rtworkq/queue.c b/dlls/rtworkq/queue.c index baf648bf771..c088d892f39 100644 --- a/dlls/rtworkq/queue.c +++ b/dlls/rtworkq/queue.c @@ -120,6 +120,13 @@ enum system_queue_index SYS_QUEUE_COUNT, }; +enum work_item_type +{ + WORK_ITEM_WORK, + WORK_ITEM_TIMER, + WORK_ITEM_WAIT, +}; + struct work_item { IUnknown IUnknown_iface; @@ -131,10 +138,11 @@ struct work_item RTWQWORKITEM_KEY key; LONG priority; DWORD flags; - TP_WORK *work_object; PTP_SIMPLE_CALLBACK finalization_callback; + enum work_item_type type; union { + TP_WORK *work_object; TP_WAIT *wait_object; TP_TIMER *timer_object; } u; @@ -389,8 +397,9 @@ static void pool_queue_submit(struct queue *queue, struct work_item *item) we need finalization callback. */ if (item->finalization_callback) IUnknown_AddRef(&item->IUnknown_iface); - item->work_object = CreateThreadpoolWork(standard_queue_worker, item, (TP_CALLBACK_ENVIRON *)&env); - SubmitThreadpoolWork(item->work_object); + item->u.work_object = CreateThreadpoolWork(standard_queue_worker, item, (TP_CALLBACK_ENVIRON *)&env); + item->type = WORK_ITEM_WORK; + SubmitThreadpoolWork(item->u.work_object); TRACE("dispatched %p.\n", item->result); } @@ -551,8 +560,18 @@ static ULONG WINAPI work_item_Release(IUnknown *iface) if (!refcount) { - if (item->work_object) - CloseThreadpoolWork(item->work_object); + switch (item->type) + { + case WORK_ITEM_WORK: + if (item->u.work_object) CloseThreadpoolWork(item->u.work_object); + break; + case WORK_ITEM_WAIT: + if (item->u.wait_object) CloseThreadpoolWait(item->u.wait_object); + break; + case WORK_ITEM_TIMER: + if (item->u.timer_object) CloseThreadpoolTimer(item->u.timer_object); + break; + } if (item->reply_result) IRtwqAsyncResult_Release(item->reply_result); IRtwqAsyncResult_Release(item->result); @@ -816,6 +835,7 @@ static HRESULT queue_submit_wait(struct queue *queue, HANDLE event, LONG priorit item->u.wait_object = CreateThreadpoolWait(callback, item, (TP_CALLBACK_ENVIRON *)&queue->envs[TP_CALLBACK_PRIORITY_NORMAL]); + item->type = WORK_ITEM_WAIT; SetThreadpoolWait(item->u.wait_object, event, NULL); TRACE("dispatched %p.\n", result); @@ -850,6 +870,7 @@ static HRESULT queue_submit_timer(struct queue *queue, IRtwqAsyncResult *result, item->u.timer_object = CreateThreadpoolTimer(callback, item, (TP_CALLBACK_ENVIRON *)&queue->envs[TP_CALLBACK_PRIORITY_NORMAL]); + item->type = WORK_ITEM_TIMER; SetThreadpoolTimer(item->u.timer_object, &filetime, period, 0); TRACE("dispatched %p.\n", result); diff --git a/dlls/sapi/Makefile.in b/dlls/sapi/Makefile.in index e0f9a8cdd07..4229320e473 100644 --- a/dlls/sapi/Makefile.in +++ b/dlls/sapi/Makefile.in @@ -1,9 +1,12 @@ MODULE = sapi.dll IMPORTS = uuid ole32 user32 advapi32 +DELAYIMPORTS = winmm C_SRCS = \ + async.c \ automation.c \ main.c \ + mmaudio.c \ resource.c \ stream.c \ token.c \ diff --git a/dlls/sapi/async.c b/dlls/sapi/async.c new file mode 100644 index 00000000000..61b99019abe --- /dev/null +++ b/dlls/sapi/async.c @@ -0,0 +1,180 @@ +/* + * Speech API (SAPI) async helper implementation. + * + * Copyright 2023 Shaun Ren for CodeWeavers + * + * 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 + */ + +#include + +#include "windef.h" +#include "winbase.h" +#include "objbase.h" + +#include "wine/heap.h" +#include "wine/list.h" +#include "wine/debug.h" + +#include "sapi_private.h" + +WINE_DEFAULT_DEBUG_CHANNEL(sapi); + +static struct async_task *async_dequeue_task(struct async_queue *queue) +{ + struct async_task *task = NULL; + struct list *head; + + EnterCriticalSection(&queue->cs); + if ((head = list_head(&queue->tasks))) + { + task = LIST_ENTRY(head, struct async_task, entry); + list_remove(head); + } + LeaveCriticalSection(&queue->cs); + + return task; +} + +void async_empty_queue(struct async_queue *queue) +{ + struct async_task *task, *next; + + if (!queue->init) return; + + EnterCriticalSection(&queue->cs); + LIST_FOR_EACH_ENTRY_SAFE(task, next, &queue->tasks, struct async_task, entry) + { + list_remove(&task->entry); + heap_free(task); + } + LeaveCriticalSection(&queue->cs); + + SetEvent(queue->empty); +} + +static void CALLBACK async_worker(TP_CALLBACK_INSTANCE *instance, void *ctx) +{ + struct async_queue *queue = ctx; + HANDLE handles[2] = { queue->cancel, queue->wait }; + DWORD ret; + + CoInitializeEx(NULL, COINIT_MULTITHREADED); + SetEvent(queue->ready); + + for (;;) + { + ret = WaitForMultipleObjects(2, handles, FALSE, INFINITE); + if (ret == WAIT_OBJECT_0) + goto cancel; + else if (ret == WAIT_OBJECT_0 + 1) + { + struct async_task *task; + + while ((task = async_dequeue_task(queue))) + { + ResetEvent(queue->empty); + task->proc(task); + heap_free(task); + if (WaitForSingleObject(queue->cancel, 0) == WAIT_OBJECT_0) + goto cancel; + } + + SetEvent(queue->empty); + } + else + ERR("WaitForMultipleObjects failed: %#lx.\n", ret); + } + +cancel: + async_empty_queue(queue); + CoUninitialize(); + TRACE("cancelled.\n"); + SetEvent(queue->ready); +} + +HRESULT async_start_queue(struct async_queue *queue) +{ + HRESULT hr; + + if (queue->init) + return S_OK; + + InitializeCriticalSection(&queue->cs); + list_init(&queue->tasks); + + if (!(queue->wait = CreateEventW(NULL, FALSE, FALSE, NULL)) || + !(queue->ready = CreateEventW(NULL, FALSE, FALSE, NULL)) || + !(queue->cancel = CreateEventW(NULL, FALSE, FALSE, NULL)) || + !(queue->empty = CreateEventW(NULL, TRUE, TRUE, NULL))) + goto fail; + + queue->init = TRUE; + + if (!TrySubmitThreadpoolCallback(async_worker, queue, NULL)) + goto fail; + + WaitForSingleObject(queue->ready, INFINITE); + return S_OK; + +fail: + hr = HRESULT_FROM_WIN32(GetLastError()); + DeleteCriticalSection(&queue->cs); + if (queue->wait) CloseHandle(queue->wait); + if (queue->ready) CloseHandle(queue->ready); + if (queue->cancel) CloseHandle(queue->cancel); + if (queue->empty) CloseHandle(queue->empty); + memset(queue, 0, sizeof(*queue)); + return hr; +} + +void async_cancel_queue(struct async_queue *queue) +{ + if (!queue->init) return; + + SetEvent(queue->cancel); + WaitForSingleObject(queue->ready, INFINITE); + + DeleteCriticalSection(&queue->cs); + CloseHandle(queue->wait); + CloseHandle(queue->ready); + CloseHandle(queue->cancel); + CloseHandle(queue->empty); + + memset(queue, 0, sizeof(*queue)); +} + +HRESULT async_queue_task(struct async_queue *queue, struct async_task *task) +{ + HRESULT hr; + + if (FAILED(hr = async_start_queue(queue))) + return hr; + + EnterCriticalSection(&queue->cs); + list_add_tail(&queue->tasks, &task->entry); + LeaveCriticalSection(&queue->cs); + + ResetEvent(queue->empty); + SetEvent(queue->wait); + + return S_OK; +} + +HRESULT async_wait_queue_empty(struct async_queue *queue, DWORD timeout) +{ + if (!queue->init) return WAIT_OBJECT_0; + return WaitForSingleObject(queue->empty, timeout); +} diff --git a/dlls/sapi/main.c b/dlls/sapi/main.c index d83d2257186..108db7d13d8 100644 --- a/dlls/sapi/main.c +++ b/dlls/sapi/main.c @@ -105,6 +105,7 @@ static const struct IClassFactoryVtbl class_factory_vtbl = static struct class_factory data_key_cf = { { &class_factory_vtbl }, data_key_create }; static struct class_factory file_stream_cf = { { &class_factory_vtbl }, file_stream_create }; +static struct class_factory mmaudio_out_cf = { { &class_factory_vtbl }, mmaudio_out_create }; static struct class_factory resource_mgr_cf = { { &class_factory_vtbl }, resource_manager_create }; static struct class_factory speech_stream_cf = { { &class_factory_vtbl }, speech_stream_create }; static struct class_factory speech_voice_cf = { { &class_factory_vtbl }, speech_voice_create }; @@ -125,6 +126,8 @@ HRESULT WINAPI DllGetClassObject( REFCLSID clsid, REFIID iid, void **obj ) cf = &data_key_cf.IClassFactory_iface; else if (IsEqualCLSID( clsid, &CLSID_SpFileStream )) cf = &file_stream_cf.IClassFactory_iface; + else if (IsEqualCLSID( clsid, &CLSID_SpMMAudioOut )) + cf = &mmaudio_out_cf.IClassFactory_iface; else if (IsEqualCLSID( clsid, &CLSID_SpObjectTokenCategory )) cf = &token_category_cf.IClassFactory_iface; else if (IsEqualCLSID( clsid, &CLSID_SpObjectTokenEnum )) diff --git a/dlls/sapi/mmaudio.c b/dlls/sapi/mmaudio.c new file mode 100644 index 00000000000..7452d9a7257 --- /dev/null +++ b/dlls/sapi/mmaudio.c @@ -0,0 +1,917 @@ +/* + * Speech API (SAPI) winmm audio implementation. + * + * Copyright 2023 Shaun Ren for CodeWeavers + * + * 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 + */ + +#include + +#define COBJMACROS + +#include "windef.h" +#include "winbase.h" +#include "objbase.h" + +#include "sapiddk.h" +#include "sperror.h" + +#include "initguid.h" + +#include "wine/debug.h" + +#include "sapi_private.h" + +WINE_DEFAULT_DEBUG_CHANNEL(sapi); + +DEFINE_GUID(SPDFID_Text, 0x7ceef9f9, 0x3d13, 0x11d2, 0x9e, 0xe7, 0x00, 0xc0, 0x4f, 0x79, 0x73, 0x96); +DEFINE_GUID(SPDFID_WaveFormatEx, 0xc31adbae, 0x527f, 0x4ff5, 0xa2, 0x30, 0xf6, 0x2b, 0xb6, 0x1f, 0xf7, 0x0c); + +enum flow_type { FLOW_IN, FLOW_OUT }; + +struct mmaudio +{ + ISpEventSource ISpEventSource_iface; + ISpEventSink ISpEventSink_iface; + ISpObjectWithToken ISpObjectWithToken_iface; + ISpMMSysAudio ISpMMSysAudio_iface; + LONG ref; + + enum flow_type flow; + ISpObjectToken *token; + UINT device_id; + SPAUDIOSTATE state; + WAVEFORMATEX *wfx; + union + { + HWAVEIN in; + HWAVEOUT out; + } hwave; + HANDLE event; + struct async_queue queue; + CRITICAL_SECTION cs; + + size_t pending_buf_count; + CRITICAL_SECTION pending_cs; +}; + +static inline struct mmaudio *impl_from_ISpEventSource(ISpEventSource *iface) +{ + return CONTAINING_RECORD(iface, struct mmaudio, ISpEventSource_iface); +} + +static inline struct mmaudio *impl_from_ISpEventSink(ISpEventSink *iface) +{ + return CONTAINING_RECORD(iface, struct mmaudio, ISpEventSink_iface); +} + +static inline struct mmaudio *impl_from_ISpObjectWithToken(ISpObjectWithToken *iface) +{ + return CONTAINING_RECORD(iface, struct mmaudio, ISpObjectWithToken_iface); +} + +static inline struct mmaudio *impl_from_ISpMMSysAudio(ISpMMSysAudio *iface) +{ + return CONTAINING_RECORD(iface, struct mmaudio, ISpMMSysAudio_iface); +} + +static HRESULT WINAPI event_source_QueryInterface(ISpEventSource *iface, REFIID iid, void **obj) +{ + struct mmaudio *This = impl_from_ISpEventSource(iface); + + TRACE("(%p, %s, %p).\n", iface, debugstr_guid(iid), obj); + + return ISpMMSysAudio_QueryInterface(&This->ISpMMSysAudio_iface, iid, obj); +} + +static ULONG WINAPI event_source_AddRef(ISpEventSource *iface) +{ + struct mmaudio *This = impl_from_ISpEventSource(iface); + + TRACE("(%p).\n", iface); + + return ISpMMSysAudio_AddRef(&This->ISpMMSysAudio_iface); +} + +static ULONG WINAPI event_source_Release(ISpEventSource *iface) +{ + struct mmaudio *This = impl_from_ISpEventSource(iface); + + TRACE("(%p).\n", iface); + + return ISpMMSysAudio_Release(&This->ISpMMSysAudio_iface); +} + +static HRESULT WINAPI event_source_SetNotifySink(ISpEventSource *iface, ISpNotifySink *sink) +{ + FIXME("(%p, %p): stub.\n", iface, sink); + + return E_NOTIMPL; +} + +static HRESULT WINAPI event_source_SetNotifyWindowMessage(ISpEventSource *iface, HWND hwnd, + UINT msg, WPARAM wparam, LPARAM lparam) +{ + FIXME("(%p, %p, %u, %Ix, %Ix): stub.\n", iface, hwnd, msg, wparam, lparam); + + return E_NOTIMPL; +} + +static HRESULT WINAPI event_source_SetNotifyCallbackFunction(ISpEventSource *iface, SPNOTIFYCALLBACK *callback, + WPARAM wparam, LPARAM lparam) +{ + FIXME("(%p, %p, %Ix, %Ix): stub.\n", iface, callback, wparam, lparam); + + return E_NOTIMPL; +} + +static HRESULT WINAPI event_source_SetNotifyCallbackInterface(ISpEventSource *iface, ISpNotifyCallback *callback, + WPARAM wparam, LPARAM lparam) +{ + FIXME("(%p, %p, %Ix, %Ix): stub.\n", iface, callback, wparam, lparam); + + return E_NOTIMPL; +} + +static HRESULT WINAPI event_source_SetNotifyWin32Event(ISpEventSource *iface) +{ + FIXME("(%p): stub.\n", iface); + + return E_NOTIMPL; +} + +static HRESULT WINAPI event_source_WaitForNotifyEvent(ISpEventSource *iface, DWORD milliseconds) +{ + FIXME("(%p, %ld): stub.\n", iface, milliseconds); + + return E_NOTIMPL; +} + +static HANDLE WINAPI event_source_GetNotifyEventHandle(ISpEventSource *iface) +{ + FIXME("(%p): stub.\n", iface); + + return NULL; +} + +static HRESULT WINAPI event_source_SetInterest(ISpEventSource *iface, ULONGLONG event, ULONGLONG queued) +{ + FIXME("(%p, %s, %s): stub.\n", iface, wine_dbgstr_longlong(event), wine_dbgstr_longlong(queued)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI event_source_GetEvents(ISpEventSource *iface, ULONG count, SPEVENT *array, ULONG *fetched) +{ + FIXME("(%p, %lu, %p, %p): stub.\n", iface, count, array, fetched); + + return E_NOTIMPL; +} + +static HRESULT WINAPI event_source_GetInfo(ISpEventSource *iface, SPEVENTSOURCEINFO *info) +{ + FIXME("(%p, %p): stub.\n", iface, info); + + return E_NOTIMPL; +} + +static const ISpEventSourceVtbl event_source_vtbl = +{ + event_source_QueryInterface, + event_source_AddRef, + event_source_Release, + event_source_SetNotifySink, + event_source_SetNotifyWindowMessage, + event_source_SetNotifyCallbackFunction, + event_source_SetNotifyCallbackInterface, + event_source_SetNotifyWin32Event, + event_source_WaitForNotifyEvent, + event_source_GetNotifyEventHandle, + event_source_SetInterest, + event_source_GetEvents, + event_source_GetInfo +}; + +static HRESULT WINAPI event_sink_QueryInterface(ISpEventSink *iface, REFIID iid, void **obj) +{ + struct mmaudio *This = impl_from_ISpEventSink(iface); + + TRACE("(%p, %s, %p).\n", iface, debugstr_guid(iid), obj); + + return ISpMMSysAudio_QueryInterface(&This->ISpMMSysAudio_iface, iid, obj); +} + +static ULONG WINAPI event_sink_AddRef(ISpEventSink *iface) +{ + struct mmaudio *This = impl_from_ISpEventSink(iface); + + TRACE("(%p).\n", iface); + + return ISpMMSysAudio_AddRef(&This->ISpMMSysAudio_iface); +} + +static ULONG WINAPI event_sink_Release(ISpEventSink *iface) +{ + struct mmaudio *This = impl_from_ISpEventSink(iface); + + TRACE("(%p).\n", iface); + + return ISpMMSysAudio_Release(&This->ISpMMSysAudio_iface); +} + +static HRESULT WINAPI event_sink_AddEvents(ISpEventSink *iface, const SPEVENT *events, ULONG count) +{ + FIXME("(%p, %p, %lu).\n", iface, events, count); + + return E_NOTIMPL; +} + +static HRESULT WINAPI event_sink_GetEventInterest(ISpEventSink *iface, ULONGLONG *interest) +{ + FIXME("(%p, %p).\n", iface, interest); + + return E_NOTIMPL; +} + +static const ISpEventSinkVtbl event_sink_vtbl = +{ + event_sink_QueryInterface, + event_sink_AddRef, + event_sink_Release, + event_sink_AddEvents, + event_sink_GetEventInterest +}; + +static HRESULT WINAPI objwithtoken_QueryInterface(ISpObjectWithToken *iface, REFIID iid, void **obj) +{ + struct mmaudio *This = impl_from_ISpObjectWithToken(iface); + + TRACE("(%p, %s, %p).\n", iface, debugstr_guid(iid), obj); + + return ISpMMSysAudio_QueryInterface(&This->ISpMMSysAudio_iface, iid, obj); +} + +static ULONG WINAPI objwithtoken_AddRef(ISpObjectWithToken *iface) +{ + struct mmaudio *This = impl_from_ISpObjectWithToken(iface); + + TRACE("(%p).\n", iface); + + return ISpMMSysAudio_AddRef(&This->ISpMMSysAudio_iface); +} + +static ULONG WINAPI objwithtoken_Release(ISpObjectWithToken *iface) +{ + struct mmaudio *This = impl_from_ISpObjectWithToken(iface); + + TRACE("(%p).\n", iface); + + return ISpMMSysAudio_Release(&This->ISpMMSysAudio_iface); +} + +static HRESULT WINAPI objwithtoken_SetObjectToken(ISpObjectWithToken *iface, ISpObjectToken *token) +{ + struct mmaudio *This = impl_from_ISpObjectWithToken(iface); + + FIXME("(%p, %p): semi-stub.\n", iface, token); + + if (!token) + return E_INVALIDARG; + if (This->token) + return SPERR_ALREADY_INITIALIZED; + + ISpObjectToken_AddRef(token); + This->token = token; + return S_OK; +} + +static HRESULT WINAPI objwithtoken_GetObjectToken(ISpObjectWithToken *iface, ISpObjectToken **token) +{ + struct mmaudio *This = impl_from_ISpObjectWithToken(iface); + + TRACE("(%p, %p).\n", iface, token); + + if (!token) + return E_POINTER; + + *token = This->token; + if (*token) + { + ISpObjectToken_AddRef(*token); + return S_OK; + } + else + return S_FALSE; +} + +static const ISpObjectWithTokenVtbl objwithtoken_vtbl = +{ + objwithtoken_QueryInterface, + objwithtoken_AddRef, + objwithtoken_Release, + objwithtoken_SetObjectToken, + objwithtoken_GetObjectToken +}; + +static HRESULT WINAPI mmsysaudio_QueryInterface(ISpMMSysAudio *iface, REFIID iid, void **obj) +{ + struct mmaudio *This = impl_from_ISpMMSysAudio(iface); + + TRACE("(%p, %s, %p).\n", iface, debugstr_guid(iid), obj); + + if (IsEqualIID(iid, &IID_IUnknown) || + IsEqualIID(iid, &IID_ISequentialStream) || + IsEqualIID(iid, &IID_IStream) || + IsEqualIID(iid, &IID_ISpStreamFormat) || + IsEqualIID(iid, &IID_ISpAudio) || + IsEqualIID(iid, &IID_ISpMMSysAudio)) + *obj = &This->ISpMMSysAudio_iface; + else if (IsEqualIID(iid, &IID_ISpEventSource)) + *obj = &This->ISpEventSource_iface; + else if (IsEqualIID(iid, &IID_ISpEventSink)) + *obj = &This->ISpEventSink_iface; + else if (IsEqualIID(iid, &IID_ISpObjectWithToken)) + *obj = &This->ISpObjectWithToken_iface; + else + { + *obj = NULL; + FIXME("interface %s not implemented.\n", debugstr_guid(iid)); + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown *)*obj); + return S_OK; +} + +static ULONG WINAPI mmsysaudio_AddRef(ISpMMSysAudio *iface) +{ + struct mmaudio *This = impl_from_ISpMMSysAudio(iface); + ULONG ref = InterlockedIncrement(&This->ref); + + TRACE("(%p): ref=%lu\n", iface, ref); + + return ref; +} + +static ULONG WINAPI mmsysaudio_Release(ISpMMSysAudio *iface) +{ + struct mmaudio *This = impl_from_ISpMMSysAudio(iface); + ULONG ref = InterlockedDecrement(&This->ref); + + TRACE("(%p): ref=%lu\n", iface, ref); + + if (!ref) + { + ISpMMSysAudio_SetState(iface, SPAS_CLOSED, 0); + + async_wait_queue_empty(&This->queue, INFINITE); + async_cancel_queue(&This->queue); + + if (This->token) ISpObjectToken_Release(This->token); + heap_free(This->wfx); + CloseHandle(This->event); + DeleteCriticalSection(&This->pending_cs); + DeleteCriticalSection(&This->cs); + + heap_free(This); + } + + return ref; +} + +static HRESULT WINAPI mmsysaudio_Read(ISpMMSysAudio *iface, void *pv, ULONG cb, ULONG *cb_read) +{ + FIXME("(%p, %p, %lu, %p): stub.\n", iface, pv, cb, cb_read); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_Write(ISpMMSysAudio *iface, const void *pv, ULONG cb, ULONG *cb_written) +{ + struct mmaudio *This = impl_from_ISpMMSysAudio(iface); + HRESULT hr = S_OK; + WAVEHDR *buf; + + TRACE("(%p, %p, %lu, %p).\n", iface, pv, cb, cb_written); + + if (This->flow != FLOW_OUT) + return STG_E_ACCESSDENIED; + + if (cb_written) + *cb_written = 0; + + EnterCriticalSection(&This->cs); + + if (This->state == SPAS_CLOSED || This->state == SPAS_STOP) + { + LeaveCriticalSection(&This->cs); + return SP_AUDIO_STOPPED; + } + + if (!(buf = heap_alloc(sizeof(WAVEHDR) + cb))) + { + LeaveCriticalSection(&This->cs); + return E_OUTOFMEMORY; + } + memcpy((char *)(buf + 1), pv, cb); + buf->lpData = (char *)(buf + 1); + buf->dwBufferLength = cb; + buf->dwFlags = 0; + + if (waveOutPrepareHeader(This->hwave.out, buf, sizeof(WAVEHDR)) != MMSYSERR_NOERROR) + { + LeaveCriticalSection(&This->cs); + heap_free(buf); + return E_FAIL; + } + + waveOutWrite(This->hwave.out, buf, sizeof(WAVEHDR)); + + EnterCriticalSection(&This->pending_cs); + ++This->pending_buf_count; + TRACE("pending_buf_count = %Iu\n", This->pending_buf_count); + LeaveCriticalSection(&This->pending_cs); + + ResetEvent(This->event); + + LeaveCriticalSection(&This->cs); + + if (cb_written) + *cb_written = cb; + + return hr; +} + +static HRESULT WINAPI mmsysaudio_Seek(ISpMMSysAudio *iface, LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER *new_pos) +{ + FIXME("(%p, %s, %lu, %p): stub.\n", iface, wine_dbgstr_longlong(move.QuadPart), origin, new_pos); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_SetSize(ISpMMSysAudio *iface, ULARGE_INTEGER new_size) +{ + FIXME("(%p, %s): stub.\n", iface, wine_dbgstr_longlong(new_size.QuadPart)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_CopyTo(ISpMMSysAudio *iface, IStream *stream, ULARGE_INTEGER cb, + ULARGE_INTEGER *cb_read, ULARGE_INTEGER *cb_written) +{ + FIXME("(%p, %p, %s, %p, %p): stub.\n", iface, stream, wine_dbgstr_longlong(cb.QuadPart), cb_read, cb_written); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_Commit(ISpMMSysAudio *iface, DWORD flags) +{ + FIXME("(%p, %#lx): stub.\n", iface, flags); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_Revert(ISpMMSysAudio *iface) +{ + FIXME("(%p).\n", iface); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_LockRegion(ISpMMSysAudio *iface, ULARGE_INTEGER offset, ULARGE_INTEGER cb, + DWORD lock_type) +{ + FIXME("(%p, %s, %s, %#lx): stub.\n", iface, wine_dbgstr_longlong(offset.QuadPart), + wine_dbgstr_longlong(cb.QuadPart), lock_type); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_UnlockRegion(ISpMMSysAudio *iface, ULARGE_INTEGER offset, ULARGE_INTEGER cb, + DWORD lock_type) +{ + FIXME("(%p, %s, %s, %#lx): stub.\n", iface, wine_dbgstr_longlong(offset.QuadPart), + wine_dbgstr_longlong(cb.QuadPart), lock_type); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_Stat(ISpMMSysAudio *iface, STATSTG *statstg, DWORD flags) +{ + FIXME("(%p, %p, %#lx): stub.\n", iface, statstg, flags); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_Clone(ISpMMSysAudio *iface, IStream **stream) +{ + FIXME("(%p, %p): stub.\n", iface, stream); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_GetFormat(ISpMMSysAudio *iface, GUID *format, WAVEFORMATEX **wfx) +{ + struct mmaudio *This = impl_from_ISpMMSysAudio(iface); + + TRACE("(%p, %p, %p).\n", iface, format, wfx); + + if (!format || !wfx) + return E_POINTER; + + EnterCriticalSection(&This->cs); + + if (!(*wfx = CoTaskMemAlloc(sizeof(WAVEFORMATEX) + This->wfx->cbSize))) + { + LeaveCriticalSection(&This->cs); + return E_OUTOFMEMORY; + } + *format = SPDFID_WaveFormatEx; + memcpy(*wfx, This->wfx, sizeof(WAVEFORMATEX) + This->wfx->cbSize); + + LeaveCriticalSection(&This->cs); + + return S_OK; +} + +struct free_buf_task +{ + struct async_task task; + struct mmaudio *audio; + WAVEHDR *buf; +}; + +static void free_out_buf_proc(struct async_task *task) +{ + struct free_buf_task *fbt = (struct free_buf_task *)task; + size_t buf_count; + + TRACE("(%p).\n", task); + + waveOutUnprepareHeader(fbt->audio->hwave.out, fbt->buf, sizeof(WAVEHDR)); + heap_free(fbt->buf); + + EnterCriticalSection(&fbt->audio->pending_cs); + buf_count = --fbt->audio->pending_buf_count; + LeaveCriticalSection(&fbt->audio->pending_cs); + if (!buf_count) + SetEvent(fbt->audio->event); + TRACE("pending_buf_count = %Iu.\n", buf_count); +} + +static void CALLBACK wave_out_proc(HWAVEOUT hwo, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) +{ + struct mmaudio *This = (struct mmaudio *)instance; + struct free_buf_task *task; + + TRACE("(%p, %#x, %08Ix, %08Ix, %08Ix).\n", hwo, msg, instance, param1, param2); + + switch (msg) + { + case WOM_DONE: + if (!(task = heap_alloc(sizeof(*task)))) + { + ERR("failed to allocate free_buf_task.\n"); + break; + } + task->task.proc = free_out_buf_proc; + task->audio = This; + task->buf = (WAVEHDR *)param1; + async_queue_task(&This->queue, (struct async_task *)task); + break; + + default: + break; + } +} + +static HRESULT WINAPI mmsysaudio_SetState(ISpMMSysAudio *iface, SPAUDIOSTATE state, ULONGLONG reserved) +{ + struct mmaudio *This = impl_from_ISpMMSysAudio(iface); + HRESULT hr = S_OK; + + TRACE("(%p, %u, %s).\n", iface, state, wine_dbgstr_longlong(reserved)); + + if (state != SPAS_CLOSED && state != SPAS_RUN) + { + FIXME("state %#x not implemented.\n", state); + return E_NOTIMPL; + } + + EnterCriticalSection(&This->cs); + + if (This->state == state) + goto done; + + if (This->state == SPAS_CLOSED) + { + if (FAILED(hr = async_start_queue(&This->queue))) + { + ERR("Failed to start async queue: %#lx.\n", hr); + goto done; + } + + if (waveOutOpen(&This->hwave.out, This->device_id, This->wfx, (DWORD_PTR)wave_out_proc, + (DWORD_PTR)This, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) + { + hr = SPERR_GENERIC_MMSYS_ERROR; + goto done; + } + } + + if (state == SPAS_CLOSED && This->state != SPAS_CLOSED) + { + waveOutReset(This->hwave.out); + /* Wait until all buffers are freed. */ + WaitForSingleObject(This->event, INFINITE); + + if (waveOutClose(This->hwave.out) != MMSYSERR_NOERROR) + { + hr = SPERR_GENERIC_MMSYS_ERROR; + goto done; + } + } + + This->state = state; + +done: + LeaveCriticalSection(&This->cs); + return hr; +} + +static HRESULT WINAPI mmsysaudio_SetFormat(ISpMMSysAudio *iface, const GUID *guid, const WAVEFORMATEX *wfx) +{ + struct mmaudio *This = impl_from_ISpMMSysAudio(iface); + MMRESULT res; + WAVEFORMATEX *new_wfx; + + TRACE("(%p, %s, %p).\n", iface, debugstr_guid(guid), wfx); + + if (!guid || !wfx || !IsEqualGUID(guid, &SPDFID_WaveFormatEx)) + return E_INVALIDARG; + + EnterCriticalSection(&This->cs); + + if (!memcmp(wfx, This->wfx, sizeof(*wfx)) && !memcmp(wfx + 1, This->wfx + 1, wfx->cbSize)) + { + LeaveCriticalSection(&This->cs); + return S_OK; + } + + if (This->state != SPAS_CLOSED) + { + LeaveCriticalSection(&This->cs); + return SPERR_DEVICE_BUSY; + } + + /* Determine whether the device supports the requested format. */ + res = waveOutOpen(NULL, This->device_id, wfx, 0, 0, WAVE_FORMAT_QUERY); + if (res != MMSYSERR_NOERROR) + { + LeaveCriticalSection(&This->cs); + return res == WAVERR_BADFORMAT ? SPERR_UNSUPPORTED_FORMAT : SPERR_GENERIC_MMSYS_ERROR; + } + + if (!(new_wfx = heap_alloc(sizeof(*wfx) + wfx->cbSize))) + { + LeaveCriticalSection(&This->cs); + return E_OUTOFMEMORY; + } + memcpy(new_wfx, wfx, sizeof(*wfx) + wfx->cbSize); + heap_free(This->wfx); + This->wfx = new_wfx; + + LeaveCriticalSection(&This->cs); + + return S_OK; +} + +static HRESULT WINAPI mmsysaudio_GetStatus(ISpMMSysAudio *iface, SPAUDIOSTATUS *status) +{ + FIXME("(%p, %p): stub.\n", iface, status); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_SetBufferInfo(ISpMMSysAudio *iface, const SPAUDIOBUFFERINFO *info) +{ + FIXME("(%p, %p): stub.\n", iface, info); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_GetBufferInfo(ISpMMSysAudio *iface, SPAUDIOBUFFERINFO *info) +{ + FIXME("(%p, %p): stub.\n", iface, info); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_GetDefaultFormat(ISpMMSysAudio *iface, GUID *guid, WAVEFORMATEX **wfx) +{ + FIXME("(%p, %p, %p): stub.\n", iface, guid, wfx); + + return E_NOTIMPL; +} + +static HANDLE WINAPI mmsysaudio_EventHandle(ISpMMSysAudio *iface) +{ + struct mmaudio *This = impl_from_ISpMMSysAudio(iface); + + TRACE("(%p).\n", iface); + + return This->event; +} + +static HRESULT WINAPI mmsysaudio_GetVolumeLevel(ISpMMSysAudio *iface, ULONG *level) +{ + FIXME("(%p, %p): stub.\n", iface, level); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_SetVolumeLevel(ISpMMSysAudio *iface, ULONG level) +{ + FIXME("(%p, %lu): stub.\n", iface, level); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_GetBufferNotifySize(ISpMMSysAudio *iface, ULONG *size) +{ + FIXME("(%p, %p): stub.\n", iface, size); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_SetBufferNotifySize(ISpMMSysAudio *iface, ULONG size) +{ + FIXME("(%p, %lu): stub.\n", iface, size); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_GetDeviceId(ISpMMSysAudio *iface, UINT *id) +{ + struct mmaudio *This = impl_from_ISpMMSysAudio(iface); + + TRACE("(%p, %p).\n", iface, id); + + if (!id) return E_POINTER; + + EnterCriticalSection(&This->cs); + *id = This->device_id; + LeaveCriticalSection(&This->cs); + + return S_OK; +} + +static HRESULT WINAPI mmsysaudio_SetDeviceId(ISpMMSysAudio *iface, UINT id) +{ + struct mmaudio *This = impl_from_ISpMMSysAudio(iface); + + TRACE("(%p, %u).\n", iface, id); + + if (id != WAVE_MAPPER && id >= waveOutGetNumDevs()) + return E_INVALIDARG; + + EnterCriticalSection(&This->cs); + + if (id == This->device_id) + { + LeaveCriticalSection(&This->cs); + return S_OK; + } + if (This->state != SPAS_CLOSED) + { + LeaveCriticalSection(&This->cs); + return SPERR_DEVICE_BUSY; + } + This->device_id = id; + + LeaveCriticalSection(&This->cs); + + return S_OK; +} + +static HRESULT WINAPI mmsysaudio_GetMMHandle(ISpMMSysAudio *iface, void **handle) +{ + FIXME("(%p, %p): stub.\n", iface, handle); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_GetLineId(ISpMMSysAudio *iface, UINT *id) +{ + FIXME("(%p, %p): stub.\n", iface, id); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_SetLineId(ISpMMSysAudio *iface, UINT id) +{ + FIXME("(%p, %u): stub.\n", iface, id); + + return E_NOTIMPL; +} + +static const ISpMMSysAudioVtbl mmsysaudio_vtbl = +{ + mmsysaudio_QueryInterface, + mmsysaudio_AddRef, + mmsysaudio_Release, + mmsysaudio_Read, + mmsysaudio_Write, + mmsysaudio_Seek, + mmsysaudio_SetSize, + mmsysaudio_CopyTo, + mmsysaudio_Commit, + mmsysaudio_Revert, + mmsysaudio_LockRegion, + mmsysaudio_UnlockRegion, + mmsysaudio_Stat, + mmsysaudio_Clone, + mmsysaudio_GetFormat, + mmsysaudio_SetState, + mmsysaudio_SetFormat, + mmsysaudio_GetStatus, + mmsysaudio_SetBufferInfo, + mmsysaudio_GetBufferInfo, + mmsysaudio_GetDefaultFormat, + mmsysaudio_EventHandle, + mmsysaudio_GetVolumeLevel, + mmsysaudio_SetVolumeLevel, + mmsysaudio_GetBufferNotifySize, + mmsysaudio_SetBufferNotifySize, + mmsysaudio_GetDeviceId, + mmsysaudio_SetDeviceId, + mmsysaudio_GetMMHandle, + mmsysaudio_GetLineId, + mmsysaudio_SetLineId +}; + +static HRESULT mmaudio_create(IUnknown *outer, REFIID iid, void **obj, enum flow_type flow) +{ + struct mmaudio *This; + HRESULT hr; + + if (flow != FLOW_OUT) + { + FIXME("flow %d not implemented.\n", flow); + return E_NOTIMPL; + } + + if (!(This = heap_alloc_zero(sizeof(*This)))) + return E_OUTOFMEMORY; + This->ISpEventSource_iface.lpVtbl = &event_source_vtbl; + This->ISpEventSink_iface.lpVtbl = &event_sink_vtbl; + This->ISpObjectWithToken_iface.lpVtbl = &objwithtoken_vtbl; + This->ISpMMSysAudio_iface.lpVtbl = &mmsysaudio_vtbl; + This->ref = 1; + + This->flow = flow; + This->token = NULL; + This->device_id = WAVE_MAPPER; + This->state = SPAS_CLOSED; + + if (!(This->wfx = heap_alloc(sizeof(*This->wfx)))) + { + heap_free(This); + return E_OUTOFMEMORY; + } + This->wfx->wFormatTag = WAVE_FORMAT_PCM; + This->wfx->nChannels = 1; + This->wfx->nSamplesPerSec = 22050; + This->wfx->nAvgBytesPerSec = 22050 * 2; + This->wfx->nBlockAlign = 2; + This->wfx->wBitsPerSample = 16; + This->wfx->cbSize = 0; + + This->pending_buf_count = 0; + This->event = CreateEventW(NULL, TRUE, TRUE, NULL); + + InitializeCriticalSection(&This->cs); + InitializeCriticalSection(&This->pending_cs); + + hr = ISpMMSysAudio_QueryInterface(&This->ISpMMSysAudio_iface, iid, obj); + ISpMMSysAudio_Release(&This->ISpMMSysAudio_iface); + return hr; +} + +HRESULT mmaudio_out_create(IUnknown *outer, REFIID iid, void **obj) +{ + return mmaudio_create(outer, iid, obj, FLOW_OUT); +} diff --git a/dlls/sapi/sapi_classes.idl b/dlls/sapi/sapi_classes.idl index bb580dde18e..14f24aa9c02 100644 --- a/dlls/sapi/sapi_classes.idl +++ b/dlls/sapi/sapi_classes.idl @@ -103,3 +103,18 @@ coclass SpFileStream interface ISpStream; [default] interface ISpeechFileStream; } + +[ + uuid(a8c680eb-3d32-11d2-9ee7-00c04f797396), + helpstring("SpMMAudioOut"), + progid("SAPI.SpMMAudioOut.1"), + vi_progid("SAPI.SpMMAudioOut"), + threading(both) +] +coclass SpMMAudioOut +{ + interface ISpEventSource; + interface ISpEventSink; + interface ISpObjectWithToken; + interface ISpMMSysAudio; +} diff --git a/dlls/sapi/sapi_private.h b/dlls/sapi/sapi_private.h index fcecafb450e..d38efb73b2e 100644 --- a/dlls/sapi/sapi_private.h +++ b/dlls/sapi/sapi_private.h @@ -19,12 +19,37 @@ */ #include "wine/heap.h" +#include "wine/list.h" + +struct async_task +{ + struct list entry; + void (*proc)(struct async_task *); +}; + +struct async_queue +{ + BOOL init; + HANDLE wait; + HANDLE ready; + HANDLE empty; + HANDLE cancel; + struct list tasks; + CRITICAL_SECTION cs; +}; + +HRESULT async_start_queue(struct async_queue *queue); +void async_empty_queue(struct async_queue *queue); +void async_cancel_queue(struct async_queue *queue); +HRESULT async_queue_task(struct async_queue *queue, struct async_task *task); +HRESULT async_wait_queue_empty(struct async_queue *queue, DWORD timeout); HRESULT data_key_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; HRESULT file_stream_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; HRESULT resource_manager_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; HRESULT speech_stream_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; HRESULT speech_voice_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; +HRESULT mmaudio_out_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; HRESULT token_category_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; HRESULT token_enum_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; HRESULT token_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; diff --git a/dlls/sapi/tests/Makefile.in b/dlls/sapi/tests/Makefile.in index 75c70d072d8..ea14710194f 100644 --- a/dlls/sapi/tests/Makefile.in +++ b/dlls/sapi/tests/Makefile.in @@ -1,8 +1,9 @@ TESTDLL = sapi.dll -IMPORTS = ole32 user32 advapi32 +IMPORTS = ole32 user32 advapi32 winmm C_SRCS = \ automation.c \ + mmaudio.c \ resource.c \ stream.c \ token.c \ diff --git a/dlls/sapi/tests/mmaudio.c b/dlls/sapi/tests/mmaudio.c new file mode 100644 index 00000000000..a990430d784 --- /dev/null +++ b/dlls/sapi/tests/mmaudio.c @@ -0,0 +1,295 @@ +/* + * Speech API (SAPI) winmm audio tests. + * + * Copyright 2023 Shaun Ren for CodeWeavers + * + * 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 + +#include "sapiddk.h" +#include "sperror.h" +#include "initguid.h" + +#include "wine/test.h" + +DEFINE_GUID(SPDFID_Text, 0x7ceef9f9, 0x3d13, 0x11d2, 0x9e, 0xe7, 0x00, 0xc0, 0x4f, 0x79, 0x73, 0x96); +DEFINE_GUID(SPDFID_WaveFormatEx, 0xc31adbae, 0x527f, 0x4ff5, 0xa2, 0x30, 0xf6, 0x2b, 0xb6, 0x1f, 0xf7, 0x0c); + +static void test_interfaces(void) +{ + ISpMMSysAudio *mmaudio; + IUnknown *unk; + ISpEventSource *source; + ISpEventSink *sink; + ISpObjectWithToken *obj_with_token; + HRESULT hr; + + hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpMMSysAudio, (void **)&mmaudio); + ok(hr == S_OK, "Failed to create ISpMMSysAudio interface: %#lx.\n", hr); + ISpMMSysAudio_Release(mmaudio); + + hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_IUnknown, (void **)&unk); + ok(hr == S_OK, "Failed to create IUnknown interface: %#lx.\n", hr); + IUnknown_Release(unk); + + hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpEventSource, (void **)&source); + ok(hr == S_OK, "Failed to create ISpEventSource interface: %#lx.\n", hr); + ISpEventSource_Release(source); + + hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpEventSink, (void **)&sink); + ok(hr == S_OK, "Failed to create ISpEventSink interface: %#lx.\n", hr); + ISpEventSink_Release(sink); + + hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectWithToken, (void **)&obj_with_token); + ok(hr == S_OK, "Failed to create ISpObjectWithToken interface: %#lx.\n", hr); + ISpObjectWithToken_Release(obj_with_token); +} + +static void test_device_id(void) +{ + ISpMMSysAudio *mmaudio; + UINT id, num_devs; + HRESULT hr; + + hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpMMSysAudio, (void **)&mmaudio); + ok(hr == S_OK, "failed to create SpMMAudioOut instance: %#lx.\n", hr); + + id = 0xdeadbeef; + hr = ISpMMSysAudio_GetDeviceId(mmaudio, &id); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(id == WAVE_MAPPER, "got %#x.\n", id); + + hr = ISpMMSysAudio_SetDeviceId(mmaudio, WAVE_MAPPER); + ok(hr == S_OK, "got %#lx.\n", hr); + + num_devs = waveOutGetNumDevs(); + if (num_devs == 0) { + skip("no wave out devices.\n"); + ISpMMSysAudio_Release(mmaudio); + return; + } + + hr = ISpMMSysAudio_SetDeviceId(mmaudio, num_devs); + ok(hr == E_INVALIDARG, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetDeviceId(mmaudio, 0); + ok(hr == S_OK || broken(hr == S_FALSE) /* Windows */, "got %#lx.\n", hr); + + id = 0xdeadbeef; + hr = ISpMMSysAudio_GetDeviceId(mmaudio, &id); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(id == 0, "got %u.\n", id); + + ISpMMSysAudio_Release(mmaudio); +} + +static void test_formats(void) +{ + ISpMMSysAudio *mmaudio; + GUID fmtid; + WAVEFORMATEX *wfx; + WAVEFORMATEX wfx2; + HRESULT hr; + + hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpMMSysAudio, (void **)&mmaudio); + ok(hr == S_OK, "failed to create SpMMAudioOut instance: %#lx.\n", hr); + + wfx = NULL; + hr = ISpMMSysAudio_GetFormat(mmaudio, &fmtid, &wfx); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(IsEqualGUID(&fmtid, &SPDFID_WaveFormatEx), "got %s.\n", wine_dbgstr_guid(&fmtid)); + ok(wfx != NULL, "wfx == NULL.\n"); + ok(wfx->wFormatTag == WAVE_FORMAT_PCM, "got %u.\n", wfx->wFormatTag); + ok(wfx->nChannels == 1, "got %u.\n", wfx->nChannels); + ok(wfx->nSamplesPerSec == 22050, "got %lu.\n", wfx->nSamplesPerSec); + ok(wfx->nAvgBytesPerSec == 22050 * 2, "got %lu.\n", wfx->nAvgBytesPerSec); + ok(wfx->nBlockAlign == 2, "got %u.\n", wfx->nBlockAlign); + ok(wfx->wBitsPerSample == 16, "got %u.\n", wfx->wBitsPerSample); + ok(wfx->cbSize == 0, "got %u.\n", wfx->cbSize); + CoTaskMemFree(wfx); + + hr = ISpMMSysAudio_SetFormat(mmaudio, NULL, NULL); + ok(hr == E_INVALIDARG, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetFormat(mmaudio, &SPDFID_Text, NULL); + ok(hr == E_INVALIDARG, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetFormat(mmaudio, &SPDFID_WaveFormatEx, NULL); + ok(hr == E_INVALIDARG, "got %#lx.\n", hr); + + if (waveOutGetNumDevs() == 0) { + skip("no wave out devices.\n"); + ISpMMSysAudio_Release(mmaudio); + return; + } + + wfx2.wFormatTag = WAVE_FORMAT_PCM; + wfx2.nChannels = 2; + wfx2.nSamplesPerSec = 16000; + wfx2.nAvgBytesPerSec = 16000 * 2 * 2; + wfx2.nBlockAlign = 2 * 2; + wfx2.wBitsPerSample = 16; + wfx2.cbSize = 0; + + hr = ISpMMSysAudio_SetFormat(mmaudio, &SPDFID_WaveFormatEx, &wfx2); + ok(hr == S_OK, "got %#lx.\n", hr); + + ISpMMSysAudio_Release(mmaudio); +} + +static void test_audio_out(void) +{ + ISpMMSysAudio *mmaudio; + GUID fmtid; + WAVEFORMATEX *wfx = NULL; + WAVEFORMATEX wfx2; + UINT devid; + char *buf = NULL; + ULONG written; + DWORD start, duration; + HANDLE event = NULL; + HRESULT hr; + + if (waveOutGetNumDevs() == 0) { + skip("no wave out devices.\n"); + return; + } + + hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpMMSysAudio, (void **)&mmaudio); + ok(hr == S_OK, "failed to create SPMMAudioOut instance: %#lx.\n", hr); + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_CLOSED, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_GetFormat(mmaudio, &fmtid, &wfx); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(IsEqualGUID(&fmtid, &SPDFID_WaveFormatEx), "got %s.\n", wine_dbgstr_guid(&fmtid)); + ok(wfx != NULL, "wfx == NULL.\n"); + ok(wfx->wFormatTag == WAVE_FORMAT_PCM, "got %u.\n", wfx->wFormatTag); + ok(wfx->cbSize == 0, "got %u.\n", wfx->cbSize); + + hr = ISpMMSysAudio_SetFormat(mmaudio, &fmtid, wfx); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetDeviceId(mmaudio, WAVE_MAPPER); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_RUN, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetDeviceId(mmaudio, WAVE_MAPPER); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetDeviceId(mmaudio, 0); + ok(hr == SPERR_DEVICE_BUSY, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetFormat(mmaudio, &fmtid, wfx); + ok(hr == S_OK, "got %#lx.\n", hr); + + memcpy(&wfx2, wfx, sizeof(wfx2)); + wfx2.nChannels = wfx->nChannels == 1 ? 2 : 1; + wfx2.nAvgBytesPerSec = wfx2.nSamplesPerSec * wfx2.nChannels * wfx2.wBitsPerSample / 8; + wfx2.nBlockAlign = wfx2.nChannels * wfx2.wBitsPerSample / 8; + + hr = ISpMMSysAudio_SetFormat(mmaudio, &fmtid, &wfx2); + ok(hr == SPERR_DEVICE_BUSY, "got %#lx.\n", hr); + + devid = 0xdeadbeef; + hr = ISpMMSysAudio_GetDeviceId(mmaudio, &devid); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(devid == WAVE_MAPPER, "got %#x.\n", devid); + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_CLOSED, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + + buf = calloc(1, wfx->nAvgBytesPerSec); + ok(buf != NULL, "failed to allocate buffer.\n"); + + hr = ISpMMSysAudio_Write(mmaudio, buf, wfx->nAvgBytesPerSec, NULL); + ok(hr == SP_AUDIO_STOPPED, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_STOP, 0); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + if (hr == S_OK) + { + hr = ISpMMSysAudio_Write(mmaudio, buf, wfx->nAvgBytesPerSec, NULL); + ok(hr == SP_AUDIO_STOPPED, "got %#lx.\n", hr); + } + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_CLOSED, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_RUN, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_Write(mmaudio, buf, wfx->nAvgBytesPerSec, NULL); + ok(hr == S_OK, "got %#lx.\n", hr); + + Sleep(200); + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_CLOSED, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_RUN, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + + written = 0xdeadbeef; + start = GetTickCount(); + hr = ISpMMSysAudio_Write(mmaudio, buf, wfx->nAvgBytesPerSec * 200 / 1000, &written); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(written == wfx->nAvgBytesPerSec * 200 / 1000, "got %lu.\n", written); + + hr = ISpMMSysAudio_Write(mmaudio, buf, wfx->nAvgBytesPerSec * 200 / 1000, NULL); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_Commit(mmaudio, STGC_DEFAULT); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + + event = ISpMMSysAudio_EventHandle(mmaudio); + ok(event != NULL, "event == NULL.\n"); + + hr = WaitForSingleObject(event, 1000); + ok(hr == WAIT_OBJECT_0, "got %#lx.\n", hr); + + duration = GetTickCount() - start; + ok(duration > 200 && duration < 800, "took %lu ms.\n", duration); + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_CLOSED, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + + CoTaskMemFree(wfx); + free(buf); + ISpMMSysAudio_Release(mmaudio); +} + +START_TEST(mmaudio) +{ + CoInitialize(NULL); + test_interfaces(); + test_device_id(); + test_formats(); + test_audio_out(); + CoUninitialize(); +} diff --git a/dlls/sapi/tests/token.c b/dlls/sapi/tests/token.c index 958c50359f5..8befd98c2a5 100644 --- a/dlls/sapi/tests/token.c +++ b/dlls/sapi/tests/token.c @@ -33,7 +33,7 @@ static void test_data_key(void) HRESULT hr; HKEY key; LONG res; - WCHAR *value; + WCHAR *value = NULL; hr = CoCreateInstance( &CLSID_SpDataKey, NULL, CLSCTX_INPROC_SERVER, &IID_ISpRegDataKey, (void **)&data_key ); @@ -49,6 +49,9 @@ static void test_data_key(void) hr = ISpRegDataKey_GetStringValue( data_key, L"Voice", &value ); ok( hr == E_HANDLE, "got %08lx\n", hr ); + hr = ISpRegDataKey_SetStringValue( data_key, L"Voice", L"Test" ); + ok( hr == E_HANDLE, "got %08lx\n", hr ); + hr = ISpRegDataKey_SetKey( data_key, key, FALSE ); ok( hr == S_OK, "got %08lx\n", hr ); hr = ISpRegDataKey_SetKey( data_key, key, FALSE ); @@ -60,19 +63,119 @@ static void test_data_key(void) hr = ISpRegDataKey_GetStringValue( data_key, L"", &value ); ok( hr == SPERR_NOT_FOUND, "got %08lx\n", hr ); + hr = ISpRegDataKey_SetStringValue( data_key, L"Voice", L"Test" ); + ok( hr == S_OK, "got %08lx\n", hr ); + + hr = ISpRegDataKey_GetStringValue( data_key, L"Voice", &value ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( !wcscmp( value, L"Test" ), "got %s\n", wine_dbgstr_w(value) ); + CoTaskMemFree( value ); + + hr = ISpRegDataKey_OpenKey( data_key, L"Testing", &sub ); + ok( hr == SPERR_NOT_FOUND, "got %08lx\n", hr ); + hr = ISpRegDataKey_CreateKey( data_key, L"Testing", &sub ); ok( hr == S_OK, "got %08lx\n", hr ); ISpDataKey_Release(sub); + hr = ISpRegDataKey_OpenKey( data_key, L"Testing", &sub ); + ok( hr == S_OK, "got %08lx\n", hr ); + ISpDataKey_Release(sub); + + ISpRegDataKey_Release( data_key ); + + hr = CoCreateInstance( &CLSID_SpDataKey, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpRegDataKey, (void **)&data_key ); + ok( hr == S_OK, "got %08lx\n", hr ); + + res = RegOpenKeyExA( HKEY_CURRENT_USER, "Software\\Winetest\\sapi", 0, KEY_ALL_ACCESS, &key ); + ok( res == ERROR_SUCCESS, "got %ld\n", res ); + + hr = ISpRegDataKey_SetKey( data_key, key, TRUE ); + ok( hr == S_OK, "got %08lx\n", hr ); + + hr = ISpRegDataKey_SetStringValue( data_key, L"Voice2", L"Test2" ); + ok( hr == S_OK, "got %08lx\n", hr ); + + hr = ISpRegDataKey_GetStringValue( data_key, L"Voice2", &value ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( !wcscmp( value, L"Test2" ), "got %s\n", wine_dbgstr_w(value) ); + CoTaskMemFree( value ); + + hr = ISpRegDataKey_CreateKey( data_key, L"Testing2", &sub ); + ok( hr == S_OK, "got %08lx\n", hr ); + ISpDataKey_Release(sub); + ISpRegDataKey_Release( data_key ); } +static void setup_test_voice_tokens(void) +{ + HKEY key; + ISpRegDataKey *data_key; + ISpDataKey *attrs_key; + LSTATUS ret; + HRESULT hr; + + ret = RegCreateKeyExA( HKEY_CURRENT_USER, "Software\\Winetest\\sapi\\TestVoices\\Tokens", 0, NULL, 0, + KEY_ALL_ACCESS, NULL, &key, NULL ); + ok( ret == ERROR_SUCCESS, "got %ld\n", ret ); + + hr = CoCreateInstance( &CLSID_SpDataKey, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpRegDataKey, (void **)&data_key ); + ok( hr == S_OK, "got %08lx\n", hr ); + hr = ISpRegDataKey_SetKey( data_key, key, FALSE ); + ok( hr == S_OK, "got %08lx\n", hr ); + + ISpRegDataKey_CreateKey( data_key, L"Voice1\\Attributes", &attrs_key ); + ISpDataKey_SetStringValue( attrs_key, L"Language", L"409" ); + ISpDataKey_SetStringValue( attrs_key, L"Gender", L"Female" ); + ISpDataKey_SetStringValue( attrs_key, L"Age", L"Child" ); + ISpDataKey_SetStringValue( attrs_key, L"Vendor", L"Vendor2" ); + ISpDataKey_Release( attrs_key ); + + ISpRegDataKey_CreateKey( data_key, L"Voice2\\Attributes", &attrs_key ); + ISpDataKey_SetStringValue( attrs_key, L"Language", L"406;407;408;409;40a" ); + ISpDataKey_SetStringValue( attrs_key, L"Gender", L"Female" ); + ISpDataKey_SetStringValue( attrs_key, L"Age", L"Adult" ); + ISpDataKey_SetStringValue( attrs_key, L"Vendor", L"Vendor1" ); + ISpDataKey_Release( attrs_key ); + + ISpRegDataKey_CreateKey( data_key, L"Voice3\\Attributes", &attrs_key ); + ISpDataKey_SetStringValue( attrs_key, L"Language", L"409;411" ); + ISpDataKey_SetStringValue( attrs_key, L"Gender", L"Female" ); + ISpDataKey_SetStringValue( attrs_key, L"Age", L"Child" ); + ISpDataKey_SetStringValue( attrs_key, L"Vendor", L"Vendor1" ); + ISpDataKey_Release( attrs_key ); + + ISpRegDataKey_CreateKey( data_key, L"Voice4\\Attributes", &attrs_key ); + ISpDataKey_SetStringValue( attrs_key, L"Language", L"411" ); + ISpDataKey_SetStringValue( attrs_key, L"Gender", L"Male" ); + ISpDataKey_SetStringValue( attrs_key, L"Age", L"Adult" ); + ISpDataKey_Release( attrs_key ); + + ISpRegDataKey_CreateKey( data_key, L"Voice5\\Attributes", &attrs_key ); + ISpDataKey_SetStringValue( attrs_key, L"Language", L"411" ); + ISpDataKey_SetStringValue( attrs_key, L"Gender", L"Female" ); + ISpDataKey_SetStringValue( attrs_key, L"Age", L"Adult" ); + ISpDataKey_SetStringValue( attrs_key, L"Vendor", L"Vendor2" ); + ISpDataKey_Release( attrs_key ); + + ISpRegDataKey_Release( data_key ); +} + +static const WCHAR test_cat[] = L"HKEY_CURRENT_USER\\Software\\Winetest\\sapi\\TestVoices"; + static void test_token_category(void) { ISpObjectTokenCategory *cat; IEnumSpObjectTokens *enum_tokens; HRESULT hr; ULONG count; + int i; + ISpObjectToken *token; + WCHAR *token_id; + WCHAR tmp[MAX_PATH]; hr = CoCreateInstance( &CLSID_SpObjectTokenCategory, NULL, CLSCTX_INPROC_SERVER, &IID_ISpObjectTokenCategory, (void **)&cat ); @@ -84,19 +187,58 @@ static void test_token_category(void) hr = ISpObjectTokenCategory_SetId( cat, L"bogus", FALSE ); ok( hr == SPERR_INVALID_REGISTRY_KEY, "got %08lx\n", hr ); - hr = ISpObjectTokenCategory_SetId( cat, SPCAT_VOICES, FALSE ); + hr = ISpObjectTokenCategory_SetId( cat, test_cat, FALSE ); ok( hr == S_OK, "got %08lx\n", hr ); - hr = ISpObjectTokenCategory_SetId( cat, SPCAT_VOICES, FALSE ); + hr = ISpObjectTokenCategory_SetId( cat, test_cat, FALSE ); ok( hr == SPERR_ALREADY_INITIALIZED, "got %08lx\n", hr ); hr = ISpObjectTokenCategory_EnumTokens( cat, NULL, NULL, &enum_tokens ); ok( hr == S_OK, "got %08lx\n", hr ); + count = 0xdeadbeef; + hr = IEnumSpObjectTokens_GetCount( enum_tokens, &count ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( count == 5, "got %lu\n", count ); + + IEnumSpObjectTokens_Release( enum_tokens ); + + hr = ISpObjectTokenCategory_EnumTokens( cat, L"Language=409", NULL, &enum_tokens ); + ok( hr == S_OK, "got %08lx\n", hr ); + + count = 0xdeadbeef; + hr = IEnumSpObjectTokens_GetCount( enum_tokens, &count ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( count == 3, "got %lu\n", count ); + + IEnumSpObjectTokens_Release( enum_tokens ); + + hr = ISpObjectTokenCategory_EnumTokens( cat, L"Language=409", L"Vendor=Vendor1;Age=Child;Gender=Female", + &enum_tokens ); + ok( hr == S_OK, "got %08lx\n", hr ); + + count = 0xdeadbeef; hr = IEnumSpObjectTokens_GetCount( enum_tokens, &count ); ok( hr == S_OK, "got %08lx\n", hr ); + ok( count == 3, "got %lu\n", count ); + + for ( i = 0; i < 3; i++ ) { + token = NULL; + hr = IEnumSpObjectTokens_Item( enum_tokens, i, &token ); + ok( hr == S_OK, "i = %d: got %08lx\n", i, hr ); + + token_id = NULL; + hr = ISpObjectToken_GetId( token, &token_id ); + ok( hr == S_OK, "i = %d: got %08lx\n", i, hr ); + swprintf( tmp, MAX_PATH, L"%ls\\Tokens\\Voice%d", test_cat, 3 - i ); + ok( !wcscmp( token_id, tmp ), "i = %d: got %s\n", i, wine_dbgstr_w(token_id) ); + + CoTaskMemFree( token_id ); + ISpObjectToken_Release( token ); + } IEnumSpObjectTokens_Release( enum_tokens ); + ISpObjectTokenCategory_Release( cat ); } @@ -104,8 +246,11 @@ static void test_token_enum(void) { ISpObjectTokenEnumBuilder *token_enum; HRESULT hr; - ISpObjectToken *token; + ISpObjectToken *tokens[5]; + ISpObjectToken *out_tokens[5]; + WCHAR token_id[MAX_PATH]; ULONG count; + int i; hr = CoCreateInstance( &CLSID_SpObjectTokenEnum, NULL, CLSCTX_INPROC_SERVER, &IID_ISpObjectTokenEnumBuilder, (void **)&token_enum ); @@ -114,7 +259,7 @@ static void test_token_enum(void) hr = ISpObjectTokenEnumBuilder_GetCount( token_enum, &count ); ok( hr == SPERR_UNINITIALIZED, "got %08lx\n", hr ); - hr = ISpObjectTokenEnumBuilder_Next( token_enum, 1, &token, &count ); + hr = ISpObjectTokenEnumBuilder_Next( token_enum, 1, tokens, &count ); ok( hr == SPERR_UNINITIALIZED, "got %08lx\n", hr ); hr = ISpObjectTokenEnumBuilder_SetAttribs( token_enum, NULL, NULL ); @@ -126,11 +271,139 @@ static void test_token_enum(void) ok( count == 0, "got %lu\n", count ); count = 0xdeadbeef; - hr = ISpObjectTokenEnumBuilder_Next( token_enum, 1, &token, &count ); + hr = ISpObjectTokenEnumBuilder_Next( token_enum, 1, &out_tokens[0], &count ); ok( hr == S_FALSE, "got %08lx\n", hr ); ok( count == 0, "got %lu\n", count ); + for ( i = 0; i < 5; i++ ) { + hr = CoCreateInstance( &CLSID_SpObjectToken, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectToken, (void **)&tokens[i] ); + ok( hr == S_OK, "got %08lx\n", hr ); + + swprintf( token_id, MAX_PATH, L"%ls\\Tokens\\Voice%d", test_cat, i + 1 ); + hr = ISpObjectToken_SetId( tokens[i], NULL, token_id, FALSE ); + ok( hr == S_OK, "got %08lx\n", hr ); + } + + hr = ISpObjectTokenEnumBuilder_AddTokens( token_enum, 3, tokens ); + ok( hr == S_OK, "got %08lx\n", hr ); + + out_tokens[0] = (ISpObjectToken *)0xdeadbeef; + hr = ISpObjectTokenEnumBuilder_Next( token_enum, 1, &out_tokens[0], NULL ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( out_tokens[0] == tokens[0], "got %p\n", out_tokens[0] ); + + out_tokens[0] = (ISpObjectToken *)0xdeadbeef; + hr = ISpObjectTokenEnumBuilder_Item( token_enum, 0, &out_tokens[0] ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( out_tokens[0] == tokens[0], "got %p\n", out_tokens[0] ); + + hr = ISpObjectTokenEnumBuilder_Item( token_enum, 3, &out_tokens[0] ); + ok( hr == SPERR_NO_MORE_ITEMS, "got %08lx\n", hr ); + + hr = ISpObjectTokenEnumBuilder_Next( token_enum, 3, &out_tokens[1], NULL ); + ok( hr == E_POINTER, "got %08lx\n", hr ); + + count = 0xdeadbeef; + out_tokens[1] = out_tokens[2] = (ISpObjectToken *)0xdeadbeef; + hr = ISpObjectTokenEnumBuilder_Next( token_enum, 3, &out_tokens[1], &count ); + ok( hr == S_FALSE, "got %08lx\n", hr ); + ok( count == 2, "got %lu\n", count ); + ok( out_tokens[1] == tokens[1], "got %p\n", out_tokens[1] ); + ok( out_tokens[2] == tokens[2], "got %p\n", out_tokens[2] ); + ISpObjectTokenEnumBuilder_Release( token_enum ); + + hr = CoCreateInstance( &CLSID_SpObjectTokenEnum, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectTokenEnumBuilder, (void **)&token_enum ); + ok( hr == S_OK, "got %08lx\n", hr ); + + /* Vendor attribute must exist */ + hr = ISpObjectTokenEnumBuilder_SetAttribs( token_enum, L"Vendor", NULL ); + ok( hr == S_OK, "got %08lx\n", hr ); + hr = ISpObjectTokenEnumBuilder_AddTokens( token_enum, 5, tokens ); + ok( hr == S_OK, "got %08lx\n", hr ); + + count = 0xdeadbeef; + hr = ISpObjectTokenEnumBuilder_Next( token_enum, 5, &out_tokens[0], &count ); + ok( hr == S_FALSE, "got %08lx\n", hr ); + ok( count == 4, "got %lu\n", count ); + + ISpObjectTokenEnumBuilder_Release( token_enum ); + + hr = CoCreateInstance( &CLSID_SpObjectTokenEnum, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectTokenEnumBuilder, (void **)&token_enum ); + ok( hr == S_OK, "got %08lx\n", hr ); + + /* Vendor attribute must contain Vendor1 */ + hr = ISpObjectTokenEnumBuilder_SetAttribs( token_enum, L"Vendor=Vendor1", NULL ); + ok( hr == S_OK, "got %08lx\n", hr ); + hr = ISpObjectTokenEnumBuilder_AddTokens( token_enum, 5, tokens ); + ok( hr == S_OK, "got %08lx\n", hr ); + + count = 0xdeadbeef; + hr = ISpObjectTokenEnumBuilder_Next( token_enum, 5, &out_tokens[0], &count ); + ok( hr == S_FALSE, "got %08lx\n", hr ); + ok( count == 2, "got %lu\n", count ); + + ISpObjectTokenEnumBuilder_Release( token_enum ); + + hr = CoCreateInstance( &CLSID_SpObjectTokenEnum, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectTokenEnumBuilder, (void **)&token_enum ); + ok( hr == S_OK, "got %08lx\n", hr ); + + /* Vendor attribute must not contain Vendor1 */ + hr = ISpObjectTokenEnumBuilder_SetAttribs( token_enum, L"Vendor!=Vendor1", NULL ); + ok( hr == S_OK, "got %08lx\n", hr ); + hr = ISpObjectTokenEnumBuilder_AddTokens( token_enum, 5, tokens ); + ok( hr == S_OK, "got %08lx\n", hr ); + + count = 0xdeadbeef; + hr = ISpObjectTokenEnumBuilder_Next( token_enum, 5, &out_tokens[0], &count ); + ok( hr == S_FALSE, "got %08lx\n", hr ); + ok( count == 3, "got %lu\n", count ); + + ISpObjectTokenEnumBuilder_Release( token_enum ); + + hr = CoCreateInstance( &CLSID_SpObjectTokenEnum, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectTokenEnumBuilder, (void **)&token_enum ); + ok( hr == S_OK, "got %08lx\n", hr ); + + /* Vendor attribute must contain Vendor1 and Language attribute must contain 407 */ + hr = ISpObjectTokenEnumBuilder_SetAttribs( token_enum, L"Vendor=Vendor1;Language=407", NULL ); + ok( hr == S_OK, "got %08lx\n", hr ); + hr = ISpObjectTokenEnumBuilder_AddTokens( token_enum, 5, tokens ); + ok( hr == S_OK, "got %08lx\n", hr ); + + count = 0xdeadbeef; + hr = ISpObjectTokenEnumBuilder_Next( token_enum, 5, &out_tokens[0], &count ); + ok( hr == S_FALSE, "got %08lx\n", hr ); + ok( count == 1, "got %lu\n", count ); + + hr = CoCreateInstance( &CLSID_SpObjectTokenEnum, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectTokenEnumBuilder, (void **)&token_enum ); + ok( hr == S_OK, "got %08lx\n", hr ); + + hr = ISpObjectTokenEnumBuilder_SetAttribs( token_enum, L"Language=409", + L"Vendor=Vendor1;Age=Child;Gender=Female" ); + ok( hr == S_OK, "got %08lx\n", hr ); + hr = ISpObjectTokenEnumBuilder_AddTokens( token_enum, 5, tokens ); + ok( hr == S_OK, "got %08lx\n", hr ); + + hr = ISpObjectTokenEnumBuilder_Sort( token_enum, NULL ); + ok( hr == S_OK, "got %08lx\n", hr ); + + count = 0xdeadbeef; + hr = ISpObjectTokenEnumBuilder_Next( token_enum, 5, &out_tokens[0], &count ); + ok( hr == S_FALSE, "got %08lx\n", hr ); + ok( count == 3, "got %lu\n", count ); + ok( out_tokens[0] == tokens[2], "got %p\n", out_tokens[0] ); + ok( out_tokens[1] == tokens[1], "got %p\n", out_tokens[1] ); + ok( out_tokens[2] == tokens[0], "got %p\n", out_tokens[2] ); + + ISpObjectTokenEnumBuilder_Release( token_enum ); + + for ( i = 0; i < 5; i++ ) ISpObjectToken_Release( tokens[i] ); } static void test_default_token_id(void) @@ -226,13 +499,117 @@ static void tests_token_voices(void) IEnumSpObjectTokens_Release(tokens); } + +#define TESTCLASS_CLSID L"{67DD26B6-50BA-3297-253E-619346F177F8}" +static const GUID CLSID_TestClass = {0x67DD26B6,0x50BA,0x3297,{0x25,0x3E,0x61,0x93,0x46,0xF1,0x77,0xF8}}; + +static ISpObjectToken *test_class_token; + +static HRESULT WINAPI test_class_QueryInterface(ISpObjectWithToken *iface, REFIID riid, void **ppv) +{ + if (IsEqualGUID( riid, &IID_IUnknown ) || IsEqualGUID( riid, &IID_ISpObjectWithToken )) + { + *ppv = iface; + return S_OK; + } + + *ppv = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI test_class_AddRef(ISpObjectWithToken *iface) +{ + return 2; +} + +static ULONG WINAPI test_class_Release(ISpObjectWithToken *iface) +{ + return 1; +} + +static HRESULT WINAPI test_class_SetObjectToken(ISpObjectWithToken *iface, ISpObjectToken *token) +{ + ok( token != NULL, "token == NULL\n" ); + test_class_token = token; + ISpObjectToken_AddRef(test_class_token); + return S_OK; +} + +static HRESULT WINAPI test_class_GetObjectToken(ISpObjectWithToken *iface, ISpObjectToken **token) +{ + ok( 0, "unexpected call\n" ); + return E_NOTIMPL; +} + +static const ISpObjectWithTokenVtbl test_class_vtbl = { + test_class_QueryInterface, + test_class_AddRef, + test_class_Release, + test_class_SetObjectToken, + test_class_GetObjectToken +}; + +static ISpObjectWithToken test_class = { &test_class_vtbl }; + +static HRESULT WINAPI ClassFactory_QueryInterface(IClassFactory *iface, REFIID riid, void **ppv) +{ + if (IsEqualGUID( &IID_IUnknown, riid ) || IsEqualGUID( &IID_IClassFactory, riid )) + { + *ppv = iface; + return S_OK; + } + + *ppv = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI ClassFactory_AddRef(IClassFactory *iface) +{ + return 2; +} + +static ULONG WINAPI ClassFactory_Release(IClassFactory *iface) +{ + return 1; +} + +static HRESULT WINAPI ClassFactory_CreateInstance(IClassFactory *iface, + IUnknown *pUnkOuter, REFIID riid, void **ppv) +{ + ok( pUnkOuter == NULL, "pUnkOuter != NULL\n" ); + ok( IsEqualGUID(riid, &IID_IUnknown), "riid = %s\n", wine_dbgstr_guid(riid) ); + + *ppv = &test_class; + return S_OK; +} + +static HRESULT WINAPI ClassFactory_LockServer(IClassFactory *iface, BOOL fLock) +{ + ok( 0, "unexpected call\n" ); + return E_NOTIMPL; +} + +static const IClassFactoryVtbl ClassFactoryVtbl = { + ClassFactory_QueryInterface, + ClassFactory_AddRef, + ClassFactory_Release, + ClassFactory_CreateInstance, + ClassFactory_LockServer +}; + +static IClassFactory test_class_cf = { &ClassFactoryVtbl }; + static void test_object_token(void) { + static const WCHAR test_token_id[] = L"HKEY_LOCAL_MACHINE\\Software\\Wine\\Winetest\\sapi\\TestToken"; + ISpObjectToken *token; ISpDataKey *sub_key; HRESULT hr; LPWSTR tempW, token_id; ISpObjectTokenCategory *cat; + DWORD regid; + IUnknown *obj; hr = CoCreateInstance( &CLSID_SpObjectToken, NULL, CLSCTX_INPROC_SERVER, &IID_ISpObjectToken, (void **)&token ); @@ -355,13 +732,65 @@ static void test_object_token(void) ISpObjectTokenCategory_Release( cat ); } + hr = CoRegisterClassObject( &CLSID_TestClass, (IUnknown *)&test_class_cf, + CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, ®id ); + ok( hr == S_OK, "got %08lx\n", hr ); + + ISpObjectToken_Release( token ); + hr = CoCreateInstance( &CLSID_SpObjectToken, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectToken, (void **)&token ); + ok( hr == S_OK, "got %08lx\n", hr ); + + hr = ISpObjectToken_SetId( token, NULL, test_token_id, TRUE ); + ok( hr == S_OK || broken(hr == E_ACCESSDENIED) /* win1064_adm */, "got %08lx\n", hr ); + if (hr == E_ACCESSDENIED) { + win_skip( "token SetId access denied\n" ); + ISpObjectToken_Release( token ); + return; + } + + hr = ISpObjectToken_CreateKey( token, L"Attributes", &sub_key ); + ok( hr == S_OK, "got %08lx\n", hr ); + ISpDataKey_Release( sub_key ); + + hr = ISpObjectToken_SetStringValue( token, L"CLSID", TESTCLASS_CLSID ); + ok( hr == S_OK, "got %08lx\n", hr ); + + tempW = NULL; + hr = ISpObjectToken_GetStringValue( token, L"CLSID", &tempW ); + + ok( hr == S_OK, "got %08lx\n", hr ); + if ( tempW ) { + ok( !wcsncmp( tempW, TESTCLASS_CLSID, wcslen(TESTCLASS_CLSID) ), + "got %s (expected %s)\n", wine_dbgstr_w(tempW), wine_dbgstr_w(TESTCLASS_CLSID) ); + CoTaskMemFree( tempW ); + } + + test_class_token = NULL; + hr = ISpObjectToken_CreateInstance( token, NULL, CLSCTX_INPROC_SERVER, &IID_IUnknown, (void **)&obj ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( test_class_token != NULL, "test_class_token not set\n" ); + + tempW = NULL; + hr = ISpObjectToken_GetId( test_class_token, &tempW ); + ok( tempW != NULL, "got %p\n", tempW ); + if (tempW) { + ok( !wcsncmp(tempW, test_token_id, wcslen(test_token_id)), + "got %s (expected %s)\n", wine_dbgstr_w(tempW), wine_dbgstr_w(test_token_id) ); + CoTaskMemFree( tempW ); + } + + ISpObjectToken_Release( test_class_token ); + IUnknown_Release( obj ); ISpObjectToken_Release( token ); } START_TEST(token) { CoInitialize( NULL ); + RegDeleteTreeA( HKEY_CURRENT_USER, "Software\\Winetest\\sapi" ); test_data_key(); + setup_test_voice_tokens(); test_token_category(); test_token_enum(); test_default_token_id(); diff --git a/dlls/sapi/tests/tts.c b/dlls/sapi/tests/tts.c index 8303dfc6ebc..6912dc08e0d 100644 --- a/dlls/sapi/tests/tts.c +++ b/dlls/sapi/tests/tts.c @@ -34,10 +34,51 @@ static void _expect_ref(IUnknown *obj, ULONG ref, int line) ok_(__FILE__,line)(rc == ref, "Unexpected refcount %ld, expected %ld.\n", rc, ref); } +#define APTTYPE_UNITIALIZED APTTYPE_CURRENT +static struct +{ + APTTYPE type; + APTTYPEQUALIFIER qualifier; +} test_apt_data; + +static DWORD WINAPI test_apt_thread(void *param) +{ + HRESULT hr; + + hr = CoGetApartmentType(&test_apt_data.type, &test_apt_data.qualifier); + if (hr == CO_E_NOTINITIALIZED) + { + test_apt_data.type = APTTYPE_UNITIALIZED; + test_apt_data.qualifier = 0; + } + + return 0; +} + +static void check_apttype(void) +{ + HANDLE thread; + MSG msg; + + memset(&test_apt_data, 0xde, sizeof(test_apt_data)); + + thread = CreateThread(NULL, 0, test_apt_thread, NULL, 0, NULL); + while (MsgWaitForMultipleObjects(1, &thread, FALSE, INFINITE, QS_ALLINPUT) != WAIT_OBJECT_0) + { + while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + CloseHandle(thread); +} + static void test_interfaces(void) { ISpeechVoice *speech_voice, *speech_voice2; IConnectionPointContainer *container; + ISpTTSEngineSite *site; ISpVoice *spvoice, *spvoice2; IDispatch *dispatch; IUnknown *unk; @@ -90,12 +131,568 @@ static void test_interfaces(void) EXPECT_REF(container, 2); IConnectionPointContainer_Release(container); + hr = ISpeechVoice_QueryInterface(speech_voice, &IID_ISpTTSEngineSite, + (void **)&site); + ok(hr == E_NOINTERFACE, "ISpeechVoice_QueryInterface for ISpTTSEngineSite returned: %#lx.\n", hr); + ISpeechVoice_Release(speech_voice); } +#define TESTENGINE_CLSID L"{57C7E6B1-2FC2-4E8E-B968-1410A39E7198}" +static const GUID CLSID_TestEngine = {0x57C7E6B1,0x2FC2,0x4E8E,{0xB9,0x68,0x14,0x10,0xA3,0x9E,0x71,0x98}}; + +struct test_engine +{ + ISpTTSEngine ISpTTSEngine_iface; + ISpObjectWithToken ISpObjectWithToken_iface; + + ISpObjectToken *token; + + BOOL speak_called; + DWORD flags; + GUID fmtid; + SPVTEXTFRAG *frag_list; + LONG rate; + USHORT volume; +}; + +static void copy_frag_list(const SPVTEXTFRAG *frag_list, SPVTEXTFRAG **ret_frag_list) +{ + SPVTEXTFRAG *frag, *prev = NULL; + + if (!frag_list) + { + *ret_frag_list = NULL; + return; + } + + while (frag_list) + { + frag = malloc(sizeof(*frag) + frag_list->ulTextLen * sizeof(WCHAR)); + memcpy(frag, frag_list, sizeof(*frag)); + + if (frag_list->pTextStart) + { + frag->pTextStart = (WCHAR *)(frag + 1); + memcpy(frag + 1, frag_list->pTextStart, frag->ulTextLen * sizeof(WCHAR)); + } + + frag->pNext = NULL; + + if (prev) + prev->pNext = frag; + else + *ret_frag_list = frag; + + prev = frag; + frag_list = frag_list->pNext; + } +} + +static void reset_engine_params(struct test_engine *engine) +{ + SPVTEXTFRAG *frag, *next; + + engine->speak_called = FALSE; + engine->flags = 0xdeadbeef; + memset(&engine->fmtid, 0xde, sizeof(engine->fmtid)); + engine->rate = 0xdeadbeef; + engine->volume = 0xbeef; + + for (frag = engine->frag_list; frag; frag = next) + { + next = frag->pNext; + free(frag); + } + engine->frag_list = NULL; +} + +static inline struct test_engine *impl_from_ISpTTSEngine(ISpTTSEngine *iface) +{ + return CONTAINING_RECORD(iface, struct test_engine, ISpTTSEngine_iface); +} + +static inline struct test_engine *impl_from_ISpObjectWithToken(ISpObjectWithToken *iface) +{ + return CONTAINING_RECORD(iface, struct test_engine, ISpObjectWithToken_iface); +} + +static HRESULT WINAPI test_engine_QueryInterface(ISpTTSEngine *iface, REFIID iid, void **obj) +{ + struct test_engine *engine = impl_from_ISpTTSEngine(iface); + + if (IsEqualIID(iid, &IID_IUnknown) || IsEqualIID(iid, &IID_ISpTTSEngine)) + *obj = &engine->ISpTTSEngine_iface; + else if (IsEqualIID(iid, &IID_ISpObjectWithToken)) + *obj = &engine->ISpObjectWithToken_iface; + else + { + *obj = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown *)*obj); + return S_OK; +} + +static ULONG WINAPI test_engine_AddRef(ISpTTSEngine *iface) +{ + return 2; +} + +static ULONG WINAPI test_engine_Release(ISpTTSEngine *iface) +{ + return 1; +} + +static HRESULT WINAPI test_engine_Speak(ISpTTSEngine *iface, DWORD flags, REFGUID fmtid, + const WAVEFORMATEX *wfx, const SPVTEXTFRAG *frag_list, + ISpTTSEngineSite *site) +{ + struct test_engine *engine = impl_from_ISpTTSEngine(iface); + DWORD actions; + char *buf; + int i; + HRESULT hr; + + engine->flags = flags; + engine->fmtid = *fmtid; + copy_frag_list(frag_list, &engine->frag_list); + engine->speak_called = TRUE; + + actions = ISpTTSEngineSite_GetActions(site); + ok(actions == (SPVES_CONTINUE | SPVES_RATE | SPVES_VOLUME), "got %#lx.\n", actions); + + hr = ISpTTSEngineSite_GetRate(site, &engine->rate); + ok(hr == S_OK, "got %#lx.\n", hr); + actions = ISpTTSEngineSite_GetActions(site); + ok(actions == (SPVES_CONTINUE | SPVES_VOLUME), "got %#lx.\n", actions); + + hr = ISpTTSEngineSite_GetVolume(site, &engine->volume); + ok(hr == S_OK, "got %#lx.\n", hr); + actions = ISpTTSEngineSite_GetActions(site); + ok(actions == SPVES_CONTINUE, "got %#lx.\n", actions); + + buf = calloc(1, 22050 * 2 / 5); + for (i = 0; i < 5; i++) + { + if (ISpTTSEngineSite_GetActions(site) & SPVES_ABORT) + break; + hr = ISpTTSEngineSite_Write(site, buf, 22050 * 2 / 5, NULL); + ok(hr == S_OK || hr == SP_AUDIO_STOPPED, "got %#lx.\n", hr); + Sleep(100); + } + free(buf); + + return S_OK; +} + +static HRESULT WINAPI test_engine_GetOutputFormat(ISpTTSEngine *iface, const GUID *fmtid, + const WAVEFORMATEX *wfx, GUID *out_fmtid, + WAVEFORMATEX **out_wfx) +{ + *out_fmtid = SPDFID_WaveFormatEx; + *out_wfx = CoTaskMemAlloc(sizeof(WAVEFORMATEX)); + (*out_wfx)->wFormatTag = WAVE_FORMAT_PCM; + (*out_wfx)->nChannels = 1; + (*out_wfx)->nSamplesPerSec = 22050; + (*out_wfx)->wBitsPerSample = 16; + (*out_wfx)->nBlockAlign = 2; + (*out_wfx)->nAvgBytesPerSec = 22050 * 2; + (*out_wfx)->cbSize = 0; + + return S_OK; +} + +static ISpTTSEngineVtbl test_engine_vtbl = +{ + test_engine_QueryInterface, + test_engine_AddRef, + test_engine_Release, + test_engine_Speak, + test_engine_GetOutputFormat, +}; + +static HRESULT WINAPI objwithtoken_QueryInterface(ISpObjectWithToken *iface, REFIID iid, void **obj) +{ + struct test_engine *engine = impl_from_ISpObjectWithToken(iface); + + return ISpTTSEngine_QueryInterface(&engine->ISpTTSEngine_iface, iid, obj); +} + +static ULONG WINAPI objwithtoken_AddRef(ISpObjectWithToken *iface) +{ + return 2; +} + +static ULONG WINAPI objwithtoken_Release(ISpObjectWithToken *iface) +{ + return 1; +} + +static HRESULT WINAPI objwithtoken_SetObjectToken(ISpObjectWithToken *iface, ISpObjectToken *token) +{ + struct test_engine *engine = impl_from_ISpObjectWithToken(iface); + + if (!token) + return E_INVALIDARG; + + ISpObjectToken_AddRef(token); + engine->token = token; + + return S_OK; +} + +static HRESULT WINAPI objwithtoken_GetObjectToken(ISpObjectWithToken *iface, ISpObjectToken **token) +{ + struct test_engine *engine = impl_from_ISpObjectWithToken(iface); + + *token = engine->token; + if (*token) + ISpObjectToken_AddRef(*token); + + return S_OK; +} + +static const ISpObjectWithTokenVtbl objwithtoken_vtbl = +{ + objwithtoken_QueryInterface, + objwithtoken_AddRef, + objwithtoken_Release, + objwithtoken_SetObjectToken, + objwithtoken_GetObjectToken +}; + +static struct test_engine test_engine = {{&test_engine_vtbl}, {&objwithtoken_vtbl}}; + +static HRESULT WINAPI ClassFactory_QueryInterface(IClassFactory *iface, REFIID riid, void **ppv) +{ + if (IsEqualGUID(&IID_IUnknown, riid) || IsEqualGUID(&IID_IClassFactory, riid)) + { + *ppv = iface; + return S_OK; + } + + *ppv = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI ClassFactory_AddRef(IClassFactory *iface) +{ + return 2; +} + +static ULONG WINAPI ClassFactory_Release(IClassFactory *iface) +{ + return 1; +} + +static HRESULT WINAPI ClassFactory_CreateInstance(IClassFactory *iface, + IUnknown *pUnkOuter, REFIID riid, void **ppv) +{ + ok(pUnkOuter == NULL, "pUnkOuter != NULL.\n"); + ok(IsEqualGUID(riid, &IID_IUnknown) || IsEqualGUID(riid, &IID_ISpTTSEngine), + "riid = %s.\n", wine_dbgstr_guid(riid)); + + *ppv = &test_engine.ISpTTSEngine_iface; + return S_OK; +} + +static HRESULT WINAPI ClassFactory_LockServer(IClassFactory *iface, BOOL fLock) +{ + ok(0, "unexpected call.\n"); + return E_NOTIMPL; +} + +static const IClassFactoryVtbl ClassFactoryVtbl = { + ClassFactory_QueryInterface, + ClassFactory_AddRef, + ClassFactory_Release, + ClassFactory_CreateInstance, + ClassFactory_LockServer +}; + +static IClassFactory test_engine_cf = { &ClassFactoryVtbl }; + +static void test_spvoice(void) +{ + static const WCHAR test_token_id[] = L"HKEY_LOCAL_MACHINE\\Software\\Wine\\Winetest\\sapi\\tts\\TestEngine"; + static const WCHAR test_text[] = L"Hello! This is a test sentence."; + + ISpVoice *voice; + IUnknown *dummy; + ISpMMSysAudio *audio_out; + ISpObjectTokenCategory *token_cat; + ISpObjectToken *token; + WCHAR *token_id = NULL, *default_token_id = NULL; + ISpDataKey *attrs_key; + LONG rate; + USHORT volume; + ULONG stream_num; + DWORD regid; + DWORD start, duration; + HRESULT hr; + + if (waveOutGetNumDevs() == 0) { + skip("no wave out devices.\n"); + return; + } + + check_apttype(); + ok(test_apt_data.type == APTTYPE_UNITIALIZED, "got apt type %d.\n", test_apt_data.type); + + hr = CoCreateInstance(&CLSID_SpVoice, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpVoice, (void **)&voice); + ok(hr == S_OK, "Failed to create SpVoice: %#lx.\n", hr); + + check_apttype(); + ok(test_apt_data.type == APTTYPE_UNITIALIZED, "got apt type %d.\n", test_apt_data.type); + + /* SpVoice initializes a MTA in SetOutput even if an invalid output object is given. */ + hr = CoCreateInstance(&CLSID_SpDataKey, NULL, CLSCTX_INPROC_SERVER, + &IID_IUnknown, (void **)&dummy); + ok(hr == S_OK, "Failed to create dummy: %#lx.\n", hr); + + hr = ISpVoice_SetOutput(voice, dummy, TRUE); + ok(hr == E_INVALIDARG, "got %#lx.\n", hr); + + check_apttype(); + ok(test_apt_data.type == APTTYPE_MTA || broken(test_apt_data.type == APTTYPE_UNITIALIZED) /* w8, w10v1507 */, + "got apt type %d.\n", test_apt_data.type); + if (test_apt_data.type == APTTYPE_MTA) + ok(test_apt_data.qualifier == APTTYPEQUALIFIER_IMPLICIT_MTA, + "got apt type qualifier %d.\n", test_apt_data.qualifier); + else + win_skip("apt type is not MTA.\n"); + + IUnknown_Release(dummy); + + hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpMMSysAudio, (void **)&audio_out); + ok(hr == S_OK, "Failed to create SpMMAudioOut: %#lx.\n", hr); + + hr = ISpVoice_SetOutput(voice, NULL, TRUE); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpVoice_SetOutput(voice, (IUnknown *)audio_out, TRUE); + todo_wine ok(hr == S_FALSE, "got %#lx.\n", hr); + + hr = ISpVoice_SetVoice(voice, NULL); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + + check_apttype(); + ok(test_apt_data.type == APTTYPE_MTA || broken(test_apt_data.type == APTTYPE_UNITIALIZED) /* w8, w10v1507 */, + "got apt type %d.\n", test_apt_data.type); + if (test_apt_data.type == APTTYPE_MTA) + ok(test_apt_data.qualifier == APTTYPEQUALIFIER_IMPLICIT_MTA, + "got apt type qualifier %d.\n", test_apt_data.qualifier); + else + win_skip("apt type is not MTA.\n"); + + hr = ISpVoice_GetVoice(voice, &token); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + + if (SUCCEEDED(hr)) + { + hr = ISpObjectToken_GetId(token, &token_id); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = CoCreateInstance(&CLSID_SpObjectTokenCategory, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectTokenCategory, (void **)&token_cat); + ok(hr == S_OK, "Failed to create SpObjectTokenCategory: %#lx.\n", hr); + + hr = ISpObjectTokenCategory_SetId(token_cat, SPCAT_VOICES, FALSE); + ok(hr == S_OK, "got %#lx.\n", hr); + hr = ISpObjectTokenCategory_GetDefaultTokenId(token_cat, &default_token_id); + ok(hr == S_OK, "got %#lx.\n", hr); + + ok(!wcscmp(token_id, default_token_id), "token_id != default_token_id\n"); + + CoTaskMemFree(token_id); + CoTaskMemFree(default_token_id); + ISpObjectToken_Release(token); + ISpObjectTokenCategory_Release(token_cat); + } + + rate = 0xdeadbeef; + hr = ISpVoice_GetRate(voice, &rate); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(rate == 0, "rate = %ld\n", rate); + + hr = ISpVoice_SetRate(voice, 1); + ok(hr == S_OK, "got %#lx.\n", hr); + + rate = 0xdeadbeef; + hr = ISpVoice_GetRate(voice, &rate); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(rate == 1, "rate = %ld\n", rate); + + hr = ISpVoice_SetRate(voice, -1000); + ok(hr == S_OK, "got %#lx.\n", hr); + + rate = 0xdeadbeef; + hr = ISpVoice_GetRate(voice, &rate); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(rate == -1000, "rate = %ld\n", rate); + + hr = ISpVoice_SetRate(voice, 1000); + ok(hr == S_OK, "got %#lx.\n", hr); + + rate = 0xdeadbeef; + hr = ISpVoice_GetRate(voice, &rate); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(rate == 1000, "rate = %ld\n", rate); + + volume = 0xbeef; + hr = ISpVoice_GetVolume(voice, &volume); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(volume == 100, "volume = %d\n", volume); + + hr = ISpVoice_SetVolume(voice, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + + volume = 0xbeef; + hr = ISpVoice_GetVolume(voice, &volume); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(volume == 0, "volume = %d\n", volume); + + hr = ISpVoice_SetVolume(voice, 100); + ok(hr == S_OK, "got %#lx.\n", hr); + + volume = 0xbeef; + hr = ISpVoice_GetVolume(voice, &volume); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(volume == 100, "volume = %d\n", volume); + + hr = ISpVoice_SetVolume(voice, 101); + ok(hr == E_INVALIDARG, "got %#lx.\n", hr); + + hr = CoRegisterClassObject(&CLSID_TestEngine, (IUnknown *)&test_engine_cf, + CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, ®id); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = CoCreateInstance(&CLSID_SpObjectToken, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectToken, (void **)&token); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpObjectToken_SetId(token, NULL, test_token_id, TRUE); + ok(hr == S_OK || broken(hr == E_ACCESSDENIED) /* w1064_adm */, "got %#lx.\n", hr); + if (hr == E_ACCESSDENIED) + { + win_skip("token SetId access denied.\n"); + goto done; + } + + ISpObjectToken_SetStringValue(token, L"CLSID", TESTENGINE_CLSID); + hr = ISpObjectToken_CreateKey(token, L"Attributes", &attrs_key); + ok(hr == S_OK, "got %#lx.\n", hr); + ISpDataKey_SetStringValue(attrs_key, L"Language", L"409"); + ISpDataKey_Release(attrs_key); + + hr = ISpVoice_SetVoice(voice, token); + ok(hr == S_OK, "got %#lx.\n", hr); + + check_apttype(); + ok(test_apt_data.type == APTTYPE_MTA || broken(test_apt_data.type == APTTYPE_UNITIALIZED) /* w8, w10v1507 */, + "got apt type %d.\n", test_apt_data.type); + if (test_apt_data.type == APTTYPE_MTA) + ok(test_apt_data.qualifier == APTTYPEQUALIFIER_IMPLICIT_MTA, + "got apt type qualifier %d.\n", test_apt_data.qualifier); + else + win_skip("apt type is not MTA.\n"); + + test_engine.speak_called = FALSE; + hr = ISpVoice_Speak(voice, NULL, SPF_PURGEBEFORESPEAK, NULL); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(!test_engine.speak_called, "ISpTTSEngine::Speak was called.\n"); + + stream_num = 0xdeadbeef; + hr = ISpVoice_Speak(voice, NULL, SPF_PURGEBEFORESPEAK, &stream_num); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(stream_num == 0xdeadbeef, "got %lu.\n", stream_num); + + ISpVoice_SetRate(voice, 0); + ISpVoice_SetVolume(voice, 100); + + reset_engine_params(&test_engine); + stream_num = 0xdeadbeef; + start = GetTickCount(); + hr = ISpVoice_Speak(voice, test_text, SPF_DEFAULT, &stream_num); + duration = GetTickCount() - start; + ok(hr == S_OK, "got %#lx.\n", hr); + ok(test_engine.speak_called, "ISpTTSEngine::Speak was not called.\n"); + ok(test_engine.flags == SPF_DEFAULT, "got %#lx.\n", test_engine.flags); + ok(test_engine.frag_list != NULL, "frag_list is NULL.\n"); + ok(test_engine.frag_list->pNext == NULL, "frag_list->pNext != NULL.\n"); + ok(test_engine.frag_list->ulTextLen == wcslen(test_text), "got %lu.\n", test_engine.frag_list->ulTextLen); + ok(!wcsncmp(test_text, test_engine.frag_list->pTextStart, wcslen(test_text)), + "got %s.\n", wine_dbgstr_w(test_engine.frag_list->pTextStart)); + ok(test_engine.rate == 0, "got %ld.\n", test_engine.rate); + ok(test_engine.volume == 100, "got %d.\n", test_engine.volume); + ok(stream_num == 1, "got %lu.\n", stream_num); + ok(duration > 800 && duration < 3500, "took %lu ms.\n", duration); + + check_apttype(); + ok(test_apt_data.type == APTTYPE_MTA, "got apt type %d.\n", test_apt_data.type); + ok(test_apt_data.qualifier == APTTYPEQUALIFIER_IMPLICIT_MTA, + "got apt type qualifier %d.\n", test_apt_data.qualifier); + + start = GetTickCount(); + hr = ISpVoice_WaitUntilDone(voice, INFINITE); + duration = GetTickCount() - start; + ok(hr == S_OK, "got %#lx.\n", hr); + ok(duration < 200, "took %lu ms.\n", duration); + + reset_engine_params(&test_engine); + stream_num = 0xdeadbeef; + start = GetTickCount(); + hr = ISpVoice_Speak(voice, test_text, SPF_DEFAULT | SPF_ASYNC | SPF_NLP_SPEAK_PUNC, &stream_num); + duration = GetTickCount() - start; + ok(hr == S_OK, "got %#lx.\n", hr); + todo_wine ok(stream_num == 1, "got %lu.\n", stream_num); + ok(duration < 500, "took %lu ms.\n", duration); + + hr = ISpVoice_WaitUntilDone(voice, 100); + ok(hr == S_FALSE, "got %#lx.\n", hr); + + hr = ISpVoice_WaitUntilDone(voice, INFINITE); + duration = GetTickCount() - start; + ok(hr == S_OK, "got %#lx.\n", hr); + ok(duration > 800 && duration < 3500, "took %lu ms.\n", duration); + + ok(test_engine.speak_called, "ISpTTSEngine::Speak was not called.\n"); + ok(test_engine.flags == SPF_NLP_SPEAK_PUNC, "got %#lx.\n", test_engine.flags); + ok(test_engine.frag_list != NULL, "frag_list is NULL.\n"); + ok(test_engine.frag_list->pNext == NULL, "frag_list->pNext != NULL.\n"); + ok(test_engine.frag_list->ulTextLen == wcslen(test_text), "got %lu.\n", test_engine.frag_list->ulTextLen); + ok(!wcsncmp(test_text, test_engine.frag_list->pTextStart, wcslen(test_text)), + "got %s.\n", wine_dbgstr_w(test_engine.frag_list->pTextStart)); + ok(test_engine.rate == 0, "got %ld.\n", test_engine.rate); + ok(test_engine.volume == 100, "got %d.\n", test_engine.volume); + + reset_engine_params(&test_engine); + hr = ISpVoice_Speak(voice, test_text, SPF_DEFAULT | SPF_ASYNC, NULL); + ok(hr == S_OK, "got %#lx.\n", hr); + + Sleep(200); + start = GetTickCount(); + hr = ISpVoice_Speak(voice, NULL, SPF_PURGEBEFORESPEAK, NULL); + duration = GetTickCount() - start; + ok(hr == S_OK, "got %#lx.\n", hr); + ok(duration < 300, "took %lu ms.\n", duration); + +done: + reset_engine_params(&test_engine); + ISpVoice_Release(voice); + ISpObjectToken_Release(token); + ISpMMSysAudio_Release(audio_out); +} + START_TEST(tts) { CoInitialize(NULL); + /* Run spvoice tests before interface tests so that a MTA won't be created before this test is run. */ + test_spvoice(); test_interfaces(); CoUninitialize(); } diff --git a/dlls/sapi/token.c b/dlls/sapi/token.c index 4e44ba8a354..f599bdb6b14 100644 --- a/dlls/sapi/token.c +++ b/dlls/sapi/token.c @@ -19,6 +19,7 @@ */ #include +#include #define COBJMACROS @@ -40,7 +41,6 @@ struct data_key LONG ref; HKEY key; - BOOL read_only; }; static struct data_key *impl_from_ISpRegDataKey( ISpRegDataKey *iface ) @@ -53,7 +53,7 @@ struct object_token ISpObjectToken ISpObjectToken_iface; LONG ref; - HKEY token_key; + ISpRegDataKey *data_key; WCHAR *token_id; }; @@ -124,8 +124,18 @@ static HRESULT WINAPI data_key_GetData( ISpRegDataKey *iface, LPCWSTR name, static HRESULT WINAPI data_key_SetStringValue( ISpRegDataKey *iface, LPCWSTR name, LPCWSTR value ) { - FIXME( "stub\n" ); - return E_NOTIMPL; + struct data_key *This = impl_from_ISpRegDataKey( iface ); + DWORD ret, size; + + TRACE( "%p, %s, %s\n", This, debugstr_w(name), debugstr_w(value) ); + + if (!This->key) + return E_HANDLE; + + size = (wcslen(value) + 1) * sizeof(WCHAR); + ret = RegSetValueExW( This->key, name, 0, REG_SZ, (BYTE *)value, size ); + + return HRESULT_FROM_WIN32(ret); } static HRESULT WINAPI data_key_GetStringValue( ISpRegDataKey *iface, @@ -177,8 +187,37 @@ static HRESULT WINAPI data_key_GetDWORD( ISpRegDataKey *iface, static HRESULT WINAPI data_key_OpenKey( ISpRegDataKey *iface, LPCWSTR name, ISpDataKey **sub_key ) { - FIXME( "stub\n" ); - return E_NOTIMPL; + struct data_key *This = impl_from_ISpRegDataKey( iface ); + ISpRegDataKey *spregkey; + HRESULT hr; + HKEY key; + LONG ret; + + TRACE( "%p, %s, %p\n", This, debugstr_w(name), sub_key ); + + ret = RegOpenKeyExW( This->key, name, 0, KEY_ALL_ACCESS, &key ); + if (ret != ERROR_SUCCESS) + return SPERR_NOT_FOUND; + + hr = data_key_create( NULL, &IID_ISpRegDataKey, (void**)&spregkey ); + if (FAILED(hr)) + { + RegCloseKey( key ); + return hr; + } + + hr = ISpRegDataKey_SetKey( spregkey, key, FALSE ); + if (FAILED(hr)) + { + RegCloseKey( key ); + ISpRegDataKey_Release( spregkey ); + return hr; + } + + hr = ISpRegDataKey_QueryInterface( spregkey, &IID_ISpDataKey, (void**)sub_key ); + ISpRegDataKey_Release( spregkey ); + + return hr; } static HRESULT WINAPI data_key_CreateKey( ISpRegDataKey *iface, @@ -243,8 +282,8 @@ static HRESULT WINAPI data_key_SetKey( ISpRegDataKey *iface, if (This->key) return SPERR_ALREADY_INITIALIZED; + /* read_only is ignored in Windows implementations. */ This->key = key; - This->read_only = read_only; return S_OK; } @@ -277,7 +316,6 @@ HRESULT data_key_create( IUnknown *outer, REFIID iid, void **obj ) This->ISpRegDataKey_iface.lpVtbl = &data_key_vtbl; This->ref = 1; This->key = NULL; - This->read_only = FALSE; hr = ISpRegDataKey_QueryInterface( &This->ISpRegDataKey_iface, iid, obj ); @@ -291,6 +329,7 @@ struct token_category LONG ref; ISpRegDataKey *data_key; + WCHAR *id; }; static struct token_category *impl_from_ISpObjectTokenCategory( ISpObjectTokenCategory *iface ) @@ -338,6 +377,7 @@ static ULONG WINAPI token_category_Release( ISpObjectTokenCategory *iface ) if (!ref) { if (This->data_key) ISpRegDataKey_Release( This->data_key ); + free( This->id ); free( This ); } return ref; @@ -459,6 +499,23 @@ static HRESULT parse_cat_id( const WCHAR *str, HKEY *root, const WCHAR **sub_key return S_FALSE; } +static HRESULT WINAPI create_data_key_with_hkey( HKEY key, ISpRegDataKey **data_key ) +{ + HRESULT hr; + + if (FAILED(hr = CoCreateInstance( &CLSID_SpDataKey, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpRegDataKey, (void **)data_key ) )) + return hr; + + if (FAILED(hr = ISpRegDataKey_SetKey( *data_key, key, TRUE ))) + { + ISpRegDataKey_Release( *data_key ); + *data_key = NULL; + } + + return hr; +} + static HRESULT WINAPI token_category_SetId( ISpObjectTokenCategory *iface, LPCWSTR id, BOOL create ) { @@ -481,17 +538,14 @@ static HRESULT WINAPI token_category_SetId( ISpObjectTokenCategory *iface, res = RegOpenKeyExW( root, subkey, 0, KEY_ALL_ACCESS, &key ); if (res) return SPERR_INVALID_REGISTRY_KEY; - hr = CoCreateInstance( &CLSID_SpDataKey, NULL, CLSCTX_ALL, - &IID_ISpRegDataKey, (void **)&This->data_key ); - if (FAILED(hr)) goto fail; - - hr = ISpRegDataKey_SetKey( This->data_key, key, FALSE ); - if (FAILED(hr)) goto fail; + if (FAILED(hr = create_data_key_with_hkey( key, &This->data_key ))) + { + RegCloseKey( key ); + return hr; + } - return hr; + This->id = wcsdup( id ); -fail: - RegCloseKey( key ); return hr; } @@ -510,6 +564,12 @@ static HRESULT WINAPI token_category_GetDataKey( ISpObjectTokenCategory *iface, return E_NOTIMPL; } +struct token_with_score +{ + ISpObjectToken *token; + uint64_t score; +}; + struct token_enum { ISpObjectTokenEnumBuilder ISpObjectTokenEnumBuilder_iface; @@ -517,8 +577,8 @@ struct token_enum BOOL init; WCHAR *req, *opt; - ULONG count; - HKEY key; + struct token_with_score *tokens; + ULONG capacity, count; DWORD index; }; @@ -533,8 +593,12 @@ static HRESULT WINAPI token_category_EnumTokens( ISpObjectTokenCategory *iface, { struct token_category *This = impl_from_ISpObjectTokenCategory( iface ); ISpObjectTokenEnumBuilder *builder; - struct token_enum *tokenenum; struct data_key *this_data_key; + HKEY tokens_key; + DWORD count, max_subkey_size, root_len, token_id_size; + DWORD size, i; + WCHAR *token_id = NULL; + ISpObjectToken *token = NULL; HRESULT hr; TRACE( "(%p)->(%s %s %p)\n", This, debugstr_w( req ), debugstr_w( opt ), enum_tokens ); @@ -550,12 +614,43 @@ static HRESULT WINAPI token_category_EnumTokens( ISpObjectTokenCategory *iface, this_data_key = impl_from_ISpRegDataKey( This->data_key ); - tokenenum = impl_from_ISpObjectTokenEnumBuilder( builder ); - - if (!RegOpenKeyExW( this_data_key->key, L"Tokens", 0, KEY_ALL_ACCESS, &tokenenum->key )) + if (!RegOpenKeyExW( this_data_key->key, L"Tokens", 0, KEY_ALL_ACCESS, &tokens_key )) { - RegQueryInfoKeyW(tokenenum->key, NULL, NULL, NULL, &tokenenum->count, NULL, NULL, - NULL, NULL, NULL, NULL, NULL); + RegQueryInfoKeyW( tokens_key, NULL, NULL, NULL, &count, &max_subkey_size, NULL, + NULL, NULL, NULL, NULL, NULL ); + max_subkey_size++; + + root_len = wcslen( This->id ); + token_id_size = root_len + sizeof("\\Tokens\\") + max_subkey_size; + token_id = malloc( token_id_size * sizeof(WCHAR) ); + if (!token_id) + { + hr = E_OUTOFMEMORY; + goto fail; + } + root_len = swprintf( token_id, token_id_size, L"%ls%lsTokens\\", + This->id, This->id[root_len - 1] == L'\\' ? L"" : L"\\" ); + + for ( i = 0; i < count; i++ ) + { + size = max_subkey_size; + hr = HRESULT_FROM_WIN32(RegEnumKeyExW( tokens_key, i, token_id + root_len, &size, NULL, NULL, NULL, NULL )); + if (FAILED(hr)) goto fail; + + hr = token_create( NULL, &IID_ISpObjectToken, (void **)&token ); + if (FAILED(hr)) goto fail; + + hr = ISpObjectToken_SetId( token, NULL, token_id, FALSE ); + if (FAILED(hr)) goto fail; + + hr = ISpObjectTokenEnumBuilder_AddTokens( builder, 1, &token ); + if (FAILED(hr)) goto fail; + ISpObjectToken_Release( token ); + token = NULL; + } + + hr = ISpObjectTokenEnumBuilder_Sort( builder, NULL ); + if (FAILED(hr)) goto fail; } hr = ISpObjectTokenEnumBuilder_QueryInterface( builder, &IID_IEnumSpObjectTokens, @@ -563,6 +658,8 @@ static HRESULT WINAPI token_category_EnumTokens( ISpObjectTokenCategory *iface, fail: ISpObjectTokenEnumBuilder_Release( builder ); + if ( token ) ISpObjectToken_Release( token ); + free( token_id ); return hr; } @@ -644,6 +741,7 @@ HRESULT token_category_create( IUnknown *outer, REFIID iid, void **obj ) This->ISpObjectTokenCategory_iface.lpVtbl = &token_category_vtbl; This->ref = 1; This->data_key = NULL; + This->id = NULL; hr = ISpObjectTokenCategory_QueryInterface( &This->ISpObjectTokenCategory_iface, iid, obj ); @@ -690,10 +788,16 @@ static ULONG WINAPI token_enum_Release( ISpObjectTokenEnumBuilder *iface ) if (!ref) { - if (This->key) - RegCloseKey(This->key); free( This->req ); free( This->opt ); + if (This->tokens) + { + ULONG i; + for ( i = 0; i < This->count; i++ ) + if ( This->tokens[i].token ) + ISpObjectToken_Release( This->tokens[i].token ); + free( This->tokens ); + } free( This ); } @@ -705,54 +809,23 @@ static HRESULT WINAPI token_enum_Next( ISpObjectTokenEnumBuilder *iface, ULONG *fetched ) { struct token_enum *This = impl_from_ISpObjectTokenEnumBuilder( iface ); - struct object_token *object; - HRESULT hr; - DWORD retCode; - WCHAR *subkey_name; - HKEY sub_key; - DWORD size; + ULONG i; TRACE( "(%p)->(%lu %p %p)\n", This, num, tokens, fetched ); if (!This->init) return SPERR_UNINITIALIZED; - if (fetched) *fetched = 0; - - *tokens = NULL; - - RegQueryInfoKeyW( This->key, NULL, NULL, NULL, NULL, &size, NULL, NULL, NULL, NULL, NULL, NULL ); - size = (size+1) * sizeof(WCHAR); - subkey_name = malloc( size ); - if (!subkey_name) - return E_OUTOFMEMORY; - - retCode = RegEnumKeyExW( This->key, This->index, subkey_name, &size, NULL, NULL, NULL, NULL ); - if (retCode != ERROR_SUCCESS) - { - free( subkey_name ); - return S_FALSE; - } - - This->index++; + if (!fetched && num != 1) return E_POINTER; + if (!tokens) return E_POINTER; - if (RegOpenKeyExW( This->key, subkey_name, 0, KEY_READ, &sub_key ) != ERROR_SUCCESS) + for ( i = 0; i < num && This->index < This->count; i++, This->index++ ) { - free( subkey_name ); - return E_FAIL; + ISpObjectToken_AddRef( This->tokens[This->index].token ); + tokens[i] = This->tokens[This->index].token; } - hr = token_create( NULL, &IID_ISpObjectToken, (void**)tokens ); - if (FAILED(hr)) - { - free( subkey_name ); - return hr; - } + if (fetched) *fetched = i; - object = impl_from_ISpObjectToken( *tokens ); - object->token_key = sub_key; - object->token_id = subkey_name; - - if (fetched) *fetched = 1; - return hr; + return i == num ? S_OK : S_FALSE; } static HRESULT WINAPI token_enum_Skip( ISpObjectTokenEnumBuilder *iface, @@ -779,53 +852,18 @@ static HRESULT WINAPI token_enum_Item( ISpObjectTokenEnumBuilder *iface, ULONG index, ISpObjectToken **token ) { struct token_enum *This = impl_from_ISpObjectTokenEnumBuilder( iface ); - struct object_token *object; - ISpObjectToken *subtoken; - HRESULT hr; - WCHAR *subkey; - DWORD size; - LONG ret; - HKEY key; - TRACE( "%p, %lu, %p\n", This, index, token ); + TRACE( "(%p)->(%lu %p)\n", This, index, token ); - if (!This->init) - return SPERR_UNINITIALIZED; - - RegQueryInfoKeyW(This->key, NULL, NULL, NULL, NULL, &size, NULL, NULL, NULL, NULL, NULL, NULL); - size = (size+1) * sizeof(WCHAR); - subkey = malloc( size ); - if (!subkey) - return E_OUTOFMEMORY; - - ret = RegEnumKeyExW(This->key, index, subkey, &size, NULL, NULL, NULL, NULL); - if (ret != ERROR_SUCCESS) - { - free( subkey ); - return HRESULT_FROM_WIN32(ret); - } - - ret = RegOpenKeyExW (This->key, subkey, 0, KEY_READ, &key); - if (ret != ERROR_SUCCESS) - { - free( subkey ); - return HRESULT_FROM_WIN32(ret); - } - - hr = token_create( NULL, &IID_ISpObjectToken, (void**)&subtoken ); - if (FAILED(hr)) - { - free( subkey ); - return hr; - } + if (!This->init) return SPERR_UNINITIALIZED; - object = impl_from_ISpObjectToken( subtoken ); - object->token_key = key; - object->token_id = subkey; + if (!token) return E_POINTER; + if (index >= This->count) return SPERR_NO_MORE_ITEMS; - *token = subtoken; + ISpObjectToken_AddRef( This->tokens[index].token ); + *token = This->tokens[index].token; - return hr; + return S_OK; } static HRESULT WINAPI token_enum_GetCount( ISpObjectTokenEnumBuilder *iface, @@ -870,11 +908,161 @@ static HRESULT WINAPI token_enum_SetAttribs( ISpObjectTokenEnumBuilder *iface, return E_OUTOFMEMORY; } +static HRESULT score_attributes( ISpObjectToken *token, const WCHAR *attrs, + BOOL match_all, uint64_t *score ) +{ + ISpDataKey *attrs_key; + WCHAR *attr, *attr_ctx, *buf; + BOOL match[64]; + unsigned int i, j; + HRESULT hr; + + if (!attrs) + { + *score = 1; + return S_OK; + } + *score = 0; + + if (FAILED(hr = ISpObjectToken_OpenKey( token, L"Attributes", &attrs_key ))) + return hr == SPERR_NOT_FOUND ? S_OK : hr; + + memset( match, 0, sizeof(match) ); + + /* attrs is a semicolon-separated list of attribute clauses. + * Each clause consists of an attribute name and an optional operator and value. + * The meaning of a clause depends on the operator given: + * If no operator is given, the attribute must exist. + * If the operator is '=', the attribute must contain the given value. + * If the operator is '!=', the attribute must not exist or contain the given value. + */ + if (!(buf = wcsdup( attrs ))) return E_OUTOFMEMORY; + for ( attr = wcstok_s( buf, L";", &attr_ctx ), i = 0; attr && i < 64; + attr = wcstok_s( NULL, L";", &attr_ctx ), i++ ) + { + WCHAR *p = wcspbrk( attr, L"!=" ); + WCHAR op = p ? *p : L'\0'; + WCHAR *value = NULL, *res; + if ( p ) + { + if ( op == L'=' ) + value = p + 1; + else if ( op == L'!' ) + { + if ( *(p + 1) != L'=' ) + { + WARN( "invalid attr operator '!%lc'.\n", *(p + 1) ); + hr = E_INVALIDARG; + goto done; + } + value = p + 2; + } + *p = L'\0'; + } + + hr = ISpDataKey_GetStringValue( attrs_key, attr, &res ); + if ( p ) *p = op; + if (SUCCEEDED(hr)) + { + if ( !op ) + match[i] = TRUE; + else + { + WCHAR *val, *val_ctx; + + match[i] = FALSE; + for ( val = wcstok_s( res, L";", &val_ctx ); val && !match[i]; + val = wcstok_s( NULL, L";", &val_ctx ) ) + match[i] = !wcscmp( val, value ); + + if (op == L'!') match[i] = !match[i]; + } + CoTaskMemFree( res ); + } + else if (hr == SPERR_NOT_FOUND) + { + hr = S_OK; + if (op == L'!') match[i] = TRUE; + } + else + goto done; + + if ( match_all && !match[i] ) + goto done; + } + + if ( attr ) + hr = E_INVALIDARG; + else + { + /* Attributes in attrs are ordered from highest to lowest priority. */ + for ( j = 0; j < i; j++ ) + if ( match[j] ) + *score |= 1ULL << (i - 1 - j); + } + +done: + free( buf ); + return hr; +} + +static BOOL grow_tokens_array( struct token_enum *This ) +{ + struct token_with_score *new_tokens; + ULONG new_cap; + + if (This->count < This->capacity) return TRUE; + + if (This->capacity > 0) + { + new_cap = This->capacity * 2; + new_tokens = realloc( This->tokens, new_cap * sizeof(*new_tokens) ); + } + else + { + new_cap = 1; + new_tokens = malloc( sizeof(*new_tokens) ); + } + + if (!new_tokens) return FALSE; + + This->tokens = new_tokens; + This->capacity = new_cap; + return TRUE; +} + static HRESULT WINAPI token_enum_AddTokens( ISpObjectTokenEnumBuilder *iface, ULONG num, ISpObjectToken **tokens ) { - FIXME( "stub\n" ); - return E_NOTIMPL; + struct token_enum *This = impl_from_ISpObjectTokenEnumBuilder( iface ); + ULONG i; + uint64_t score; + HRESULT hr; + + TRACE( "(%p)->(%lu %p)\n", iface, num, tokens ); + + if (!This->init) return SPERR_UNINITIALIZED; + if (!tokens) return E_POINTER; + + for ( i = 0; i < num; i++ ) + { + if (!tokens[i]) return E_POINTER; + + hr = score_attributes( tokens[i], This->req, TRUE, &score ); + if (FAILED(hr)) return hr; + if (!score) continue; + + hr = score_attributes( tokens[i], This->opt, FALSE, &score ); + if (FAILED(hr)) return hr; + + if (!grow_tokens_array( This )) return E_OUTOFMEMORY; + ISpObjectToken_AddRef( tokens[i] ); + This->tokens[This->count].token = tokens[i]; + This->tokens[This->count].score = score; + This->count++; + } + + return S_OK; } static HRESULT WINAPI token_enum_AddTokensFromDataKey( ISpObjectTokenEnumBuilder *iface, @@ -892,11 +1080,35 @@ static HRESULT WINAPI token_enum_AddTokensFromTokenEnum( ISpObjectTokenEnumBuild return E_NOTIMPL; } +static int __cdecl token_with_score_cmp( const void *a, const void *b ) +{ + const struct token_with_score *ta = a, *tb = b; + + if (ta->score > tb->score) return -1; + else if (ta->score < tb->score) return 1; + else return 0; +} + static HRESULT WINAPI token_enum_Sort( ISpObjectTokenEnumBuilder *iface, LPCWSTR first ) { - FIXME( "stub\n" ); - return E_NOTIMPL; + struct token_enum *This = impl_from_ISpObjectTokenEnumBuilder( iface ); + + TRACE( "(%p)->(%s).\n", iface, debugstr_w(first) ); + + if (!This->init) return SPERR_UNINITIALIZED; + if (!This->tokens) return S_OK; + + if (first) + { + FIXME( "first != NULL is not implemented.\n" ); + return E_NOTIMPL; + } + + if (This->opt) + qsort( This->tokens, This->count, sizeof(*This->tokens), token_with_score_cmp ); + + return S_OK; } const struct ISpObjectTokenEnumBuilderVtbl token_enum_vtbl = @@ -928,8 +1140,8 @@ HRESULT token_enum_create( IUnknown *outer, REFIID iid, void **obj ) This->req = NULL; This->opt = NULL; This->init = FALSE; - This->count = 0; - This->key = NULL; + This->tokens = NULL; + This->capacity = This->count = 0; This->index = 0; hr = ISpObjectTokenEnumBuilder_QueryInterface( &This->ISpObjectTokenEnumBuilder_iface, iid, obj ); @@ -977,7 +1189,7 @@ static ULONG WINAPI token_Release( ISpObjectToken *iface ) if (!ref) { - if (This->token_key) RegCloseKey( This->token_key ); + if (This->data_key) ISpRegDataKey_Release( This->data_key ); free( This->token_id ); free( This ); } @@ -1004,15 +1216,21 @@ static HRESULT WINAPI token_GetData( ISpObjectToken *iface, static HRESULT WINAPI token_SetStringValue( ISpObjectToken *iface, LPCWSTR name, LPCWSTR value ) { - FIXME( "stub\n" ); - return E_NOTIMPL; + struct object_token *This = impl_from_ISpObjectToken( iface ); + + TRACE( "%p, %s, %s\n", This, debugstr_w(name), debugstr_w(value) ); + + return ISpRegDataKey_SetStringValue( This->data_key, name, value ); } static HRESULT WINAPI token_GetStringValue( ISpObjectToken *iface, LPCWSTR name, LPWSTR *value ) { - FIXME( "stub\n" ); - return E_NOTIMPL; + struct object_token *This = impl_from_ISpObjectToken( iface ); + + TRACE( "%p, %s, %p\n", This, debugstr_w(name), value ); + + return ISpRegDataKey_GetStringValue( This->data_key, name, value ); } static HRESULT WINAPI token_SetDWORD( ISpObjectToken *iface, @@ -1033,43 +1251,20 @@ static HRESULT WINAPI token_OpenKey( ISpObjectToken *iface, LPCWSTR name, ISpDataKey **sub_key ) { struct object_token *This = impl_from_ISpObjectToken( iface ); - ISpRegDataKey *spregkey; - HRESULT hr; - HKEY key; - LONG ret; TRACE( "%p, %s, %p\n", This, debugstr_w(name), sub_key ); - ret = RegOpenKeyExW (This->token_key, name, 0, KEY_ALL_ACCESS, &key); - if (ret != ERROR_SUCCESS) - return SPERR_NOT_FOUND; - - hr = data_key_create(NULL, &IID_ISpRegDataKey, (void**)&spregkey); - if (FAILED(hr)) - { - RegCloseKey(key); - return hr; - } - - hr = ISpRegDataKey_SetKey(spregkey, key, FALSE); - if (FAILED(hr)) - { - RegCloseKey(key); - ISpRegDataKey_Release(spregkey); - return hr; - } - - hr = ISpRegDataKey_QueryInterface(spregkey, &IID_ISpDataKey, (void**)sub_key); - ISpRegDataKey_Release(spregkey); - - return hr; + return ISpRegDataKey_OpenKey( This->data_key, name, sub_key ); } static HRESULT WINAPI token_CreateKey( ISpObjectToken *iface, LPCWSTR name, ISpDataKey **sub_key ) { - FIXME( "stub\n" ); - return E_NOTIMPL; + struct object_token *This = impl_from_ISpObjectToken( iface ); + + TRACE( "%p, %s, %p\n", iface, debugstr_w(name), sub_key ); + + return ISpRegDataKey_CreateKey( This->data_key, name, sub_key ); } static HRESULT WINAPI token_DeleteKey( ISpObjectToken *iface, @@ -1110,10 +1305,10 @@ static HRESULT WINAPI token_SetId( ISpObjectToken *iface, HKEY root, key; const WCHAR *subkey; - FIXME( "(%p)->(%s %s %d): semi-stub\n", This, debugstr_w( category_id ), + TRACE( "(%p)->(%s %s %d)\n", This, debugstr_w( category_id ), debugstr_w(token_id), create ); - if (This->token_key) return SPERR_ALREADY_INITIALIZED; + if (This->data_key) return SPERR_ALREADY_INITIALIZED; if (!token_id) return E_POINTER; @@ -1126,7 +1321,13 @@ static HRESULT WINAPI token_SetId( ISpObjectToken *iface, res = RegOpenKeyExW( root, subkey, 0, KEY_ALL_ACCESS, &key ); if (res) return SPERR_NOT_FOUND; - This->token_key = key; + hr = create_data_key_with_hkey( key, &This->data_key ); + if (FAILED(hr)) + { + RegCloseKey( key ); + return hr; + } + This->token_id = wcsdup(token_id); return S_OK; @@ -1139,7 +1340,7 @@ static HRESULT WINAPI token_GetId( ISpObjectToken *iface, TRACE( "%p, %p\n", This, token_id); - if (!This->token_key) + if (!This->data_key) return SPERR_UNINITIALIZED; if (!token_id) @@ -1166,291 +1367,45 @@ static HRESULT WINAPI token_GetCategory( ISpObjectToken *iface, return E_NOTIMPL; } -struct speech_audio -{ - ISpAudio ISpAudio_iface; - LONG ref; -}; - -static inline struct speech_audio *impl_from_ISpAudio(ISpAudio *iface) -{ - return CONTAINING_RECORD(iface, struct speech_audio, ISpAudio_iface); -} - -static HRESULT WINAPI spaudio_QueryInterface(ISpAudio *iface, REFIID iid, void **obj) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - - TRACE("(%p, %s %p).\n", audio, debugstr_guid(iid), obj); - - if (IsEqualIID(iid, &IID_IUnknown) || - IsEqualIID(iid, &IID_ISequentialStream) || - IsEqualIID(iid, &IID_IStream) || - IsEqualIID(iid, &IID_ISpStreamFormat) || - IsEqualIID(iid, &IID_ISpAudio)) - *obj = &audio->ISpAudio_iface; - else - { - *obj = NULL; - FIXME("interface %s not implemented.\n", debugstr_guid(iid)); - return E_NOINTERFACE; - } - - IUnknown_AddRef((IUnknown *)*obj); - return S_OK; -} - -static ULONG WINAPI spaudio_AddRef(ISpAudio *iface) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - ULONG ref = InterlockedIncrement(&audio->ref); - - TRACE("(%p): ref=%lu.\n", audio, ref); - - return ref; -} - -static ULONG WINAPI spaudio_Release(ISpAudio *iface) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - ULONG ref = InterlockedDecrement(&audio->ref); - - TRACE("(%p): ref=%lu.\n", audio, ref); - - if (!ref) - { - heap_free(audio); - } - - return ref; -} - -static HRESULT WINAPI spaudio_Read(ISpAudio *iface,void *pv, ULONG cb, ULONG *pcbRead) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - - FIXME("%p, %p, %ld %p\n", audio, pv, cb, pcbRead); - - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_Write(ISpAudio *iface, const void *pv, ULONG cb, ULONG *pcbWritten) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - - FIXME("%p, %p, %ld %p\n", audio, pv, cb, pcbWritten); - - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_Seek(ISpAudio *iface, LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("%p, %s, %ld, %p\n", audio, wine_dbgstr_longlong(dlibMove.QuadPart), dwOrigin, plibNewPosition); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_SetSize(ISpAudio *iface, ULARGE_INTEGER libNewSize) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("(%p, %s)\n", audio, wine_dbgstr_longlong(libNewSize.QuadPart)); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_CopyTo(ISpAudio *iface,IStream *pstm, ULARGE_INTEGER cb, - ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("(%p, %p, %s, %p, %p)\n", audio, pstm, wine_dbgstr_longlong(cb.QuadPart), pcbRead, pcbWritten); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_Commit(ISpAudio *iface,DWORD grfCommitFlags) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("(%p, %#lx)\n", audio, grfCommitFlags); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_Revert(ISpAudio *iface) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("(%p)\n", audio); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_LockRegion(ISpAudio *iface, ULARGE_INTEGER offset, ULARGE_INTEGER cb, DWORD dwLockType) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("(%p, %s, %s, %ld)\n", audio, wine_dbgstr_longlong(offset.QuadPart), - wine_dbgstr_longlong(cb.QuadPart), dwLockType); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_UnlockRegion(ISpAudio *iface,ULARGE_INTEGER offset, ULARGE_INTEGER cb, DWORD dwLockType) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("(%p, %s, %s, %ld)\n", audio, wine_dbgstr_longlong(offset.QuadPart), - wine_dbgstr_longlong(cb.QuadPart), dwLockType); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_Stat(ISpAudio *iface, STATSTG *stg, DWORD flag) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("%p, %p, %ld\n", audio, stg, flag); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_Clone(ISpAudio *iface, IStream **ppstm) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("%p, %p\n", audio, ppstm); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_GetFormat(ISpAudio *iface, GUID *format, WAVEFORMATEX **wave) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("%p, %p, %p\n", audio, format, wave); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_SetState(ISpAudio *iface, SPAUDIOSTATE state, ULONGLONG reserved) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("%p, %d, %s\n", audio, state, wine_dbgstr_longlong(reserved)); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_SetFormat(ISpAudio *iface, REFGUID guid, const WAVEFORMATEX *wave) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("%p, %s, %p\n", audio, debugstr_guid(guid), wave); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_GetStatus(ISpAudio *iface, SPAUDIOSTATUS *status) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("%p, %p\n", audio, status); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_SetBufferInfo(ISpAudio *iface,const SPAUDIOBUFFERINFO *buffer) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("%p, %p\n", audio, buffer); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_GetBufferInfo(ISpAudio *iface, SPAUDIOBUFFERINFO *buffer) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("%p, %p\n", audio, buffer); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_GetDefaultFormat(ISpAudio *iface, GUID *guid, WAVEFORMATEX **wave) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("%p, %p, %p\n", audio, guid, wave); - return E_NOTIMPL; -} - -static HANDLE WINAPI spaudio_EventHandle(ISpAudio *iface) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("%p\n", audio); - return NULL; -} - -static HRESULT WINAPI spaudio_GetVolumeLevel(ISpAudio *iface, ULONG *level) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("%p, %p\n", audio, level); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_SetVolumeLevel(ISpAudio *iface, ULONG level) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("%p, %ld\n", audio, level); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_GetBufferNotifySize(ISpAudio *iface, ULONG *size) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("%p, %p\n", audio, size); - return E_NOTIMPL; -} - -static HRESULT WINAPI spaudio_SetBufferNotifySize(ISpAudio *iface, ULONG size) -{ - struct speech_audio *audio = impl_from_ISpAudio(iface); - FIXME("%p, %ld\n", audio, size); - return E_NOTIMPL; -} - -const struct ISpAudioVtbl spaudio_vtbl = -{ - spaudio_QueryInterface, - spaudio_AddRef, - spaudio_Release, - spaudio_Read, - spaudio_Write, - spaudio_Seek, - spaudio_SetSize, - spaudio_CopyTo, - spaudio_Commit, - spaudio_Revert, - spaudio_LockRegion, - spaudio_UnlockRegion, - spaudio_Stat, - spaudio_Clone, - spaudio_GetFormat, - spaudio_SetState, - spaudio_SetFormat, - spaudio_GetStatus, - spaudio_SetBufferInfo, - spaudio_GetBufferInfo, - spaudio_GetDefaultFormat, - spaudio_EventHandle, - spaudio_GetVolumeLevel, - spaudio_SetVolumeLevel, - spaudio_GetBufferNotifySize, - spaudio_SetBufferNotifySize -}; - -static HRESULT speech_audio_create(void **obj) -{ - struct speech_audio *This = heap_alloc(sizeof(*This)); - - if (!This) - return E_OUTOFMEMORY; - This->ISpAudio_iface.lpVtbl = &spaudio_vtbl; - This->ref = 1; - - *obj = &This->ISpAudio_iface; - return S_OK; -} - static HRESULT WINAPI token_CreateInstance( ISpObjectToken *iface, IUnknown *outer, DWORD class_context, REFIID riid, void **object ) { - struct object_token *This = impl_from_ISpObjectToken( iface ); + WCHAR *clsid_str; + CLSID clsid; + IUnknown *unk; + ISpObjectWithToken *obj_token_iface; + HRESULT hr; - FIXME( "(%p)->(%p 0x%08lx %s, %p): semi-stub\n", This, outer, class_context, debugstr_guid( riid ), object); + TRACE( "%p, %p, %#lx, %s, %p\n", iface, outer, class_context, debugstr_guid( riid ), object ); - if (IsEqualIID(riid, &IID_ISpAudio)) + if (FAILED(hr = ISpObjectToken_GetStringValue( iface, L"CLSID", &clsid_str ))) + return hr; + + hr = CLSIDFromString( clsid_str, &clsid ); + CoTaskMemFree( clsid_str ); + if (FAILED(hr)) + return hr; + + if (FAILED(hr = CoCreateInstance( &clsid, outer, class_context, &IID_IUnknown, (void **)&unk ))) + return hr; + + /* Call ISpObjectWithToken::SetObjectToken if the interface is available. */ + if (SUCCEEDED(IUnknown_QueryInterface( unk, &IID_ISpObjectWithToken, (void **)&obj_token_iface ))) { - return speech_audio_create(object); + hr = ISpObjectWithToken_SetObjectToken( obj_token_iface, iface ); + ISpObjectWithToken_Release( obj_token_iface ); + if (FAILED(hr)) + goto done; } - return E_NOTIMPL; + + hr = IUnknown_QueryInterface( unk, riid, object ); + +done: + IUnknown_Release( unk ); + return hr; } static HRESULT WINAPI token_GetStorageFileName( ISpObjectToken *iface, @@ -1549,7 +1504,7 @@ HRESULT token_create( IUnknown *outer, REFIID iid, void **obj ) This->ISpObjectToken_iface.lpVtbl = &token_vtbl; This->ref = 1; - This->token_key = NULL; + This->data_key = NULL; This->token_id = NULL; hr = ISpObjectToken_QueryInterface( &This->ISpObjectToken_iface, iid, obj ); diff --git a/dlls/sapi/tts.c b/dlls/sapi/tts.c index 9f60c70e6c4..b1c90627d5e 100644 --- a/dlls/sapi/tts.c +++ b/dlls/sapi/tts.c @@ -27,6 +27,7 @@ #include "objbase.h" #include "sapiddk.h" +#include "sperror.h" #include "wine/debug.h" @@ -40,6 +41,15 @@ struct speech_voice ISpVoice ISpVoice_iface; IConnectionPointContainer IConnectionPointContainer_iface; LONG ref; + + ISpStreamFormat *output; + ISpTTSEngine *engine; + LONG cur_stream_num; + DWORD actions; + USHORT volume; + LONG rate; + struct async_queue queue; + CRITICAL_SECTION cs; }; static inline struct speech_voice *impl_from_ISpeechVoice(ISpeechVoice *iface) @@ -57,6 +67,55 @@ static inline struct speech_voice *impl_from_IConnectionPointContainer(IConnecti return CONTAINING_RECORD(iface, struct speech_voice, IConnectionPointContainer_iface); } +struct tts_engine_site +{ + ISpTTSEngineSite ISpTTSEngineSite_iface; + LONG ref; + + struct speech_voice *voice; + ULONG stream_num; +}; + +static inline struct tts_engine_site *impl_from_ISpTTSEngineSite(ISpTTSEngineSite *iface) +{ + return CONTAINING_RECORD(iface, struct tts_engine_site, ISpTTSEngineSite_iface); +} + +static HRESULT create_default_token(const WCHAR *cat_id, ISpObjectToken **token) +{ + ISpObjectTokenCategory *cat; + WCHAR *default_token_id = NULL; + HRESULT hr; + + TRACE("(%s, %p).\n", debugstr_w(cat_id), token); + + if (FAILED(hr = CoCreateInstance(&CLSID_SpObjectTokenCategory, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectTokenCategory, (void **)&cat))) + return hr; + + if (FAILED(hr = ISpObjectTokenCategory_SetId(cat, cat_id, FALSE)) || + FAILED(hr = ISpObjectTokenCategory_GetDefaultTokenId(cat, &default_token_id))) + { + ISpObjectTokenCategory_Release(cat); + return hr; + } + ISpObjectTokenCategory_Release(cat); + + if (FAILED(hr = CoCreateInstance(&CLSID_SpObjectToken, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectToken, (void **)token))) + goto done; + + if (FAILED(hr = ISpObjectToken_SetId(*token, NULL, default_token_id, FALSE))) + { + ISpObjectToken_Release(*token); + *token = NULL; + } + +done: + CoTaskMemFree(default_token_id); + return hr; +} + /* ISpeechVoice interface */ static HRESULT WINAPI speech_voice_QueryInterface(ISpeechVoice *iface, REFIID iid, void **obj) { @@ -102,6 +161,11 @@ static ULONG WINAPI speech_voice_Release(ISpeechVoice *iface) if (!ref) { + async_cancel_queue(&This->queue); + if (This->output) ISpStreamFormat_Release(This->output); + if (This->engine) ISpTTSEngine_Release(This->engine); + DeleteCriticalSection(&This->cs); + heap_free(This); } @@ -515,11 +579,54 @@ static HRESULT WINAPI spvoice_GetInfo(ISpVoice *iface, SPEVENTSOURCEINFO *info) return E_NOTIMPL; } -static HRESULT WINAPI spvoice_SetOutput(ISpVoice *iface, IUnknown *unk, BOOL changes) +static HRESULT WINAPI spvoice_SetOutput(ISpVoice *iface, IUnknown *unk, BOOL allow_format_changes) { - FIXME("(%p, %p, %d): stub.\n", iface, unk, changes); + struct speech_voice *This = impl_from_ISpVoice(iface); + ISpStreamFormat *stream = NULL; + ISpObjectToken *token = NULL; + HRESULT hr; - return E_NOTIMPL; + TRACE("(%p, %p, %d).\n", iface, unk, allow_format_changes); + + if (!allow_format_changes) + FIXME("ignoring allow_format_changes = FALSE.\n"); + + if (FAILED(hr = async_start_queue(&This->queue))) + return hr; + + if (!unk) + { + /* TODO: Create the default SpAudioOut token here once SpMMAudioEnum is implemented. */ + if (FAILED(hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpStreamFormat, (void **)&stream))) + return hr; + } + else + { + if (FAILED(IUnknown_QueryInterface(unk, &IID_ISpStreamFormat, (void **)&stream))) + { + if (FAILED(IUnknown_QueryInterface(unk, &IID_ISpObjectToken, (void **)&token))) + return E_INVALIDARG; + } + } + + if (!stream) + { + hr = ISpObjectToken_CreateInstance(token, NULL, CLSCTX_ALL, &IID_ISpStreamFormat, (void **)&stream); + ISpObjectToken_Release(token); + if (FAILED(hr)) + return hr; + } + + EnterCriticalSection(&This->cs); + + if (This->output) + ISpStreamFormat_Release(This->output); + This->output = stream; + + LeaveCriticalSection(&This->cs); + + return S_OK; } static HRESULT WINAPI spvoice_GetOutputObjectToken(ISpVoice *iface, ISpObjectToken **token) @@ -552,23 +659,313 @@ static HRESULT WINAPI spvoice_Resume(ISpVoice *iface) static HRESULT WINAPI spvoice_SetVoice(ISpVoice *iface, ISpObjectToken *token) { - FIXME("(%p, %p): stub.\n", iface, token); + struct speech_voice *This = impl_from_ISpVoice(iface); + ISpTTSEngine *engine; + HRESULT hr; - return E_NOTIMPL; + TRACE("(%p, %p).\n", iface, token); + + if (!token) + { + if (FAILED(hr = create_default_token(SPCAT_VOICES, &token))) + return hr; + } + else + ISpObjectToken_AddRef(token); + + hr = ISpObjectToken_CreateInstance(token, NULL, CLSCTX_ALL, &IID_ISpTTSEngine, (void **)&engine); + ISpObjectToken_Release(token); + if (FAILED(hr)) + return hr; + + EnterCriticalSection(&This->cs); + + if (This->engine) + ISpTTSEngine_Release(This->engine); + This->engine = engine; + + LeaveCriticalSection(&This->cs); + + return S_OK; } static HRESULT WINAPI spvoice_GetVoice(ISpVoice *iface, ISpObjectToken **token) { - FIXME("(%p, %p): stub.\n", iface, token); + struct speech_voice *This = impl_from_ISpVoice(iface); + ISpObjectWithToken *engine_token_iface; + HRESULT hr; + + TRACE("(%p, %p).\n", iface, token); + + if (!token) + return E_POINTER; + + EnterCriticalSection(&This->cs); + + if (!This->engine) + { + LeaveCriticalSection(&This->cs); + return create_default_token(SPCAT_VOICES, token); + } + + if (SUCCEEDED(hr = ISpTTSEngine_QueryInterface(This->engine, &IID_ISpObjectWithToken, (void **)&engine_token_iface))) + { + hr = ISpObjectWithToken_GetObjectToken(engine_token_iface, token); + ISpObjectWithToken_Release(engine_token_iface); + } + + LeaveCriticalSection(&This->cs); + + return hr; +} + +struct async_result +{ + HANDLE done; + HRESULT hr; +}; - return token_create(NULL, &IID_ISpObjectToken, (void **)token); +struct speak_task +{ + struct async_task task; + struct async_result *result; + + struct speech_voice *voice; + SPVTEXTFRAG *frag_list; + ISpTTSEngineSite *site; + DWORD flags; +}; + +static HRESULT set_output_format(ISpStreamFormat *output, ISpTTSEngine *engine, GUID *fmtid, WAVEFORMATEX **wfx) +{ + GUID output_fmtid; + WAVEFORMATEX *output_wfx = NULL; + ISpAudio *audio = NULL; + HRESULT hr; + + if (FAILED(hr = ISpStreamFormat_GetFormat(output, &output_fmtid, &output_wfx))) + return hr; + if (FAILED(hr = ISpTTSEngine_GetOutputFormat(engine, &output_fmtid, output_wfx, fmtid, wfx))) + goto done; + if (!IsEqualGUID(fmtid, &SPDFID_WaveFormatEx)) + { + hr = E_INVALIDARG; + goto done; + } + + if (memcmp(output_wfx, *wfx, sizeof(WAVEFORMATEX)) || + memcmp(output_wfx + 1, *wfx + 1, output_wfx->cbSize)) + { + if (FAILED(hr = ISpStreamFormat_QueryInterface(output, &IID_ISpAudio, (void **)&audio)) || + FAILED(hr = ISpAudio_SetFormat(audio, &SPDFID_WaveFormatEx, *wfx))) + goto done; + } + +done: + CoTaskMemFree(output_wfx); + if (audio) ISpAudio_Release(audio); + return hr; } -static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWORD flags, ULONG *number) +static void speak_proc(struct async_task *task) { - FIXME("(%p, %p, %#lx, %p): stub.\n", iface, contents, flags, number); + struct speak_task *speak_task = (struct speak_task *)task; + struct speech_voice *This = speak_task->voice; + GUID fmtid; + WAVEFORMATEX *wfx = NULL; + ISpTTSEngine *engine = NULL; + ISpAudio *audio = NULL; + HRESULT hr; - return E_NOTIMPL; + TRACE("(%p).\n", task); + + EnterCriticalSection(&This->cs); + + if (This->actions & SPVES_ABORT) + { + LeaveCriticalSection(&This->cs); + hr = S_OK; + goto done; + } + + if (FAILED(hr = set_output_format(This->output, This->engine, &fmtid, &wfx))) + { + LeaveCriticalSection(&This->cs); + ERR("failed setting output format: %#lx.\n", hr); + goto done; + } + engine = This->engine; + ISpTTSEngine_AddRef(engine); + + if (SUCCEEDED(ISpStreamFormat_QueryInterface(This->output, &IID_ISpAudio, (void **)&audio))) + ISpAudio_SetState(audio, SPAS_RUN, 0); + + This->actions = SPVES_RATE | SPVES_VOLUME; + + LeaveCriticalSection(&This->cs); + + hr = ISpTTSEngine_Speak(engine, speak_task->flags, &fmtid, wfx, speak_task->frag_list, speak_task->site); + if (SUCCEEDED(hr)) + { + ISpStreamFormat_Commit(This->output, STGC_DEFAULT); + if (audio) + WaitForSingleObject(ISpAudio_EventHandle(audio), INFINITE); + } + else + WARN("ISpTTSEngine_Speak failed: %#lx.\n", hr); + +done: + if (audio) + { + ISpAudio_SetState(audio, SPAS_CLOSED, 0); + ISpAudio_Release(audio); + } + CoTaskMemFree(wfx); + if (engine) ISpTTSEngine_Release(engine); + heap_free(speak_task->frag_list); + ISpTTSEngineSite_Release(speak_task->site); + + if (speak_task->result) + { + speak_task->result->hr = hr; + SetEvent(speak_task->result->done); + } +} + +static HRESULT ttsenginesite_create(struct speech_voice *voice, ULONG stream_num, ISpTTSEngineSite **site); + +static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWORD flags, ULONG *stream_num_out) +{ + struct speech_voice *This = impl_from_ISpVoice(iface); + ISpTTSEngineSite *site = NULL; + SPVTEXTFRAG *frag; + struct speak_task *speak_task = NULL; + struct async_result *result = NULL; + size_t contents_len, contents_size; + ULONG stream_num; + HRESULT hr; + + TRACE("(%p, %p, %#lx, %p).\n", iface, contents, flags, stream_num_out); + + flags &= ~SPF_IS_NOT_XML; + if (flags & ~(SPF_ASYNC | SPF_PURGEBEFORESPEAK | SPF_NLP_SPEAK_PUNC)) + { + FIXME("flags %#lx not implemented.\n", flags & ~(SPF_ASYNC | SPF_PURGEBEFORESPEAK | SPF_NLP_SPEAK_PUNC)); + return E_NOTIMPL; + } + + if (flags & SPF_PURGEBEFORESPEAK) + { + ISpAudio *audio; + + EnterCriticalSection(&This->cs); + + This->actions = SPVES_ABORT; + if (This->output && SUCCEEDED(ISpStreamFormat_QueryInterface(This->output, &IID_ISpAudio, (void **)&audio))) + { + ISpAudio_SetState(audio, SPAS_CLOSED, 0); + ISpAudio_Release(audio); + } + + LeaveCriticalSection(&This->cs); + + async_empty_queue(&This->queue); + + EnterCriticalSection(&This->cs); + This->actions = SPVES_CONTINUE; + LeaveCriticalSection(&This->cs); + + if (!contents || !*contents) + return S_OK; + } + else if (!contents) + return E_POINTER; + + contents_len = wcslen(contents); + contents_size = sizeof(WCHAR) * (contents_len + 1); + + if (!This->output) + { + /* Create a new output stream with the default output. */ + if (FAILED(hr = ISpVoice_SetOutput(iface, NULL, TRUE))) + return hr; + } + + if (!This->engine) + { + /* Create a new engine with the default voice. */ + if (FAILED(hr = ISpVoice_SetVoice(iface, NULL))) + return hr; + } + + if (!(frag = heap_alloc(sizeof(*frag) + contents_size))) + return E_OUTOFMEMORY; + memset(frag, 0, sizeof(*frag)); + memcpy(frag + 1, contents, contents_size); + frag->State.eAction = SPVA_Speak; + frag->State.Volume = 100; + frag->pTextStart = (WCHAR *)(frag + 1); + frag->ulTextLen = contents_len; + frag->ulTextSrcOffset = 0; + + stream_num = InterlockedIncrement(&This->cur_stream_num); + if (FAILED(hr = ttsenginesite_create(This, stream_num, &site))) + { + FIXME("Failed to create ttsenginesite: %#lx.\n", hr); + goto fail; + } + + speak_task = heap_alloc(sizeof(*speak_task)); + + speak_task->task.proc = speak_proc; + speak_task->result = NULL; + speak_task->voice = This; + speak_task->frag_list = frag; + speak_task->site = site; + speak_task->flags = flags & SPF_NLP_SPEAK_PUNC; + + if (!(flags & SPF_ASYNC)) + { + if (!(result = heap_alloc(sizeof(*result)))) + { + hr = E_OUTOFMEMORY; + goto fail; + } + result->hr = E_FAIL; + result->done = CreateEventW(NULL, FALSE, FALSE, NULL); + speak_task->result = result; + } + + if (FAILED(hr = async_queue_task(&This->queue, (struct async_task *)speak_task))) + { + WARN("Failed to queue task: %#lx.\n", hr); + goto fail; + } + + if (stream_num_out) + *stream_num_out = stream_num; + + if (flags & SPF_ASYNC) + return S_OK; + else + { + WaitForSingleObject(result->done, INFINITE); + hr = result->hr; + CloseHandle(result->done); + heap_free(result); + return hr; + } + +fail: + if (site) ISpTTSEngineSite_Release(site); + heap_free(frag); + heap_free(speak_task); + if (result) + { + CloseHandle(result->done); + heap_free(result); + } + return hr; } static HRESULT WINAPI spvoice_SpeakStream(ISpVoice *iface, IStream *stream, DWORD flags, ULONG *number) @@ -620,39 +1017,75 @@ static HRESULT WINAPI spvoice_GetAlertBoundary(ISpVoice *iface, SPEVENTENUM *bou return E_NOTIMPL; } -static HRESULT WINAPI spvoice_SetRate(ISpVoice *iface, LONG adjust) +static HRESULT WINAPI spvoice_SetRate(ISpVoice *iface, LONG rate) { - FIXME("(%p, %ld): stub.\n", iface, adjust); + struct speech_voice *This = impl_from_ISpVoice(iface); - return E_NOTIMPL; + TRACE("(%p, %ld).\n", iface, rate); + + EnterCriticalSection(&This->cs); + This->rate = rate; + This->actions |= SPVES_RATE; + LeaveCriticalSection(&This->cs); + + return S_OK; } -static HRESULT WINAPI spvoice_GetRate(ISpVoice *iface, LONG *adjust) +static HRESULT WINAPI spvoice_GetRate(ISpVoice *iface, LONG *rate) { - FIXME("(%p, %p): stub.\n", iface, adjust); + struct speech_voice *This = impl_from_ISpVoice(iface); - return E_NOTIMPL; + TRACE("(%p, %p).\n", iface, rate); + + EnterCriticalSection(&This->cs); + *rate = This->rate; + LeaveCriticalSection(&This->cs); + + return S_OK; } static HRESULT WINAPI spvoice_SetVolume(ISpVoice *iface, USHORT volume) { - FIXME("(%p, %d): stub.\n", iface, volume); + struct speech_voice *This = impl_from_ISpVoice(iface); - return E_NOTIMPL; + TRACE("(%p, %d).\n", iface, volume); + + if (volume > 100) + return E_INVALIDARG; + + EnterCriticalSection(&This->cs); + This->volume = volume; + This->actions |= SPVES_VOLUME; + LeaveCriticalSection(&This->cs); + + return S_OK; } static HRESULT WINAPI spvoice_GetVolume(ISpVoice *iface, USHORT *volume) { - FIXME("(%p, %p): stub.\n", iface, volume); + struct speech_voice *This = impl_from_ISpVoice(iface); - return E_NOTIMPL; + TRACE("(%p, %p).\n", iface, volume); + + EnterCriticalSection(&This->cs); + *volume = This->volume; + LeaveCriticalSection(&This->cs); + + return S_OK; } static HRESULT WINAPI spvoice_WaitUntilDone(ISpVoice *iface, ULONG timeout) { - FIXME("(%p, %ld): stub.\n", iface, timeout); + struct speech_voice *This = impl_from_ISpVoice(iface); + HRESULT hr; - return E_NOTIMPL; + TRACE("(%p, %ld).\n", iface, timeout); + + hr = async_wait_queue_empty(&This->queue, timeout); + + if (hr == WAIT_OBJECT_0) return S_OK; + else if (hr == WAIT_TIMEOUT) return S_FALSE; + return hr; } static HRESULT WINAPI spvoice_SetSyncSpeakTimeout(ISpVoice *iface, ULONG timeout) @@ -734,6 +1167,171 @@ static const ISpVoiceVtbl spvoice_vtbl = spvoice_DisplayUI }; +/* ISpTTSEngineSite interface */ +static HRESULT WINAPI ttsenginesite_QueryInterface(ISpTTSEngineSite *iface, REFIID iid, void **obj) +{ + struct tts_engine_site *This = impl_from_ISpTTSEngineSite(iface); + + TRACE("(%p, %s %p).\n", iface, debugstr_guid(iid), obj); + + if (IsEqualIID(iid, &IID_IUnknown) || + IsEqualIID(iid, &IID_ISpTTSEngineSite)) + *obj = &This->ISpTTSEngineSite_iface; + else + { + *obj = NULL; + FIXME("interface %s not implemented.\n", debugstr_guid(iid)); + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown *)*obj); + return S_OK; +} + +static ULONG WINAPI ttsenginesite_AddRef(ISpTTSEngineSite *iface) +{ + struct tts_engine_site *This = impl_from_ISpTTSEngineSite(iface); + ULONG ref = InterlockedIncrement(&This->ref); + + TRACE("(%p): ref=%lu.\n", iface, ref); + + return ref; +} + +static ULONG WINAPI ttsenginesite_Release(ISpTTSEngineSite *iface) +{ + struct tts_engine_site *This = impl_from_ISpTTSEngineSite(iface); + + ULONG ref = InterlockedDecrement(&This->ref); + + TRACE("(%p): ref=%lu.\n", iface, ref); + + if (!ref) + { + if (This->voice) + ISpeechVoice_Release(&This->voice->ISpeechVoice_iface); + heap_free(This); + } + + return ref; +} + +static HRESULT WINAPI ttsenginesite_AddEvents(ISpTTSEngineSite *iface, const SPEVENT *events, ULONG count) +{ + FIXME("(%p, %p, %ld): stub.\n", iface, events, count); + + return S_OK; +} + +static HRESULT WINAPI ttsenginesite_GetEventInterest(ISpTTSEngineSite *iface, ULONGLONG *interest) +{ + FIXME("(%p, %p): stub.\n", iface, interest); + + return E_NOTIMPL; +} + +static DWORD WINAPI ttsenginesite_GetActions(ISpTTSEngineSite *iface) +{ + struct tts_engine_site *This = impl_from_ISpTTSEngineSite(iface); + DWORD actions; + + TRACE("(%p).\n", iface); + + EnterCriticalSection(&This->voice->cs); + actions = This->voice->actions; + LeaveCriticalSection(&This->voice->cs); + + return actions; +} + +static HRESULT WINAPI ttsenginesite_Write(ISpTTSEngineSite *iface, const void *buf, ULONG cb, ULONG *cb_written) +{ + struct tts_engine_site *This = impl_from_ISpTTSEngineSite(iface); + + TRACE("(%p, %p, %ld, %p).\n", iface, buf, cb, cb_written); + + if (!This->voice->output) + return SPERR_UNINITIALIZED; + + return ISpStreamFormat_Write(This->voice->output, buf, cb, cb_written); +} + +static HRESULT WINAPI ttsenginesite_GetRate(ISpTTSEngineSite *iface, LONG *rate) +{ + struct tts_engine_site *This = impl_from_ISpTTSEngineSite(iface); + + TRACE("(%p, %p).\n", iface, rate); + + EnterCriticalSection(&This->voice->cs); + *rate = This->voice->rate; + This->voice->actions &= ~SPVES_RATE; + LeaveCriticalSection(&This->voice->cs); + + return S_OK; +} + +static HRESULT WINAPI ttsenginesite_GetVolume(ISpTTSEngineSite *iface, USHORT *volume) +{ + struct tts_engine_site *This = impl_from_ISpTTSEngineSite(iface); + + TRACE("(%p, %p).\n", iface, volume); + + EnterCriticalSection(&This->voice->cs); + *volume = This->voice->volume; + This->voice->actions &= ~SPVES_VOLUME; + LeaveCriticalSection(&This->voice->cs); + + return S_OK; +} + +static HRESULT WINAPI ttsenginesite_GetSkipInfo(ISpTTSEngineSite *iface, SPVSKIPTYPE *type, LONG *skip_count) +{ + FIXME("(%p, %p, %p): stub.\n", iface, type, skip_count); + + return E_NOTIMPL; +} + +static HRESULT WINAPI ttsenginesite_CompleteSkip(ISpTTSEngineSite *iface, LONG num_skipped) +{ + FIXME("(%p, %ld): stub.\n", iface, num_skipped); + + return E_NOTIMPL; +} + +const static ISpTTSEngineSiteVtbl ttsenginesite_vtbl = +{ + ttsenginesite_QueryInterface, + ttsenginesite_AddRef, + ttsenginesite_Release, + ttsenginesite_AddEvents, + ttsenginesite_GetEventInterest, + ttsenginesite_GetActions, + ttsenginesite_Write, + ttsenginesite_GetRate, + ttsenginesite_GetVolume, + ttsenginesite_GetSkipInfo, + ttsenginesite_CompleteSkip +}; + +static HRESULT ttsenginesite_create(struct speech_voice *voice, ULONG stream_num, ISpTTSEngineSite **site) +{ + struct tts_engine_site *This = heap_alloc(sizeof(*This)); + + if (!This) return E_OUTOFMEMORY; + + This->ISpTTSEngineSite_iface.lpVtbl = &ttsenginesite_vtbl; + + This->ref = 1; + This->voice = voice; + This->stream_num = stream_num; + + ISpeechVoice_AddRef(&This->voice->ISpeechVoice_iface); + + *site = &This->ISpTTSEngineSite_iface; + + return S_OK; +} + /* IConnectionPointContainer interface */ static HRESULT WINAPI container_QueryInterface(IConnectionPointContainer *iface, REFIID iid, void **obj) { @@ -798,6 +1396,16 @@ HRESULT speech_voice_create(IUnknown *outer, REFIID iid, void **obj) This->IConnectionPointContainer_iface.lpVtbl = &container_vtbl; This->ref = 1; + This->output = NULL; + This->engine = NULL; + This->cur_stream_num = 0; + This->actions = SPVES_CONTINUE; + This->volume = 100; + This->rate = 0; + memset(&This->queue, 0, sizeof(This->queue)); + + InitializeCriticalSection(&This->cs); + hr = ISpeechVoice_QueryInterface(&This->ISpeechVoice_iface, iid, obj); ISpeechVoice_Release(&This->ISpeechVoice_iface); diff --git a/dlls/user32/clipboard.c b/dlls/user32/clipboard.c index dd33bd7df82..72c7720dc17 100644 --- a/dlls/user32/clipboard.c +++ b/dlls/user32/clipboard.c @@ -157,11 +157,13 @@ static HANDLE marshal_data( UINT format, HANDLE handle, size_t *ret_size ) } case CF_UNICODETEXT: { - WCHAR *ptr; + char *ptr; if (!(size = GlobalSize( handle ))) return 0; if ((data_size_t)size != size) return 0; + if (size < sizeof(WCHAR)) return 0; if (!(ptr = GlobalLock( handle ))) return 0; - ptr[(size + 1) / sizeof(WCHAR) - 1] = 0; /* enforce null-termination */ + /* enforce nul-termination the Windows way: ignoring alignment */ + *((WCHAR *)(ptr + size) - 1) = 0; GlobalUnlock( handle ); *ret_size = size; return handle; diff --git a/dlls/user32/defwnd.c b/dlls/user32/defwnd.c index b4b42b86ae1..60afec4e7c6 100644 --- a/dlls/user32/defwnd.c +++ b/dlls/user32/defwnd.c @@ -25,6 +25,59 @@ WINE_DEFAULT_DEBUG_CHANNEL(win); +static void default_ime_compositionW( HWND hwnd, WPARAM wparam, LPARAM lparam ) +{ + WCHAR *buf = NULL; + LONG size, i; + HIMC himc; + + if (!(lparam & GCS_RESULTSTR) || !(himc = ImmGetContext( hwnd ))) return; + + if ((size = ImmGetCompositionStringW( himc, GCS_RESULTSTR, NULL, 0 ))) + { + if (!(buf = HeapAlloc( GetProcessHeap(), 0, size * sizeof(WCHAR) ))) size = 0; + else size = ImmGetCompositionStringW( himc, GCS_RESULTSTR, buf, size * sizeof(WCHAR) ); + } + ImmReleaseContext( hwnd, himc ); + + for (i = 0; i < size / sizeof(WCHAR); i++) + SendMessageW( hwnd, WM_IME_CHAR, buf[i], 1 ); + HeapFree( GetProcessHeap(), 0, buf ); +} + +static void default_ime_compositionA( HWND hwnd, WPARAM wparam, LPARAM lparam ) +{ + unsigned char lead = 0; + char *buf = NULL; + LONG size, i; + HIMC himc; + + if (!(lparam & GCS_RESULTSTR) || !(himc = ImmGetContext( hwnd ))) return; + + if ((size = ImmGetCompositionStringA( himc, GCS_RESULTSTR, NULL, 0 ))) + { + if (!(buf = HeapAlloc( GetProcessHeap(), 0, size ))) size = 0; + else size = ImmGetCompositionStringA( himc, GCS_RESULTSTR, buf, size ); + } + ImmReleaseContext( hwnd, himc ); + + for (i = 0; i < size; i++) + { + unsigned char c = buf[i]; + if (!lead) + { + if (IsDBCSLeadByte( c )) lead = c; + else SendMessageA( hwnd, WM_IME_CHAR, c, 1 ); + } + else + { + SendMessageA( hwnd, WM_IME_CHAR, MAKEWORD(c, lead), 1 ); + lead = 0; + } + } + + HeapFree( GetProcessHeap(), 0, buf ); +} /*********************************************************************** * DefWindowProcA (USER32.@) @@ -66,42 +119,13 @@ LRESULT WINAPI DefWindowProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam break; case WM_IME_COMPOSITION: - if (lParam & GCS_RESULTSTR) - { - LONG size, i; - unsigned char lead = 0; - char *buf = NULL; - HIMC himc = ImmGetContext( hwnd ); - - if (himc) - { - if ((size = ImmGetCompositionStringA( himc, GCS_RESULTSTR, NULL, 0 ))) - { - if (!(buf = HeapAlloc( GetProcessHeap(), 0, size ))) size = 0; - else size = ImmGetCompositionStringA( himc, GCS_RESULTSTR, buf, size ); - } - ImmReleaseContext( hwnd, himc ); - - for (i = 0; i < size; i++) - { - unsigned char c = buf[i]; - if (!lead) - { - if (IsDBCSLeadByte( c )) - lead = c; - else - SendMessageA( hwnd, WM_IME_CHAR, c, 1 ); - } - else - { - SendMessageA( hwnd, WM_IME_CHAR, MAKEWORD(c, lead), 1 ); - lead = 0; - } - } - HeapFree( GetProcessHeap(), 0, buf ); - } - } + { + HWND ime_hwnd = NtUserGetDefaultImeWindow( hwnd ); + if (!ime_hwnd || ime_hwnd == NtUserGetParent( hwnd )) break; + + default_ime_compositionA( hwnd, wParam, lParam ); /* fall through */ + } default: result = NtUserMessageCall( hwnd, msg, wParam, lParam, 0, NtUserDefWindowProc, TRUE ); @@ -159,27 +183,13 @@ LRESULT WINAPI DefWindowProcW( break; case WM_IME_COMPOSITION: - if (lParam & GCS_RESULTSTR) - { - LONG size, i; - WCHAR *buf = NULL; - HIMC himc = ImmGetContext( hwnd ); - - if (himc) - { - if ((size = ImmGetCompositionStringW( himc, GCS_RESULTSTR, NULL, 0 ))) - { - if (!(buf = HeapAlloc( GetProcessHeap(), 0, size * sizeof(WCHAR) ))) size = 0; - else size = ImmGetCompositionStringW( himc, GCS_RESULTSTR, buf, size * sizeof(WCHAR) ); - } - ImmReleaseContext( hwnd, himc ); - - for (i = 0; i < size / sizeof(WCHAR); i++) - SendMessageW( hwnd, WM_IME_CHAR, buf[i], 1 ); - HeapFree( GetProcessHeap(), 0, buf ); - } - } + { + HWND ime_hwnd = NtUserGetDefaultImeWindow( hwnd ); + if (!ime_hwnd || ime_hwnd == NtUserGetParent( hwnd )) break; + + default_ime_compositionW( hwnd, wParam, lParam ); /* fall through */ + } default: result = NtUserMessageCall( hwnd, msg, wParam, lParam, 0, NtUserDefWindowProc, FALSE ); diff --git a/dlls/user32/edit.c b/dlls/user32/edit.c index 2e651e455dd..73df9d0b330 100644 --- a/dlls/user32/edit.c +++ b/dlls/user32/edit.c @@ -113,6 +113,7 @@ typedef struct should be sent to the first parent. */ HWND hwndListBox; /* handle of ComboBox's listbox or NULL */ INT wheelDeltaRemainder; /* scroll wheel delta left over after scrolling whole lines */ + BYTE lead_byte; /* DBCS lead byte store for WM_CHAR */ /* * only for multi line controls */ @@ -127,9 +128,8 @@ typedef struct /* * IME Data */ - UINT composition_len; /* length of composition, 0 == no composition */ - int composition_start; /* the character position for the composition */ - UINT ime_status; /* IME status flag */ + UINT ime_status; /* IME status flag */ + /* * Uniscribe Data */ @@ -1778,6 +1778,20 @@ static LRESULT EDIT_EM_Scroll(EDITSTATE *es, INT action) } +static void EDIT_UpdateImmCompositionWindow(EDITSTATE *es, UINT x, UINT y) +{ + COMPOSITIONFORM form = + { + .dwStyle = CFS_RECT, + .ptCurrentPos = {.x = x, .y = y}, + .rcArea = es->format_rect, + }; + HIMC himc = ImmGetContext(es->hwndSelf); + ImmSetCompositionWindow(himc, &form); + ImmReleaseContext(es->hwndSelf, himc); +} + + /********************************************************************* * * EDIT_SetCaretPos @@ -1793,6 +1807,7 @@ static void EDIT_SetCaretPos(EDITSTATE *es, INT pos, res = EDIT_EM_PosFromChar(es, pos, after_wrap); TRACE("%d - %dx%d\n", pos, (short)LOWORD(res), (short)HIWORD(res)); SetCaretPos((short)LOWORD(res), (short)HIWORD(res)); + EDIT_UpdateImmCompositionWindow(es, (short)LOWORD(res), (short)HIWORD(res)); } } @@ -2133,9 +2148,6 @@ static INT EDIT_PaintText(EDITSTATE *es, HDC dc, INT x, INT y, INT line, INT col { COLORREF BkColor; COLORREF TextColor; - LOGFONTW underline_font; - HFONT hUnderline = 0; - HFONT old_font = 0; INT ret; INT li; INT BkMode; @@ -2147,20 +2159,9 @@ static INT EDIT_PaintText(EDITSTATE *es, HDC dc, INT x, INT y, INT line, INT col BkColor = GetBkColor(dc); TextColor = GetTextColor(dc); if (rev) { - if (es->composition_len == 0) - { - SetBkColor(dc, GetSysColor(COLOR_HIGHLIGHT)); - SetTextColor(dc, GetSysColor(COLOR_HIGHLIGHTTEXT)); - SetBkMode( dc, OPAQUE); - } - else - { - HFONT current = GetCurrentObject(dc,OBJ_FONT); - GetObjectW(current,sizeof(LOGFONTW),&underline_font); - underline_font.lfUnderline = TRUE; - hUnderline = CreateFontIndirectW(&underline_font); - old_font = SelectObject(dc,hUnderline); - } + SetBkColor(dc, GetSysColor(COLOR_HIGHLIGHT)); + SetTextColor(dc, GetSysColor(COLOR_HIGHLIGHTTEXT)); + SetBkMode(dc, OPAQUE); } li = EDIT_EM_LineIndex(es, line); if (es->style & ES_MULTILINE) { @@ -2172,19 +2173,9 @@ static INT EDIT_PaintText(EDITSTATE *es, HDC dc, INT x, INT y, INT line, INT col ret = size.cx; } if (rev) { - if (es->composition_len == 0) - { - SetBkColor(dc, BkColor); - SetTextColor(dc, TextColor); - SetBkMode( dc, BkMode); - } - else - { - if (old_font) - SelectObject(dc,old_font); - if (hUnderline) - DeleteObject(hUnderline); - } + SetBkColor(dc, BkColor); + SetTextColor(dc, TextColor); + SetBkMode(dc, BkMode); } return ret; } @@ -3840,6 +3831,15 @@ static DWORD get_font_margins(HDC hdc, const TEXTMETRICW *tm, BOOL unicode) return MAKELONG(left, right); } +static void EDIT_UpdateImmCompositionFont(EDITSTATE *es) +{ + LOGFONTW composition_font; + HIMC himc = ImmGetContext(es->hwndSelf); + GetObjectW(es->font, sizeof(LOGFONTW), &composition_font); + ImmSetCompositionFontW(himc, &composition_font); + ImmReleaseContext(es->hwndSelf, himc); +} + /********************************************************************* * * WM_SETFONT @@ -3891,6 +3891,8 @@ static void EDIT_WM_SetFont(EDITSTATE *es, HFONT font, BOOL redraw) es->flags & EF_AFTER_WRAP); NtUserShowCaret( es->hwndSelf ); } + + EDIT_UpdateImmCompositionFont(es); } @@ -4335,75 +4337,6 @@ static LRESULT EDIT_EM_GetThumb(EDITSTATE *es) * The Following code is to handle inline editing from IMEs */ -static void EDIT_GetCompositionStr(HIMC hIMC, LPARAM CompFlag, EDITSTATE *es) -{ - LONG buflen; - LPWSTR lpCompStr; - LPSTR lpCompStrAttr = NULL; - DWORD dwBufLenAttr; - - buflen = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, NULL, 0); - - if (buflen < 0) - { - return; - } - - lpCompStr = HeapAlloc(GetProcessHeap(),0,buflen); - if (!lpCompStr) - { - ERR("Unable to allocate IME CompositionString\n"); - return; - } - - if (buflen) - ImmGetCompositionStringW(hIMC, GCS_COMPSTR, lpCompStr, buflen); - - if (CompFlag & GCS_COMPATTR) - { - /* - * We do not use the attributes yet. it would tell us what characters - * are in transition and which are converted or decided upon - */ - dwBufLenAttr = ImmGetCompositionStringW(hIMC, GCS_COMPATTR, NULL, 0); - if (dwBufLenAttr) - { - dwBufLenAttr ++; - lpCompStrAttr = HeapAlloc(GetProcessHeap(),0,dwBufLenAttr+1); - if (!lpCompStrAttr) - { - ERR("Unable to allocate IME Attribute String\n"); - HeapFree(GetProcessHeap(),0,lpCompStr); - return; - } - ImmGetCompositionStringW(hIMC,GCS_COMPATTR, lpCompStrAttr, - dwBufLenAttr); - lpCompStrAttr[dwBufLenAttr] = 0; - } - } - - /* check for change in composition start */ - if (es->selection_end < es->composition_start) - es->composition_start = es->selection_end; - - /* replace existing selection string */ - es->selection_start = es->composition_start; - - if (es->composition_len > 0) - es->selection_end = es->composition_start + es->composition_len; - else - es->selection_end = es->selection_start; - - EDIT_EM_ReplaceSel(es, FALSE, lpCompStr, buflen / sizeof(WCHAR), TRUE, TRUE); - es->composition_len = abs(es->composition_start - es->selection_end); - - es->selection_start = es->composition_start; - es->selection_end = es->selection_start + es->composition_len; - - HeapFree(GetProcessHeap(),0,lpCompStrAttr); - HeapFree(GetProcessHeap(),0,lpCompStr); -} - static void EDIT_GetResultStr(HIMC hIMC, EDITSTATE *es) { LONG buflen; @@ -4423,48 +4356,22 @@ static void EDIT_GetResultStr(HIMC hIMC, EDITSTATE *es) } ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, lpResultStr, buflen); - - /* check for change in composition start */ - if (es->selection_end < es->composition_start) - es->composition_start = es->selection_end; - - es->selection_start = es->composition_start; - es->selection_end = es->composition_start + es->composition_len; EDIT_EM_ReplaceSel(es, TRUE, lpResultStr, buflen / sizeof(WCHAR), TRUE, TRUE); - es->composition_start = es->selection_end; - es->composition_len = 0; - HeapFree(GetProcessHeap(),0,lpResultStr); } static void EDIT_ImeComposition(HWND hwnd, LPARAM CompFlag, EDITSTATE *es) { HIMC hIMC; - int cursor; - - if (es->composition_len == 0 && es->selection_start != es->selection_end) - { - EDIT_EM_ReplaceSel(es, TRUE, NULL, 0, TRUE, TRUE); - es->composition_start = es->selection_end; - } hIMC = ImmGetContext(hwnd); if (!hIMC) return; if (CompFlag & GCS_RESULTSTR) - { EDIT_GetResultStr(hIMC, es); - cursor = 0; - } - else - { - if (CompFlag & GCS_COMPSTR) - EDIT_GetCompositionStr(hIMC, CompFlag, es); - cursor = ImmGetCompositionStringW(hIMC, GCS_CURSORPOS, 0, 0); - } + ImmReleaseContext(hwnd, hIMC); - EDIT_SetCaretPos(es, es->selection_start + cursor, es->flags & EF_AFTER_WRAP); } @@ -4686,6 +4593,7 @@ LRESULT EditWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, B { EDITSTATE *es = (EDITSTATE *)GetWindowLongPtrW( hwnd, 0 ); LRESULT result = 0; + POINT pt; TRACE("hwnd=%p msg=%x (%s) wparam=%Ix lparam=%Ix\n", hwnd, msg, SPY_GetMsgName(msg, hwnd), wParam, lParam); @@ -4975,8 +4883,24 @@ LRESULT EditWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, B charW = wParam; else { - CHAR charA = wParam; - MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1); + BYTE low = wParam; + DWORD cp = get_input_codepage(); + if (es->lead_byte) + { + char ch[2]; + ch[0] = es->lead_byte; + ch[1] = low; + MultiByteToWideChar(cp, 0, ch, 2, &charW, 1); + } + else if (IsDBCSLeadByteEx(cp, low)) + { + es->lead_byte = low; + result = 1; + break; + } + else + MultiByteToWideChar(cp, 0, (char *)&low, 1, &charW, 1); + es->lead_byte = 0; } if (es->hwndListBox) @@ -5181,34 +5105,16 @@ LRESULT EditWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, B /* IME messages to make the edit control IME aware */ case WM_IME_SETCONTEXT: - break; - - case WM_IME_STARTCOMPOSITION: - es->composition_start = es->selection_end; - es->composition_len = 0; + NtUserGetCaretPos(&pt); + EDIT_UpdateImmCompositionWindow(es, pt.x, pt.y); + EDIT_UpdateImmCompositionFont(es); break; case WM_IME_COMPOSITION: - if (lParam & GCS_RESULTSTR && !(es->ime_status & EIMES_GETCOMPSTRATONCE)) - { - DefWindowProcT(hwnd, msg, wParam, lParam, unicode); - break; - } - - EDIT_ImeComposition(hwnd, lParam, es); - break; - - case WM_IME_ENDCOMPOSITION: - if (es->composition_len > 0) - { - EDIT_EM_ReplaceSel(es, TRUE, NULL, 0, TRUE, TRUE); - es->selection_end = es->selection_start; - es->composition_len= 0; - } - break; - - case WM_IME_COMPOSITIONFULL: - break; + EDIT_EM_ReplaceSel(es, TRUE, NULL, 0, TRUE, TRUE); + if ((lParam & GCS_RESULTSTR) && (es->ime_status & EIMES_GETCOMPSTRATONCE)) + EDIT_ImeComposition(hwnd, lParam, es); + return DefWindowProcW(hwnd, msg, wParam, lParam); case WM_IME_SELECT: break; diff --git a/dlls/user32/input.c b/dlls/user32/input.c index 2e08d0e2eda..9fb270ed3ed 100644 --- a/dlls/user32/input.c +++ b/dlls/user32/input.c @@ -440,7 +440,8 @@ BOOL WINAPI BlockInput(BOOL fBlockIt) HKL WINAPI LoadKeyboardLayoutW( const WCHAR *name, UINT flags ) { WCHAR layout_path[MAX_PATH], value[5]; - DWORD value_size, tmp; + LCID locale = GetUserDefaultLCID(); + DWORD id, value_size, tmp; HKEY hkey; HKL layout; @@ -450,6 +451,9 @@ HKL WINAPI LoadKeyboardLayoutW( const WCHAR *name, UINT flags ) if (HIWORD( tmp )) layout = UlongToHandle( tmp ); else layout = UlongToHandle( MAKELONG( LOWORD( tmp ), LOWORD( tmp ) ) ); + if (!((UINT_PTR)layout >> 28)) id = LOWORD( tmp ); + else id = HIWORD( layout ); /* IME or aliased layout */ + wcscpy( layout_path, L"System\\CurrentControlSet\\Control\\Keyboard Layouts\\" ); wcscat( layout_path, name ); @@ -457,11 +461,12 @@ HKL WINAPI LoadKeyboardLayoutW( const WCHAR *name, UINT flags ) { value_size = sizeof(value); if (!RegGetValueW( hkey, NULL, L"Layout Id", RRF_RT_REG_SZ, NULL, (void *)&value, &value_size )) - layout = UlongToHandle( MAKELONG( LOWORD( tmp ), 0xf000 | (wcstoul( value, NULL, 16 ) & 0xfff) ) ); + id = 0xf000 | (wcstoul( value, NULL, 16 ) & 0xfff); RegCloseKey( hkey ); } + layout = UlongToHandle( MAKELONG( locale, id ) ); if ((flags & KLF_ACTIVATE) && NtUserActivateKeyboardLayout( layout, 0 )) return layout; /* FIXME: semi-stub: returning default layout */ diff --git a/dlls/user32/sysparams.c b/dlls/user32/sysparams.c index c7bd702726a..5a9087881ce 100644 --- a/dlls/user32/sysparams.c +++ b/dlls/user32/sysparams.c @@ -160,43 +160,6 @@ static void SYSPARAMS_NonClientMetrics32ATo32W( const NONCLIENTMETRICSA* lpnm32A /* Helper functions to retrieve monitors info */ -static BOOL CALLBACK get_virtual_screen_proc( HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM lp ) -{ - RECT *virtual_rect = (RECT *)lp; - - UnionRect( virtual_rect, virtual_rect, rect ); - return TRUE; -} - -RECT get_virtual_screen_rect(void) -{ - RECT rect = {0}; - - NtUserEnumDisplayMonitors( 0, NULL, get_virtual_screen_proc, (LPARAM)&rect ); - return rect; -} - -static BOOL CALLBACK get_primary_monitor_proc( HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM lp ) -{ - RECT *primary_rect = (RECT *)lp; - - if (!rect->top && !rect->left && rect->right && rect->bottom) - { - *primary_rect = *rect; - return FALSE; - } - - return TRUE; -} - -RECT get_primary_monitor_rect(void) -{ - RECT rect = {0}; - - NtUserEnumDisplayMonitors( 0, NULL, get_primary_monitor_proc, (LPARAM)&rect ); - return rect; -} - HDC get_display_dc(void) { EnterCriticalSection( &display_dc_section ); diff --git a/dlls/user32/tests/clipboard.c b/dlls/user32/tests/clipboard.c index 3131c2e0e8c..87df06b6621 100644 --- a/dlls/user32/tests/clipboard.c +++ b/dlls/user32/tests/clipboard.c @@ -986,7 +986,9 @@ static void test_synthesized(void) SetLastError(0xdeadbeef); data = GetClipboardData( CF_TEXT ); - ok(GetLastError() == 0xdeadbeef, "bad last error %ld\n", GetLastError()); + ok(GetLastError() == ERROR_NOT_FOUND /* win11 */ || + broken(GetLastError() == 0xdeadbeef), + "bad last error %ld\n", GetLastError()); ok(!data, "GetClipboardData() should have returned NULL\n"); r = CloseClipboard(); @@ -1587,7 +1589,9 @@ static void test_handles( HWND hwnd ) SetLastError( 0xdeadbeef ); data = GetClipboardData( CF_RIFF ); - ok( GetLastError() == 0xdeadbeef, "unexpected last error %ld\n", GetLastError() ); + ok( GetLastError() == ERROR_NOT_FOUND /* win11 */ || + broken(GetLastError() == 0xdeadbeef), + "unexpected last error %ld\n", GetLastError() ); ok( !data, "wrong data %p\n", data ); h = SetClipboardData( CF_PRIVATEFIRST + 7, htext4 ); @@ -2213,35 +2217,39 @@ static const struct UINT len; } test_data[] = { - { "foo", {}, 3 }, /* 0 */ + { "foo", {}, 3 }, /* 0 */ { "foo", {}, 4 }, { "foo\0bar", {}, 7 }, { "foo\0bar", {}, 8 }, - { "", {'f','o','o'}, 3 * sizeof(WCHAR) }, - { "", {'f','o','o',0}, 4 * sizeof(WCHAR) }, /* 5 */ + { "", {}, 0 }, + { "", {'f'}, 1 }, /* 5 */ + { "", {'f'}, 2 }, + { "", {0x3b1,0x3b2,0x3b3}, 5 }, /* Alpha, beta, ... */ + { "", {0x3b1,0x3b2,0x3b3}, 6 }, + { "", {0x3b1,0x3b2,0x3b3,0}, 7 }, /* 10 */ + { "", {0x3b1,0x3b2,0x3b3,0}, 8 }, + { "", {0x3b1,0x3b2,0x3b3,0,0x3b4}, 9 }, { "", {'f','o','o',0,'b','a','r'}, 7 * sizeof(WCHAR) }, { "", {'f','o','o',0,'b','a','r',0}, 8 * sizeof(WCHAR) }, - { "", {'f','o','o'}, 1 }, - { "", {'f','o','o'}, 2 }, - { "", {'f','o','o'}, 5 }, /* 10 */ - { "", {'f','o','o',0}, 7 }, - { "", {'f','o','o',0}, 9 }, }; static void test_string_data(void) { UINT i; BOOL r; - HANDLE data; + HANDLE data, clip; char cmd[16]; char bufferA[12]; WCHAR bufferW[12]; for (i = 0; i < ARRAY_SIZE(test_data); i++) { - /* 1-byte Unicode strings crash on Win64 */ #ifdef _WIN64 - if (!test_data[i].strA[0] && test_data[i].len < sizeof(WCHAR)) continue; + /* Before Windows 11 1-byte Unicode strings crash on Win64 + * because it underflows when 'appending' a 2 bytes NUL character! + */ + if (!test_data[i].strA[0] && test_data[i].len < sizeof(WCHAR) && + strcmp(winetest_platform, "windows") == 0) continue; #endif winetest_push_context("%d", i); r = open_clipboard( 0 ); @@ -2261,11 +2269,17 @@ static void test_string_data(void) else { memcpy( data, test_data[i].strW, test_data[i].len ); - SetClipboardData( CF_UNICODETEXT, data ); - memcpy( bufferW, test_data[i].strW, test_data[i].len ); - bufferW[(test_data[i].len + 1) / sizeof(WCHAR) - 1] = 0; - ok( !memcmp( data, bufferW, test_data[i].len ), - "wrong data %s\n", wine_dbgstr_wn( data, (test_data[i].len + 1) / sizeof(WCHAR) )); + clip = SetClipboardData( CF_UNICODETEXT, data ); + if (test_data[i].len >= sizeof(WCHAR)) + { + ok( clip == data, "SetClipboardData() returned %p != %p\n", clip, data ); + memcpy( bufferW, test_data[i].strW, test_data[i].len ); + *((WCHAR *)((char *)bufferW + test_data[i].len) - 1) = 0; + ok( !memcmp( data, bufferW, test_data[i].len ), + "wrong data %s\n", wine_dbgstr_an( data, test_data[i].len )); + } + else + ok( !clip || broken(clip != NULL), "expected SetClipboardData() to fail\n" ); } r = CloseClipboard(); ok( r, "gle %ld\n", GetLastError() ); @@ -2305,13 +2319,27 @@ static void test_string_data_process( int i ) else { data = GetClipboardData( CF_UNICODETEXT ); - ok( data != 0, "could not get data\n" ); - len = GlobalSize( data ); - ok( len == test_data[i].len, "wrong size %u / %u\n", len, test_data[i].len ); - memcpy( bufferW, test_data[i].strW, test_data[i].len ); - bufferW[(test_data[i].len + 1) / sizeof(WCHAR) - 1] = 0; - ok( !memcmp( data, bufferW, len ), - "wrong data %s\n", wine_dbgstr_wn( data, (len + 1) / sizeof(WCHAR) )); + if (!data) + { + ok( test_data[i].len < sizeof(WCHAR), "got no data for len=%d\n", test_data[i].len ); + ok( !IsClipboardFormatAvailable( CF_UNICODETEXT ), "unicode available\n" ); + } + else if (test_data[i].len == 0) + { + /* 0-byte string handling is broken on Windows <= 10 */ + len = GlobalSize( data ); + ok( broken(len == 1), "wrong size %u / 0\n", len ); + ok( broken(*((char*)data) == 0), "wrong data %s\n", wine_dbgstr_an( data, len )); + } + else + { + len = GlobalSize( data ); + ok( len == test_data[i].len, "wrong size %u / %u\n", len, test_data[i].len ); + memcpy( bufferW, test_data[i].strW, test_data[i].len ); + *((WCHAR *)((char *)bufferW + test_data[i].len) - 1) = 0; + ok( !memcmp( data, bufferW, len ), "wrong data %s\n", wine_dbgstr_an( data, len )); + } + data = GetClipboardData( CF_TEXT ); if (test_data[i].len >= sizeof(WCHAR)) { @@ -2321,12 +2349,15 @@ static void test_string_data_process( int i ) bufferA, ARRAY_SIZE(bufferA), NULL, NULL ); bufferA[len2 - 1] = 0; ok( len == len2, "wrong size %u / %u\n", len, len2 ); - ok( !memcmp( data, bufferA, len ), "wrong data %.*s\n", len, (char *)data ); + ok( !memcmp( data, bufferA, len ), "wrong data %s, expected %s\n", + wine_dbgstr_an( data, len ), wine_dbgstr_an( bufferA, len2 )); } else { + BOOL available = IsClipboardFormatAvailable( CF_TEXT ); + ok( !available || broken(available /* win10 */), + "available text %d\n", available ); ok( !data, "got data for empty string\n" ); - ok( IsClipboardFormatAvailable( CF_TEXT ), "text not available\n" ); } } r = CloseClipboard(); diff --git a/dlls/user32/tests/edit.c b/dlls/user32/tests/edit.c index b70cf2d6e06..78328ee1729 100644 --- a/dlls/user32/tests/edit.c +++ b/dlls/user32/tests/edit.c @@ -3366,6 +3366,58 @@ static void test_wordbreak_proc(void) DestroyWindow(hwnd); } +static void test_dbcs_WM_CHAR(void) +{ + WCHAR textW[] = { 0x4e00, 0x4e8c, 0x4e09, 0 }; /* one, two, three */ + unsigned char bytes[7]; + HWND hwnd[2]; + int i; + + WideCharToMultiByte(CP_ACP, 0, textW, -1, (char *)bytes, ARRAY_SIZE(bytes), NULL, NULL); + if (!IsDBCSLeadByte(bytes[0])) + { + skip("Skipping DBCS WM_CHAR test in this codepage\n"); + return; + } + hwnd[0] = create_editcontrol(ES_AUTOHSCROLL | ES_AUTOVSCROLL, 0); + hwnd[1] = create_editcontrolW(ES_AUTOHSCROLL | ES_AUTOVSCROLL, 0); + + for (i = 0; i < ARRAY_SIZE(hwnd); i++) + { + const unsigned char* p; + WCHAR strW[4]; + char str[7]; + MSG msg; + BOOL r; + int n; + + winetest_push_context("%c", i ? 'W' : 'A'); + + r = SetWindowTextA(hwnd[i], ""); + ok(r, "SetWindowText failed\n"); + + for (p = bytes; *p; p++) + PostMessageA(hwnd[i], WM_CHAR, *p, 1); + + while (PeekMessageA(&msg, hwnd[i], 0, 0, PM_REMOVE)) + DispatchMessageA(&msg); + + n = GetWindowTextW(hwnd[i], strW, ARRAY_SIZE(strW)); + ok(n > 0, "GetWindowTextW failed\n"); + ok(!wcscmp(strW, textW), "got %s, expected %s\n", + wine_dbgstr_w(strW), wine_dbgstr_w(textW)); + + n = GetWindowTextA(hwnd[i], str, ARRAY_SIZE(str)); + ok(n > 0, "GetWindowText failed\n"); + ok(!strcmp(str, (char*)bytes), "got %s, expected %s\n", + wine_dbgstr_a(str), wine_dbgstr_a((char *)bytes)); + + DestroyWindow(hwnd[i]); + + winetest_pop_context(); + } +} + START_TEST(edit) { BOOL b; @@ -3403,6 +3455,7 @@ START_TEST(edit) test_paste(); test_EM_GETLINE(); test_wordbreak_proc(); + test_dbcs_WM_CHAR(); UnregisterWindowClasses(); } diff --git a/dlls/user32/tests/input.c b/dlls/user32/tests/input.c index f8b40099091..52418d9c864 100644 --- a/dlls/user32/tests/input.c +++ b/dlls/user32/tests/input.c @@ -180,6 +180,118 @@ static void init_function_pointers(void) is_wow64 = FALSE; } +static const char *debugstr_ok( const char *cond ) +{ + int c, n = 0; + /* skip possible casts */ + while ((c = *cond++)) + { + if (c == '(') n++; + if (!n) break; + if (c == ')') n--; + } + if (!strchr( cond - 1, '(' )) return wine_dbg_sprintf( "got %s", cond - 1 ); + return wine_dbg_sprintf( "%.*s returned", (int)strcspn( cond - 1, "( " ), cond - 1 ); +} + +#define ok_eq( e, r, t, f, ... ) \ + do \ + { \ + t v = (r); \ + ok( v == (e), "%s " f "\n", debugstr_ok( #r ), v, ##__VA_ARGS__ ); \ + } while (0) +#define ok_rect( e, r ) \ + do \ + { \ + RECT v = (r); \ + ok( EqualRect( &v, &(e) ), "%s %s\n", debugstr_ok(#r), wine_dbgstr_rect(&v) ); \ + } while (0) +#define ok_ret( e, r ) ok_eq( e, r, UINT_PTR, "%Iu, error %ld", GetLastError() ) + +#define run_in_process( a, b ) run_in_process_( __FILE__, __LINE__, a, b ) +static void run_in_process_( const char *file, int line, char **argv, const char *args ) +{ + STARTUPINFOA startup = {.cb = sizeof(STARTUPINFOA)}; + PROCESS_INFORMATION info = {0}; + char cmdline[MAX_PATH * 2]; + DWORD ret; + + sprintf( cmdline, "%s %s %s", argv[0], argv[1], args ); + ret = CreateProcessA( NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &startup, &info ); + ok_(file, line)( ret, "CreateProcessA failed, error %lu\n", GetLastError() ); + if (!ret) return; + + wait_child_process( info.hProcess ); + CloseHandle( info.hThread ); + CloseHandle( info.hProcess ); +} + +#define run_in_desktop( a, b, c ) run_in_desktop_( __FILE__, __LINE__, a, b, c ) +static void run_in_desktop_( const char *file, int line, char **argv, + const char *args, BOOL input ) +{ + const char *desktop_name = "WineTest Desktop"; + STARTUPINFOA startup = {.cb = sizeof(STARTUPINFOA)}; + PROCESS_INFORMATION info = {0}; + HDESK old_desktop, desktop; + char cmdline[MAX_PATH * 2]; + DWORD ret; + + old_desktop = OpenInputDesktop( 0, FALSE, DESKTOP_ALL_ACCESS ); + ok_(file, line)( !!old_desktop, "OpenInputDesktop failed, error %lu\n", GetLastError() ); + desktop = CreateDesktopA( desktop_name, NULL, NULL, 0, DESKTOP_ALL_ACCESS, NULL ); + ok_(file, line)( !!desktop, "CreateDesktopA failed, error %lu\n", GetLastError() ); + if (input) + { + ret = SwitchDesktop( desktop ); + ok_(file, line)( ret, "SwitchDesktop failed, error %lu\n", GetLastError() ); + } + + startup.lpDesktop = (char *)desktop_name; + sprintf( cmdline, "%s %s %s", argv[0], argv[1], args ); + ret = CreateProcessA( NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &startup, &info ); + ok_(file, line)( ret, "CreateProcessA failed, error %lu\n", GetLastError() ); + if (!ret) return; + + wait_child_process( info.hProcess ); + CloseHandle( info.hThread ); + CloseHandle( info.hProcess ); + + if (input) + { + ret = SwitchDesktop( old_desktop ); + ok_(file, line)( ret, "SwitchDesktop failed, error %lu\n", GetLastError() ); + } + ret = CloseDesktop( desktop ); + ok_(file, line)( ret, "CloseDesktop failed, error %lu\n", GetLastError() ); + ret = CloseDesktop( old_desktop ); + ok_(file, line)( ret, "CloseDesktop failed, error %lu\n", GetLastError() ); +} + +#define msg_wait_for_events( a, b, c ) msg_wait_for_events_( __FILE__, __LINE__, a, b, c ) +static DWORD msg_wait_for_events_( const char *file, int line, DWORD count, HANDLE *events, DWORD timeout ) +{ + DWORD ret, end = GetTickCount() + min( timeout, 5000 ); + MSG msg; + + while ((ret = MsgWaitForMultipleObjects( count, events, FALSE, min( timeout, 5000 ), QS_ALLINPUT )) <= count) + { + while (PeekMessageW( &msg, 0, 0, 0, PM_REMOVE )) + { + TranslateMessage( &msg ); + DispatchMessageW( &msg ); + } + if (ret < count) return ret; + if (timeout >= 5000) continue; + if (end <= GetTickCount()) timeout = 0; + else timeout = end - GetTickCount(); + } + + if (timeout >= 5000) ok_(file, line)( 0, "MsgWaitForMultipleObjects returned %#lx\n", ret ); + else ok_(file, line)( ret == WAIT_TIMEOUT, "MsgWaitForMultipleObjects returned %#lx\n", ret ); + return ret; +} + static int KbdMessage( KEV kev, WPARAM *pwParam, LPARAM *plParam ) { UINT message; @@ -1471,13 +1583,10 @@ static void test_mouse_ll_hook(void) SetCursorPos(pt_org.x, pt_org.y); } -static void test_GetMouseMovePointsEx(const char *argv0) +static void test_GetMouseMovePointsEx( char **argv ) { #define BUFLIM 64 #define MYERROR 0xdeadbeef - PROCESS_INFORMATION process_info; - STARTUPINFOA startup_info; - char path[MAX_PATH]; int i, count, retval; MOUSEMOVEPOINT in; MOUSEMOVEPOINT out[200]; @@ -1695,16 +1804,7 @@ static void test_GetMouseMovePointsEx(const char *argv0) retval = pGetMouseMovePointsEx( sizeof(MOUSEMOVEPOINT), &in, out, BUFLIM, GMMP_USE_HIGH_RESOLUTION_POINTS ); todo_wine ok( retval == 64, "expected to get 64 high resolution mouse move points but got %d\n", retval ); - sprintf(path, "%s input get_mouse_move_points_test", argv0); - memset(&startup_info, 0, sizeof(startup_info)); - startup_info.cb = sizeof(startup_info); - startup_info.dwFlags = STARTF_USESHOWWINDOW; - startup_info.wShowWindow = SW_SHOWNORMAL; - retval = CreateProcessA(NULL, path, NULL, NULL, TRUE, 0, NULL, NULL, &startup_info, &process_info ); - ok(retval, "CreateProcess \"%s\" failed err %lu.\n", path, GetLastError()); - winetest_wait_child_process(process_info.hProcess); - CloseHandle(process_info.hProcess); - CloseHandle(process_info.hThread); + run_in_process( argv, "test_GetMouseMovePointsEx_process" ); #undef BUFLIM #undef MYERROR } @@ -3042,6 +3142,7 @@ static void test_key_map(void) #define shift 1 #define ctrl 2 +#define menu 4 static const struct tounicode_tests { @@ -3054,6 +3155,9 @@ static const struct tounicode_tests { { 0, 0, 'a', 1, {'a',0}}, { 0, shift, 'a', 1, {'A',0}}, + { 0, menu, 'a', 1, {'a',0}}, + { 0, shift|menu, 'a', 1, {'A',0}}, + { 0, shift|ctrl|menu, 'a', 0, {}}, { 0, ctrl, 'a', 1, {1, 0}}, { 0, shift|ctrl, 'a', 1, {1, 0}}, { VK_TAB, ctrl, 0, 0, {}}, @@ -3142,6 +3246,7 @@ static void test_ToUnicode(void) state[VK_SHIFT] = state[VK_LSHIFT] = (mod & shift) ? HIGHEST_BIT : 0; state[VK_CONTROL] = state[VK_LCONTROL] = (mod & ctrl) ? HIGHEST_BIT : 0; + state[VK_MENU] = state[VK_LMENU] = (mod & menu) ? HIGHEST_BIT : 0; ret = ToUnicode(vk, scan, state, wStr, 4, 0); ok(ret == utests[i].expect_ret, "%d: got %d expected %d\n", i, ret, utests[i].expect_ret); @@ -3302,7 +3407,20 @@ static void test_keyboard_layout_name(void) for (i = len - 1; i >= 0; --i) { id = (DWORD_PTR)layouts[i]; + + winetest_push_context( "%08lx", id ); + ActivateKeyboardLayout(layouts[i], 0); + + tmplayout = GetKeyboardLayout(0); + todo_wine_if(tmplayout != layouts[i]) + ok( tmplayout == layouts[i], "Failed to activate keyboard layout\n"); + if (tmplayout != layouts[i]) + { + winetest_pop_context(); + continue; + } + GetKeyboardLayoutNameW(klid); for (j = 0; j < len; ++j) @@ -3341,6 +3459,8 @@ static void test_keyboard_layout_name(void) /* The layout name only depends on the keyboard layout: the high word of HKL. */ GetKeyboardLayoutNameW(tmpklid); ok(!wcsicmp(klid, tmpklid), "GetKeyboardLayoutNameW returned %s, expected %s\n", debugstr_w(tmpklid), debugstr_w(klid)); + + winetest_pop_context(); } ActivateKeyboardLayout(layout, 0); @@ -3349,6 +3469,178 @@ static void test_keyboard_layout_name(void) free(layouts_preload); } +static HKL expect_hkl; +static HKL change_hkl; +static int got_setfocus; + +static LRESULT CALLBACK test_ActivateKeyboardLayout_window_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) +{ + ok( msg != WM_INPUTLANGCHANGEREQUEST, "got WM_INPUTLANGCHANGEREQUEST\n" ); + + if (msg == WM_SETFOCUS) got_setfocus = 1; + if (msg == WM_INPUTLANGCHANGE) + { + HKL layout = GetKeyboardLayout( 0 ); + CHARSETINFO info; + WCHAR klidW[64]; + UINT codepage; + LCID lcid; + + /* get keyboard layout lcid from its name, as the HKL might be aliased */ + GetKeyboardLayoutNameW( klidW ); + swscanf( klidW, L"%x", &lcid ); + lcid = LOWORD(lcid); + + if (!(HIWORD(layout) & 0x8000)) ok( lcid == HIWORD(layout), "got lcid %#lx\n", lcid ); + + GetLocaleInfoA( lcid, LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER, + (char *)&codepage, sizeof(codepage) ); + TranslateCharsetInfo( UlongToPtr( codepage ), &info, TCI_SRCCODEPAGE ); + + ok( !got_setfocus, "got WM_SETFOCUS before WM_INPUTLANGCHANGE\n" ); + ok( layout == expect_hkl, "got layout %p\n", layout ); + ok( wparam == info.ciCharset || broken(wparam == 0 && (HIWORD(layout) & 0x8000)), + "got wparam %#Ix\n", wparam ); + ok( lparam == (LPARAM)expect_hkl, "got lparam %#Ix\n", lparam ); + change_hkl = (HKL)lparam; + } + + return DefWindowProcW( hwnd, msg, wparam, lparam ); +} + +static DWORD CALLBACK test_ActivateKeyboardLayout_thread_proc( void *arg ) +{ + ActivateKeyboardLayout( arg, 0 ); + return 0; +} + +static void test_ActivateKeyboardLayout( char **argv ) +{ + HKL layout, tmp_layout, *layouts; + HWND hwnd1, hwnd2; + HANDLE thread; + UINT i, count; + DWORD ret; + + layout = GetKeyboardLayout( 0 ); + count = GetKeyboardLayoutList( 0, NULL ); + ok( count > 0, "GetKeyboardLayoutList returned %d\n", count ); + layouts = malloc( count * sizeof(HKL) ); + ok( layouts != NULL, "Could not allocate memory\n" ); + count = GetKeyboardLayoutList( count, layouts ); + ok( count > 0, "GetKeyboardLayoutList returned %d\n", count ); + + hwnd1 = CreateWindowA( "static", "static", WS_VISIBLE | WS_POPUP, + 100, 100, 100, 100, 0, NULL, NULL, NULL ); + ok( !!hwnd1, "CreateWindow failed, error %lu\n", GetLastError() ); + empty_message_queue(); + + SetWindowLongPtrA( hwnd1, GWLP_WNDPROC, (LONG_PTR)test_ActivateKeyboardLayout_window_proc ); + + for (i = 0; i < count; ++i) + { + BOOL broken_focus_activate = FALSE; + HKL other_layout = layouts[i]; + + winetest_push_context( "%08x / %08x", (UINT)(UINT_PTR)layout, (UINT)(UINT_PTR)other_layout ); + + /* test WM_INPUTLANGCHANGE message */ + + change_hkl = 0; + expect_hkl = other_layout; + got_setfocus = 0; + ActivateKeyboardLayout( other_layout, 0 ); + if (other_layout == layout) ok( change_hkl == 0, "got change_hkl %p\n", change_hkl ); + else todo_wine ok( change_hkl == other_layout, "got change_hkl %p\n", change_hkl ); + change_hkl = expect_hkl = 0; + + tmp_layout = GetKeyboardLayout( 0 ); + todo_wine_if(layout != other_layout) + ok( tmp_layout == other_layout, "got tmp_layout %p\n", tmp_layout ); + + /* changing the layout from another thread doesn't send the message */ + + thread = CreateThread( NULL, 0, test_ActivateKeyboardLayout_thread_proc, layout, 0, 0 ); + ret = WaitForSingleObject( thread, 1000 ); + ok( !ret, "WaitForSingleObject returned %#lx\n", ret ); + CloseHandle( thread ); + + /* and has no immediate effect */ + + empty_message_queue(); + tmp_layout = GetKeyboardLayout( 0 ); + todo_wine_if(layout != other_layout) + ok( tmp_layout == other_layout, "got tmp_layout %p\n", tmp_layout ); + + /* but the change only takes effect after focus changes */ + + hwnd2 = CreateWindowA( "static", "static", WS_VISIBLE | WS_POPUP, + 100, 100, 100, 100, 0, NULL, NULL, NULL ); + ok( !!hwnd2, "CreateWindow failed, error %lu\n", GetLastError() ); + + tmp_layout = GetKeyboardLayout( 0 ); + todo_wine_if(layout != other_layout) + ok( tmp_layout == layout || broken(layout != other_layout && tmp_layout == other_layout) /* w7u */, + "got tmp_layout %p\n", tmp_layout ); + if (broken(layout != other_layout && tmp_layout == other_layout)) + { + win_skip( "Broken layout activation on focus change, skipping some tests\n" ); + broken_focus_activate = TRUE; + } + empty_message_queue(); + + /* only the focused window receives the WM_INPUTLANGCHANGE message */ + + ActivateKeyboardLayout( other_layout, 0 ); + ok( change_hkl == 0, "got change_hkl %p\n", change_hkl ); + + tmp_layout = GetKeyboardLayout( 0 ); + todo_wine_if(layout != other_layout) + ok( tmp_layout == other_layout, "got tmp_layout %p\n", tmp_layout ); + + thread = CreateThread( NULL, 0, test_ActivateKeyboardLayout_thread_proc, layout, 0, 0 ); + ret = WaitForSingleObject( thread, 1000 ); + ok( !ret, "WaitForSingleObject returned %#lx\n", ret ); + CloseHandle( thread ); + + tmp_layout = GetKeyboardLayout( 0 ); + todo_wine_if(layout != other_layout) + ok( tmp_layout == other_layout, "got tmp_layout %p\n", tmp_layout ); + + /* changing focus is enough for the layout change to take effect */ + + change_hkl = 0; + expect_hkl = layout; + got_setfocus = 0; + SetFocus( hwnd1 ); + + if (broken_focus_activate) + { + ok( got_setfocus == 1, "got got_setfocus %d\n", got_setfocus ); + ok( change_hkl == 0, "got change_hkl %p\n", change_hkl ); + got_setfocus = 0; + ActivateKeyboardLayout( layout, 0 ); + } + + if (other_layout == layout) ok( change_hkl == 0, "got change_hkl %p\n", change_hkl ); + else todo_wine ok( change_hkl == layout, "got change_hkl %p\n", change_hkl ); + change_hkl = expect_hkl = 0; + + tmp_layout = GetKeyboardLayout( 0 ); + todo_wine_if(layout != other_layout) + ok( tmp_layout == layout, "got tmp_layout %p\n", tmp_layout ); + + DestroyWindow( hwnd2 ); + empty_message_queue(); + + winetest_pop_context(); + } + + DestroyWindow( hwnd1 ); + + free( layouts ); +} + static void test_key_names(void) { char buffer[40]; @@ -4158,7 +4450,7 @@ static DWORD WINAPI get_key_state_thread(void *arg) struct get_key_state_test_desc* test; HANDLE *semaphores = params->semaphores; DWORD result; - BYTE keystate[256]; + BYTE keystate[256] = {0}; BOOL has_queue; BOOL expect_x, expect_c; MSG msg; @@ -4220,7 +4512,7 @@ static void test_GetKeyState(void) struct get_key_state_thread_params params; HANDLE thread; DWORD result; - BYTE keystate[256]; + BYTE keystate[256] = {0}; BOOL expect_x, expect_c; HWND hwnd; MSG msg; @@ -4233,7 +4525,6 @@ static void test_GetKeyState(void) return; } - memset(keystate, 0, sizeof(keystate)); params.semaphores[0] = CreateSemaphoreA(NULL, 0, 1, NULL); ok(params.semaphores[0] != NULL, "CreateSemaphoreA failed %lu\n", GetLastError()); params.semaphores[1] = CreateSemaphoreA(NULL, 0, 1, NULL); @@ -4830,11 +5121,13 @@ static void test_GetPointerInfo( BOOL mouse_in_pointer_enabled ) ok( ret, "UnregisterClassW failed: %lu\n", GetLastError() ); } -static void test_EnableMouseInPointer_process( const char *arg ) +static void test_EnableMouseInPointer( const char *arg ) { DWORD enable = strtoul( arg, 0, 10 ); BOOL ret; + winetest_push_context( "enable %lu", enable ); + ret = pEnableMouseInPointer( enable ); todo_wine ok( ret, "EnableMouseInPointer failed, error %lu\n", GetLastError() ); @@ -4856,23 +5149,198 @@ static void test_EnableMouseInPointer_process( const char *arg ) ok( ret == enable, "IsMouseInPointerEnabled returned %u, error %lu\n", ret, GetLastError() ); test_GetPointerInfo( enable ); + + winetest_pop_context(); } -static void test_EnableMouseInPointer( char **argv, BOOL enable ) +static BOOL CALLBACK get_virtual_screen_proc( HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM lp ) { - STARTUPINFOA startup = {.cb = sizeof(STARTUPINFOA)}; - PROCESS_INFORMATION info = {0}; - char cmdline[MAX_PATH * 2]; - BOOL ret; + RECT *virtual_rect = (RECT *)lp; + UnionRect( virtual_rect, virtual_rect, rect ); + return TRUE; +} - sprintf( cmdline, "%s %s EnableMouseInPointer %u", argv[0], argv[1], enable ); - ret = CreateProcessA( NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &startup, &info ); - ok( ret, "CreateProcessA failed, error %lu\n", GetLastError() ); - if (!ret) return; +RECT get_virtual_screen_rect(void) +{ + RECT rect = {0}; + EnumDisplayMonitors( 0, NULL, get_virtual_screen_proc, (LPARAM)&rect ); + return rect; +} - wait_child_process( info.hProcess ); - CloseHandle( info.hThread ); - CloseHandle( info.hProcess ); +static void test_ClipCursor_dirty( const char *arg ) +{ + RECT rect, expect_rect = {1, 2, 3, 4}; + + /* check leaked clip rect from another desktop or process */ + ok_ret( 1, GetClipCursor( &rect ) ); + todo_wine_if( !strcmp( arg, "desktop" ) ) + ok_rect( expect_rect, rect ); + + /* intentionally leaking clipping rect */ +} + +static DWORD CALLBACK test_ClipCursor_thread( void *arg ) +{ + RECT rect, clip_rect, virtual_rect = get_virtual_screen_rect(); + HWND hwnd; + + clip_rect.left = clip_rect.right = (virtual_rect.left + virtual_rect.right) / 2; + clip_rect.top = clip_rect.bottom = (virtual_rect.top + virtual_rect.bottom) / 2; + + /* creating a window doesn't reset clipping rect */ + hwnd = CreateWindowW( L"static", NULL, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, + NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + ok_ret( 1, GetClipCursor( &rect ) ); + ok_rect( clip_rect, rect ); + + /* setting a window foreground does, even from the same process */ + ok_ret( 1, SetForegroundWindow( hwnd ) ); + ok_ret( 1, GetClipCursor( &rect ) ); + ok_rect( virtual_rect, rect ); + + /* destroying the window doesn't reset the clipping rect */ + InflateRect( &clip_rect, +1, +1 ); + ok_ret( 1, ClipCursor( &clip_rect ) ); + ok_ret( 1, DestroyWindow( hwnd ) ); + ok_ret( 1, GetClipCursor( &rect ) ); + ok_rect( clip_rect, rect ); + + /* intentionally leaking clipping rect */ + return 0; +} + +static void test_ClipCursor_process(void) +{ + RECT rect, clip_rect, virtual_rect = get_virtual_screen_rect(); + HWND hwnd, tmp_hwnd; + HANDLE thread; + + clip_rect.left = clip_rect.right = (virtual_rect.left + virtual_rect.right) / 2; + clip_rect.top = clip_rect.bottom = (virtual_rect.top + virtual_rect.bottom) / 2; + + ok_ret( 1, GetClipCursor( &rect ) ); + ok_rect( clip_rect, rect ); + + /* creating an invisible window doesn't reset clip cursor */ + hwnd = CreateWindowW( L"static", NULL, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, + NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + ok_ret( 1, DestroyWindow( hwnd ) ); + ok_ret( 1, GetClipCursor( &rect ) ); + ok_rect( clip_rect, rect ); + + /* setting a window foreground, even invisible, resets it */ + hwnd = CreateWindowW( L"static", NULL, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, + NULL, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + ok_ret( 1, SetForegroundWindow( hwnd ) ); + ok_ret( 1, GetClipCursor( &rect ) ); + ok_rect( virtual_rect, rect ); + + ok_ret( 1, ClipCursor( &clip_rect ) ); + + /* creating and setting another window foreground doesn't reset it */ + tmp_hwnd = CreateWindowW( L"static", NULL, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, + NULL, NULL, NULL, NULL ); + ok( !!tmp_hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + ok_ret( 1, SetForegroundWindow( tmp_hwnd ) ); + ok_ret( 1, DestroyWindow( tmp_hwnd ) ); + ok_ret( 1, GetClipCursor( &rect ) ); + ok_rect( clip_rect, rect ); + + /* but changing foreground to another thread in the same process reset it */ + thread = CreateThread( NULL, 0, test_ClipCursor_thread, NULL, 0, NULL ); + ok( !!thread, "CreateThread failed, error %lu\n", GetLastError() ); + msg_wait_for_events( 1, &thread, 5000 ); + + /* thread exit and foreground window destruction doesn't reset the clipping rect */ + InflateRect( &clip_rect, +1, +1 ); + ok_ret( 1, DestroyWindow( hwnd ) ); + ok_ret( 1, GetClipCursor( &rect ) ); + ok_rect( clip_rect, rect ); + + /* intentionally leaking clipping rect */ +} + +static void test_ClipCursor_desktop( char **argv ) +{ + RECT rect, clip_rect, virtual_rect = get_virtual_screen_rect(); + + ok_ret( 1, GetClipCursor( &rect ) ); + ok_rect( virtual_rect, rect ); + + /* ClipCursor clips rectangle to the virtual screen rect */ + clip_rect = virtual_rect; + InflateRect( &clip_rect, +1, +1 ); + ok_ret( 1, ClipCursor( &clip_rect ) ); + ok_ret( 1, GetClipCursor( &rect ) ); + ok_rect( virtual_rect, rect ); + + clip_rect = virtual_rect; + InflateRect( &clip_rect, -1, -1 ); + ok_ret( 1, ClipCursor( &clip_rect ) ); + ok_ret( 1, GetClipCursor( &rect ) ); + ok_rect( clip_rect, rect ); + + /* ClipCursor(NULL) resets to the virtual screen rect */ + ok_ret( 1, ClipCursor( NULL ) ); + ok_ret( 1, GetClipCursor( &rect ) ); + ok_rect( virtual_rect, rect ); + + clip_rect.left = clip_rect.right = (virtual_rect.left + virtual_rect.right) / 2; + clip_rect.top = clip_rect.bottom = (virtual_rect.top + virtual_rect.bottom) / 2; + ok_ret( 1, ClipCursor( &clip_rect ) ); + ok_ret( 1, GetClipCursor( &rect ) ); + ok_rect( clip_rect, rect ); + + /* ClipCursor rejects invalid rectangles */ + clip_rect.right -= 1; + clip_rect.bottom -= 1; + SetLastError( 0xdeadbeef ); + ok_ret( 0, ClipCursor( &clip_rect ) ); + todo_wine + ok_ret( ERROR_ACCESS_DENIED, GetLastError() ); + + /* which doesn't reset the previous clip rect */ + clip_rect.right += 1; + clip_rect.bottom += 1; + ok_ret( 1, GetClipCursor( &rect ) ); + ok_rect( clip_rect, rect ); + + /* running a process causes it to leak until foreground actually changes */ + run_in_process( argv, "test_ClipCursor_process" ); + + /* as foreground window is now transient, cursor clipping isn't reset */ + InflateRect( &clip_rect, +1, +1 ); + ok_ret( 1, GetClipCursor( &rect ) ); + ok_rect( clip_rect, rect ); + + /* intentionally leaking clipping rect */ +} + +static void test_ClipCursor( char **argv ) +{ + RECT rect, clip_rect = {1, 2, 3, 4}, virtual_rect = get_virtual_screen_rect(); + + ok_ret( 1, ClipCursor( &clip_rect ) ); + + /* running a new process doesn't reset clipping rectangle */ + run_in_process( argv, "test_ClipCursor_dirty process" ); + + /* running in a separate desktop, without switching desktop as well */ + run_in_desktop( argv, "test_ClipCursor_dirty desktop", 0 ); + + ok_ret( 1, GetClipCursor( &rect ) ); + ok_rect( clip_rect, rect ); + + /* running in a desktop and switching input resets the clipping rect */ + run_in_desktop( argv, "test_ClipCursor_desktop", 1 ); + + ok_ret( 1, GetClipCursor( &rect ) ); + todo_wine + ok_rect( virtual_rect, rect ); + if (!EqualRect( &rect, &virtual_rect )) ok_ret( 1, ClipCursor( NULL ) ); } START_TEST(input) @@ -4885,25 +5353,18 @@ START_TEST(input) GetCursorPos( &pos ); argc = winetest_get_mainargs(&argv); - if (argc >= 3 && strcmp(argv[2], "rawinput_test") == 0) - { - rawinput_test_process(); - return; - } - - if (argc >= 3 && strcmp(argv[2], "get_mouse_move_points_test") == 0) - { - test_GetMouseMovePointsEx_process(); - return; - } - - if (argc >= 4 && strcmp( argv[2], "EnableMouseInPointer" ) == 0) - { - winetest_push_context( "enable %s", argv[3] ); - test_EnableMouseInPointer_process( argv[3] ); - winetest_pop_context(); - return; - } + if (argc >= 3 && !strcmp( argv[2], "rawinput_test" )) + return rawinput_test_process(); + if (argc >= 3 && !strcmp( argv[2], "test_GetMouseMovePointsEx_process" )) + return test_GetMouseMovePointsEx_process(); + if (argc >= 4 && !strcmp( argv[2], "test_EnableMouseInPointer" )) + return test_EnableMouseInPointer( argv[3] ); + if (argc >= 4 && !strcmp( argv[2], "test_ClipCursor_dirty" )) + return test_ClipCursor_dirty( argv[3] ); + if (argc >= 3 && !strcmp( argv[2], "test_ClipCursor_process" )) + return test_ClipCursor_process(); + if (argc >= 3 && !strcmp( argv[2], "test_ClipCursor_desktop" )) + return test_ClipCursor_desktop( argv ); test_SendInput(); test_Input_blackbox(); @@ -4917,6 +5378,7 @@ START_TEST(input) test_ToAscii(); test_get_async_key_state(); test_keyboard_layout_name(); + test_ActivateKeyboardLayout( argv ); test_key_names(); test_attach_input(); test_GetKeyState(); @@ -4928,7 +5390,7 @@ START_TEST(input) test_DefRawInputProc(); if(pGetMouseMovePointsEx) - test_GetMouseMovePointsEx(argv[0]); + test_GetMouseMovePointsEx( argv ); else win_skip("GetMouseMovePointsEx is not available\n"); @@ -4955,7 +5417,9 @@ START_TEST(input) win_skip( "EnableMouseInPointer not found, skipping tests\n" ); else { - test_EnableMouseInPointer( argv, FALSE ); - test_EnableMouseInPointer( argv, TRUE ); + run_in_process( argv, "test_EnableMouseInPointer 0" ); + run_in_process( argv, "test_EnableMouseInPointer 1" ); } + + test_ClipCursor( argv ); } diff --git a/dlls/user32/tests/sysparams.c b/dlls/user32/tests/sysparams.c index 5c199a07769..6ed7755b91b 100644 --- a/dlls/user32/tests/sysparams.c +++ b/dlls/user32/tests/sysparams.c @@ -3123,7 +3123,8 @@ static void test_EnumDisplaySettings(void) { static const DWORD mode_fields = DM_DISPLAYORIENTATION | DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFLAGS | DM_DISPLAYFREQUENCY; - static const DWORD setting_fields = mode_fields | DM_POSITION; + static const DWORD setting_fields = DM_DISPLAYORIENTATION | DM_BITSPERPEL | + DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFLAGS | DM_DISPLAYFREQUENCY | DM_POSITION; CHAR primary_adapter[CCHDEVICENAME]; DPI_AWARENESS_CONTEXT ctx = NULL; DWORD err, val, device, mode; diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c index 1252e046ad3..31d14480861 100644 --- a/dlls/user32/tests/win.c +++ b/dlls/user32/tests/win.c @@ -3282,7 +3282,7 @@ static void test_SetWindowPos(HWND hwnd, HWND hwnd2) ret = SetWindowPos(hwnd_child, NULL, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE|SWP_SHOWWINDOW); ok(ret, "Got %d\n", ret); flush_events( TRUE ); - todo_wine check_active_state(hwnd2, hwnd2, hwnd2); + flaky todo_wine check_active_state(hwnd2, hwnd2, hwnd2); DestroyWindow(hwnd_child); } @@ -10186,7 +10186,9 @@ static void simulate_click(int x, int y) { INPUT input[2]; UINT events_no; + POINT pt; + GetCursorPos(&pt); SetCursorPos(x, y); memset(input, 0, sizeof(input)); input[0].type = INPUT_MOUSE; @@ -10199,6 +10201,7 @@ static void simulate_click(int x, int y) U(input[1]).mi.dwFlags = MOUSEEVENTF_LEFTUP; events_no = SendInput(2, input, sizeof(input[0])); ok(events_no == 2, "SendInput returned %d\n", events_no); + SetCursorPos(pt.x, pt.y); } static WNDPROC def_static_proc; diff --git a/dlls/user32/user_private.h b/dlls/user32/user_private.h index 430ac826f7e..c3f877758c1 100644 --- a/dlls/user32/user_private.h +++ b/dlls/user32/user_private.h @@ -55,10 +55,7 @@ extern HANDLE render_synthesized_format( UINT format, UINT from ) DECLSPEC_HIDDE extern void CLIPBOARD_ReleaseOwner( HWND hwnd ) DECLSPEC_HIDDEN; extern HDC get_display_dc(void) DECLSPEC_HIDDEN; extern void release_display_dc( HDC hdc ) DECLSPEC_HIDDEN; -extern void wait_graphics_driver_ready(void) DECLSPEC_HIDDEN; extern void *get_hook_proc( void *proc, const WCHAR *module, HMODULE *free_module ) DECLSPEC_HIDDEN; -extern RECT get_virtual_screen_rect(void) DECLSPEC_HIDDEN; -extern RECT get_primary_monitor_rect(void) DECLSPEC_HIDDEN; extern DWORD get_input_codepage( void ) DECLSPEC_HIDDEN; extern BOOL map_wparam_AtoW( UINT message, WPARAM *wparam, enum wm_char_mapping mapping ) DECLSPEC_HIDDEN; extern HPEN SYSCOLOR_GetPen( INT index ) DECLSPEC_HIDDEN; diff --git a/dlls/user32/win.c b/dlls/user32/win.c index 28cf40441d9..7eaaa41e951 100644 --- a/dlls/user32/win.c +++ b/dlls/user32/win.c @@ -888,7 +888,28 @@ WORD WINAPI GetWindowWord( HWND hwnd, INT offset ) /********************************************************************** * GetWindowLongA (USER32.@) */ -LONG WINAPI GetWindowLongA( HWND hwnd, INT offset ) + +#ifdef __i386__ + +/* This wrapper is here to workaround a ntlea quirk. First of all, ntlea + * checks whether GetWindowLongA starts with the Win32 hotpatchable prologue, + * if it can find that, it will use a hooking strategy more difficult for us + * to deal with. Secondly, it assumes what follows the prologue is a `pushl $-2`, + * and will try to skip over this instruction when calling `GetWindowLongA`, + * (i.e. it tries to jump to `GetWindowLongA + 7`, 5 bytes for the prologue, 2 + * bytes for the `pushl`.). We have to anticipate that and make sure the result + * of doing this won't be a messed up stack, or a desynced PC. + */ +__ASM_STDCALL_FUNC( GetWindowLongA, 8, + ".byte 0x8b, 0xff, 0x55, 0x8b, 0xec\n" /* Win32 hotpatchable prologue. */ + "pushl $-2\n" + "addl $4, %esp\n" + "popl %ebp\n" + "jmp " __ASM_STDCALL("get_window_longA", 8) ) +LONG WINAPI get_window_longA( HWND hwnd, INT offset ) +#else +LONG WINAPI DECLSPEC_HOTPATCH GetWindowLongA( HWND hwnd, INT offset ) +#endif { switch (offset) { diff --git a/dlls/user32/winproc.c b/dlls/user32/winproc.c index 6cd41b51435..83cc9533fe2 100644 --- a/dlls/user32/winproc.c +++ b/dlls/user32/winproc.c @@ -831,7 +831,7 @@ static BOOL unpack_message( HWND hwnd, UINT message, WPARAM *wparam, LPARAM *lpa if (!check_string( str, size )) return FALSE; cs.lpszClass = str; } - memcpy( &ps->cs, &cs, sizeof(cs) ); + memcpy( *buffer, &cs, sizeof(cs) ); break; } case WM_GETTEXT: @@ -865,7 +865,7 @@ static BOOL unpack_message( HWND hwnd, UINT message, WPARAM *wparam, LPARAM *lpa dis.hDC = unpack_handle( ps->dis.hDC ); dis.rcItem = ps->dis.rcItem; dis.itemData = (ULONG_PTR)unpack_ptr( ps->dis.itemData ); - memcpy( &ps->dis, &dis, sizeof(dis) ); + memcpy( *buffer, &dis, sizeof(dis) ); break; } case WM_MEASUREITEM: @@ -878,7 +878,7 @@ static BOOL unpack_message( HWND hwnd, UINT message, WPARAM *wparam, LPARAM *lpa mis.itemWidth = ps->mis.itemWidth; mis.itemHeight = ps->mis.itemHeight; mis.itemData = (ULONG_PTR)unpack_ptr( ps->mis.itemData ); - memcpy( &ps->mis, &mis, sizeof(mis) ); + memcpy( *buffer, &mis, sizeof(mis) ); break; } case WM_DELETEITEM: @@ -890,7 +890,7 @@ static BOOL unpack_message( HWND hwnd, UINT message, WPARAM *wparam, LPARAM *lpa dls.itemID = ps->dls.itemID; dls.hwndItem = unpack_handle( ps->dls.hwndItem ); dls.itemData = (ULONG_PTR)unpack_ptr( ps->dls.itemData ); - memcpy( &ps->dls, &dls, sizeof(dls) ); + memcpy( *buffer, &dls, sizeof(dls) ); break; } case WM_COMPAREITEM: @@ -905,7 +905,7 @@ static BOOL unpack_message( HWND hwnd, UINT message, WPARAM *wparam, LPARAM *lpa cis.itemID2 = ps->cis.itemID2; cis.itemData2 = (ULONG_PTR)unpack_ptr( ps->cis.itemData2 ); cis.dwLocaleId = ps->cis.dwLocaleId; - memcpy( &ps->cis, &cis, sizeof(cis) ); + memcpy( *buffer, &cis, sizeof(cis) ); break; } case WM_WINDOWPOSCHANGING: @@ -920,7 +920,7 @@ static BOOL unpack_message( HWND hwnd, UINT message, WPARAM *wparam, LPARAM *lpa wp.cx = ps->wp.cx; wp.cy = ps->wp.cy; wp.flags = ps->wp.flags; - memcpy( &ps->wp, &wp, sizeof(wp) ); + memcpy( *buffer, &wp, sizeof(wp) ); break; } case WM_COPYDATA: @@ -1080,7 +1080,7 @@ static BOOL unpack_message( HWND hwnd, UINT message, WPARAM *wparam, LPARAM *lpa mnm.hmenuIn = unpack_handle( ps->mnm.hmenuIn ); mnm.hmenuNext = unpack_handle( ps->mnm.hmenuNext ); mnm.hwndNext = unpack_handle( ps->mnm.hwndNext ); - memcpy( &ps->mnm, &mnm, sizeof(mnm) ); + memcpy( *buffer, &mnm, sizeof(mnm) ); break; } case WM_SIZING: @@ -1116,7 +1116,7 @@ static BOOL unpack_message( HWND hwnd, UINT message, WPARAM *wparam, LPARAM *lpa if (!check_string( str, size )) return FALSE; mcs.szTitle = str; } - memcpy( &ps->mcs, &mcs, sizeof(mcs) ); + memcpy( *buffer, &mcs, sizeof(mcs) ); break; } case WM_MDIGETACTIVE: @@ -1251,7 +1251,7 @@ BOOL WINAPI User32CallSendAsyncCallback( const struct send_async_params *params, * * ECMA-234, Win32 */ -LRESULT WINAPI CallWindowProcA( WNDPROC func, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) +LRESULT WINAPI DECLSPEC_HOTPATCH CallWindowProcA( WNDPROC func, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) { struct win_proc_params params; LRESULT result; diff --git a/dlls/user32/winstation.c b/dlls/user32/winstation.c index 23844482f2c..62593ca046f 100644 --- a/dlls/user32/winstation.c +++ b/dlls/user32/winstation.c @@ -256,7 +256,7 @@ HDESK WINAPI CreateDesktopW( LPCWSTR name, LPCWSTR device, LPDEVMODEW devmode, OBJECT_ATTRIBUTES attr; UNICODE_STRING str; - if (device || devmode) + if (device || (devmode && !(flags & DF_WINE_CREATE_DESKTOP))) { SetLastError( ERROR_INVALID_PARAMETER ); return 0; diff --git a/dlls/win32u/class.c b/dlls/win32u/class.c index 9f3b4251e65..396e2285797 100644 --- a/dlls/win32u/class.c +++ b/dlls/win32u/class.c @@ -108,7 +108,7 @@ static WINDOWPROC *find_winproc( WNDPROC func, BOOL ansi ) /* return the window proc for a given handle, or NULL for an invalid handle, * or WINPROC_PROC16 for a handle to a 16-bit proc. */ -WINDOWPROC *get_winproc_ptr( WNDPROC handle ) +static WINDOWPROC *get_winproc_ptr( WNDPROC handle ) { UINT index = LOWORD(handle); if ((ULONG_PTR)handle >> 16 != WINPROC_HANDLE) return NULL; diff --git a/dlls/win32u/clipboard.c b/dlls/win32u/clipboard.c index d53cd966d36..6cf484a56ca 100644 --- a/dlls/win32u/clipboard.c +++ b/dlls/win32u/clipboard.c @@ -720,7 +720,11 @@ HANDLE WINAPI NtUserGetClipboardData( UINT format, struct get_clipboard_params * params->data_size = size; return 0; } - if (status == STATUS_OBJECT_NAME_NOT_FOUND) return 0; /* no such format */ + if (status == STATUS_OBJECT_NAME_NOT_FOUND) + { + RtlSetLastWin32Error( ERROR_NOT_FOUND ); /* no such format */ + return 0; + } if (status) { RtlSetLastWin32Error( RtlNtStatusToDosError( status )); diff --git a/dlls/win32u/cursoricon.c b/dlls/win32u/cursoricon.c index eb1d07afb6c..fa51788dce9 100644 --- a/dlls/win32u/cursoricon.c +++ b/dlls/win32u/cursoricon.c @@ -74,12 +74,18 @@ static struct cursoricon_object *get_icon_ptr( HICON handle ) return obj; } +BOOL process_wine_setcursor( HWND hwnd, HWND window, HCURSOR handle ) +{ + TRACE( "hwnd %p, window %p, hcursor %p\n", hwnd, window, handle ); + user_driver->pSetCursor( window, handle ); + return TRUE; +} + /*********************************************************************** * NtUserShowCursor (win32u.@) */ INT WINAPI NtUserShowCursor( BOOL show ) { - HCURSOR cursor; int increment = show ? 1 : -1; int count; @@ -88,16 +94,11 @@ INT WINAPI NtUserShowCursor( BOOL show ) req->flags = SET_CURSOR_COUNT; req->show_count = increment; wine_server_call( req ); - cursor = wine_server_ptr_handle( reply->prev_handle ); count = reply->prev_count + increment; } SERVER_END_REQ; TRACE("%d, count=%d\n", show, count ); - - if (show && !count) user_driver->pSetCursor( cursor ); - else if (!show && count == -1) user_driver->pSetCursor( 0 ); - return count; } @@ -108,7 +109,6 @@ HCURSOR WINAPI NtUserSetCursor( HCURSOR cursor ) { struct cursoricon_object *obj; HCURSOR old_cursor; - int show_count; BOOL ret; TRACE( "%p\n", cursor ); @@ -118,16 +118,11 @@ HCURSOR WINAPI NtUserSetCursor( HCURSOR cursor ) req->flags = SET_CURSOR_HANDLE; req->handle = wine_server_user_handle( cursor ); if ((ret = !wine_server_call_err( req ))) - { old_cursor = wine_server_ptr_handle( reply->prev_handle ); - show_count = reply->prev_count; - } } SERVER_END_REQ; if (!ret) return 0; - user_driver->pSetCursor( show_count >= 0 ? cursor : 0 ); - if (!(obj = get_icon_ptr( old_cursor ))) return 0; release_user_handle_ptr( obj ); return old_cursor; @@ -150,78 +145,6 @@ HCURSOR WINAPI NtUserGetCursor(void) return ret; } -/*********************************************************************** - * NtUserClipCursor (win32u.@) - */ -BOOL WINAPI NtUserClipCursor( const RECT *rect ) -{ - UINT dpi; - BOOL ret; - RECT new_rect; - - TRACE( "Clipping to %s\n", wine_dbgstr_rect(rect) ); - - if (rect) - { - if (rect->left > rect->right || rect->top > rect->bottom) return FALSE; - if ((dpi = get_thread_dpi())) - { - HMONITOR monitor = monitor_from_rect( rect, MONITOR_DEFAULTTOPRIMARY, dpi ); - new_rect = map_dpi_rect( *rect, dpi, get_monitor_dpi( monitor )); - rect = &new_rect; - } - } - - SERVER_START_REQ( set_cursor ) - { - req->clip_msg = WM_WINE_CLIPCURSOR; - if (rect) - { - req->flags = SET_CURSOR_CLIP; - req->clip.left = rect->left; - req->clip.top = rect->top; - req->clip.right = rect->right; - req->clip.bottom = rect->bottom; - } - else req->flags = SET_CURSOR_NOCLIP; - - if ((ret = !wine_server_call( req ))) - { - new_rect.left = reply->new_clip.left; - new_rect.top = reply->new_clip.top; - new_rect.right = reply->new_clip.right; - new_rect.bottom = reply->new_clip.bottom; - } - } - SERVER_END_REQ; - if (ret) user_driver->pClipCursor( &new_rect ); - return ret; -} - -BOOL get_clip_cursor( RECT *rect ) -{ - volatile struct desktop_shared_memory *shared = get_desktop_shared_memory(); - UINT dpi; - - if (!rect || !shared) return FALSE; - - SHARED_READ_BEGIN( &shared->seq ) - { - rect->left = shared->cursor.clip.left; - rect->top = shared->cursor.clip.top; - rect->right = shared->cursor.clip.right; - rect->bottom = shared->cursor.clip.bottom; - } - SHARED_READ_END( &shared->seq ); - - if ((dpi = get_thread_dpi())) - { - HMONITOR monitor = monitor_from_rect( rect, MONITOR_DEFAULTTOPRIMARY, 0 ); - *rect = map_dpi_rect( *rect, get_monitor_dpi( monitor ), dpi ); - } - return TRUE; -} - HICON alloc_cursoricon_handle( BOOL is_icon ) { struct cursoricon_object *obj; diff --git a/dlls/win32u/dce.c b/dlls/win32u/dce.c index 773d6e0b059..d7aa75b3039 100644 --- a/dlls/win32u/dce.c +++ b/dlls/win32u/dce.c @@ -1725,7 +1725,7 @@ INT WINAPI NtUserScrollWindowEx( HWND hwnd, INT dx, INT dy, const RECT *rect, TRACE( "%p, %d,%d update_rgn=%p update_rect = %p %s %04x\n", hwnd, dx, dy, update_rgn, update_rect, wine_dbgstr_rect(rect), flags ); TRACE( "clip_rect = %s\n", wine_dbgstr_rect(clip_rect) ); - if (flags & ~(SW_SCROLLCHILDREN | SW_INVALIDATE | SW_ERASE)) + if (flags & ~(SW_SCROLLCHILDREN | SW_INVALIDATE | SW_ERASE | SW_NODCCACHE)) FIXME( "some flags (%04x) are unhandled\n", flags ); rdw_flags = (flags & SW_ERASE) && (flags & SW_INVALIDATE) ? diff --git a/dlls/win32u/defwnd.c b/dlls/win32u/defwnd.c index b28d02a79f4..eb1c082ff20 100644 --- a/dlls/win32u/defwnd.c +++ b/dlls/win32u/defwnd.c @@ -1302,7 +1302,7 @@ static BOOL draw_push_button( HDC dc, RECT *r, UINT flags ) return TRUE; } -BOOL draw_frame_caption( HDC dc, RECT *r, UINT flags ) +static BOOL draw_frame_caption( HDC dc, RECT *r, UINT flags ) { RECT rect; int small_diam = make_square_rect( r, &rect ) - 2; @@ -2943,12 +2943,10 @@ LRESULT default_window_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, case WM_IME_COMPOSITION: case WM_IME_STARTCOMPOSITION: case WM_IME_ENDCOMPOSITION: - case WM_IME_SELECT: case WM_IME_NOTIFY: - case WM_IME_CONTROL: { HWND ime_hwnd = get_default_ime_window( hwnd ); - if (ime_hwnd) + if (ime_hwnd && ime_hwnd != NtUserGetParent( hwnd )) result = NtUserMessageCall( ime_hwnd, msg, wparam, lparam, 0, NtUserSendMessage, ansi ); } diff --git a/dlls/win32u/driver.c b/dlls/win32u/driver.c index 1c0708461a1..2a1da075dd4 100644 --- a/dlls/win32u/driver.c +++ b/dlls/win32u/driver.c @@ -716,6 +716,20 @@ static SHORT nulldrv_VkKeyScanEx( WCHAR ch, HKL layout ) return -256; /* use default implementation */ } +static UINT nulldrv_ImeProcessKey( HIMC himc, UINT wparam, UINT lparam, const BYTE *state ) +{ + return 0; +} + +static UINT nulldrv_ImeToAsciiEx( UINT vkey, UINT vsc, const BYTE *state, COMPOSITIONSTRING *compstr, HIMC himc ) +{ + return 0; +} + +static void nulldrv_NotifyIMEStatus( HWND hwnd, UINT status ) +{ +} + static LRESULT nulldrv_DesktopWindowProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) { return default_window_proc( hwnd, msg, wparam, lparam, FALSE ); @@ -725,7 +739,7 @@ static void nulldrv_DestroyCursorIcon( HCURSOR cursor ) { } -static void nulldrv_SetCursor( HCURSOR cursor ) +static void nulldrv_SetCursor( HWND hwnd, HCURSOR cursor ) { } @@ -739,7 +753,7 @@ static BOOL nulldrv_SetCursorPos( INT x, INT y ) return TRUE; } -static BOOL nulldrv_ClipCursor( LPCRECT clip ) +static BOOL nulldrv_ClipCursor( const RECT *clip, BOOL reset ) { return TRUE; } @@ -761,7 +775,7 @@ static BOOL nulldrv_GetCurrentDisplaySettings( LPCWSTR name, BOOL is_primary, LP static INT nulldrv_GetDisplayDepth( LPCWSTR name, BOOL is_primary ) { - return 32; + return -1; /* use default implementation */ } static BOOL nulldrv_UpdateDisplayDevices( const struct gdi_device_manager *manager, BOOL force, void *param ) @@ -769,7 +783,7 @@ static BOOL nulldrv_UpdateDisplayDevices( const struct gdi_device_manager *manag return FALSE; } -static BOOL nulldrv_CreateDesktopWindow( HWND hwnd ) +static BOOL nulldrv_CreateDesktop( const WCHAR *name, UINT width, UINT height ) { return TRUE; } @@ -828,6 +842,10 @@ static void nulldrv_SetCapture( HWND hwnd, UINT flags ) { } +static void nulldrv_SetDesktopWindow( HWND hwnd ) +{ +} + static void nulldrv_SetFocus( HWND hwnd ) { } @@ -923,6 +941,8 @@ static const WCHAR guid_key_suffixW[] = {'}','\\','0','0','0','0'}; static BOOL load_desktop_driver( HWND hwnd ) { + static const WCHAR guid_nullW[] = {'0','0','0','0','0','0','0','0','-','0','0','0','0','-','0','0','0','0','-', + '0','0','0','0','-','0','0','0','0','0','0','0','0','0','0','0','0',0}; WCHAR key[ARRAYSIZE(guid_key_prefixW) + 40 + ARRAYSIZE(guid_key_suffixW)], *ptr; char buf[4096]; KEY_VALUE_PARTIAL_INFORMATION *info = (void *)buf; @@ -946,9 +966,15 @@ static BOOL load_desktop_driver( HWND hwnd ) memcpy( key, guid_key_prefixW, sizeof(guid_key_prefixW) ); ptr = key + ARRAYSIZE(guid_key_prefixW); if (NtQueryInformationAtom( guid_atom, AtomBasicInformation, buf, sizeof(buf), NULL )) - return FALSE; - memcpy( ptr, abi->Name, abi->NameLength ); - ptr += abi->NameLength / sizeof(WCHAR); + { + wcscpy( ptr, guid_nullW ); + ptr += ARRAY_SIZE(guid_nullW) - 1; + } + else + { + memcpy( ptr, abi->Name, abi->NameLength ); + ptr += abi->NameLength / sizeof(WCHAR); + } memcpy( ptr, guid_key_suffixW, sizeof(guid_key_suffixW) ); ptr += ARRAY_SIZE(guid_key_suffixW); @@ -1062,6 +1088,21 @@ static SHORT loaderdrv_VkKeyScanEx( WCHAR ch, HKL layout ) return load_driver()->pVkKeyScanEx( ch, layout ); } +static UINT loaderdrv_ImeProcessKey( HIMC himc, UINT wparam, UINT lparam, const BYTE *state ) +{ + return load_driver()->pImeProcessKey( himc, wparam, lparam, state ); +} + +static UINT loaderdrv_ImeToAsciiEx( UINT vkey, UINT vsc, const BYTE *state, COMPOSITIONSTRING *compstr, HIMC himc ) +{ + return load_driver()->pImeToAsciiEx( vkey, vsc, state, compstr, himc ); +} + +static void loaderdrv_NotifyIMEStatus( HWND hwnd, UINT status ) +{ + return load_driver()->pNotifyIMEStatus( hwnd, status ); +} + static LONG loaderdrv_ChangeDisplaySettings( LPDEVMODEW displays, LPCWSTR primary_name, HWND hwnd, DWORD flags, LPVOID lparam ) { @@ -1078,9 +1119,9 @@ static INT loaderdrv_GetDisplayDepth( LPCWSTR name, BOOL is_primary ) return load_driver()->pGetDisplayDepth( name, is_primary ); } -static void loaderdrv_SetCursor( HCURSOR cursor ) +static void loaderdrv_SetCursor( HWND hwnd, HCURSOR cursor ) { - load_driver()->pSetCursor( cursor ); + load_driver()->pSetCursor( hwnd, cursor ); } static BOOL loaderdrv_GetCursorPos( POINT *pt ) @@ -1093,9 +1134,9 @@ static BOOL loaderdrv_SetCursorPos( INT x, INT y ) return load_driver()->pSetCursorPos( x, y ); } -static BOOL loaderdrv_ClipCursor( const RECT *clip ) +static BOOL loaderdrv_ClipCursor( const RECT *clip, BOOL reset ) { - return load_driver()->pClipCursor( clip ); + return load_driver()->pClipCursor( clip, reset ); } static LRESULT nulldrv_ClipboardWindowProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) @@ -1113,9 +1154,9 @@ static BOOL loaderdrv_UpdateDisplayDevices( const struct gdi_device_manager *man return load_driver()->pUpdateDisplayDevices( manager, force, param ); } -static BOOL loaderdrv_CreateDesktopWindow( HWND hwnd ) +static BOOL loaderdrv_CreateDesktop( const WCHAR *name, UINT width, UINT height ) { - return load_driver()->pCreateDesktopWindow( hwnd ); + return load_driver()->pCreateDesktop( name, width, height ); } static BOOL loaderdrv_CreateWindow( HWND hwnd ) @@ -1134,6 +1175,11 @@ static void loaderdrv_FlashWindowEx( FLASHWINFO *info ) load_driver()->pFlashWindowEx( info ); } +static void loaderdrv_SetDesktopWindow( HWND hwnd ) +{ + load_driver()->pSetDesktopWindow( hwnd ); +} + static void loaderdrv_SetLayeredWindowAttributes( HWND hwnd, COLORREF key, BYTE alpha, DWORD flags ) { load_driver()->pSetLayeredWindowAttributes( hwnd, key, alpha, flags ); @@ -1168,6 +1214,9 @@ static const struct user_driver_funcs lazy_load_driver = loaderdrv_ToUnicodeEx, loaderdrv_UnregisterHotKey, loaderdrv_VkKeyScanEx, + loaderdrv_ImeProcessKey, + loaderdrv_ImeToAsciiEx, + loaderdrv_NotifyIMEStatus, /* cursor/icon functions */ nulldrv_DestroyCursorIcon, loaderdrv_SetCursor, @@ -1183,7 +1232,7 @@ static const struct user_driver_funcs lazy_load_driver = loaderdrv_GetDisplayDepth, loaderdrv_UpdateDisplayDevices, /* windowing functions */ - loaderdrv_CreateDesktopWindow, + loaderdrv_CreateDesktop, loaderdrv_CreateWindow, nulldrv_DesktopWindowProc, nulldrv_DestroyWindow, @@ -1193,6 +1242,7 @@ static const struct user_driver_funcs lazy_load_driver = nulldrv_ReleaseDC, nulldrv_ScrollDC, nulldrv_SetCapture, + loaderdrv_SetDesktopWindow, nulldrv_SetFocus, loaderdrv_SetLayeredWindowAttributes, nulldrv_SetParent, @@ -1233,7 +1283,7 @@ void __wine_set_user_driver( const struct user_driver_funcs *funcs, UINT version } driver = malloc( sizeof(*driver) ); - *driver = *funcs; + *driver = funcs ? *funcs : null_user_driver; #define SET_USER_FUNC(name) \ do { if (!driver->p##name) driver->p##name = nulldrv_##name; } while(0) @@ -1247,6 +1297,9 @@ void __wine_set_user_driver( const struct user_driver_funcs *funcs, UINT version SET_USER_FUNC(ToUnicodeEx); SET_USER_FUNC(UnregisterHotKey); SET_USER_FUNC(VkKeyScanEx); + SET_USER_FUNC(ImeProcessKey); + SET_USER_FUNC(ImeToAsciiEx); + SET_USER_FUNC(NotifyIMEStatus); SET_USER_FUNC(DestroyCursorIcon); SET_USER_FUNC(SetCursor); SET_USER_FUNC(GetCursorPos); @@ -1258,7 +1311,7 @@ void __wine_set_user_driver( const struct user_driver_funcs *funcs, UINT version SET_USER_FUNC(GetCurrentDisplaySettings); SET_USER_FUNC(GetDisplayDepth); SET_USER_FUNC(UpdateDisplayDevices); - SET_USER_FUNC(CreateDesktopWindow); + SET_USER_FUNC(CreateDesktop); SET_USER_FUNC(CreateWindow); SET_USER_FUNC(DesktopWindowProc); SET_USER_FUNC(DestroyWindow); @@ -1268,6 +1321,7 @@ void __wine_set_user_driver( const struct user_driver_funcs *funcs, UINT version SET_USER_FUNC(ReleaseDC); SET_USER_FUNC(ScrollDC); SET_USER_FUNC(SetCapture); + SET_USER_FUNC(SetDesktopWindow); SET_USER_FUNC(SetFocus); SET_USER_FUNC(SetLayeredWindowAttributes); SET_USER_FUNC(SetParent); diff --git a/dlls/win32u/font.c b/dlls/win32u/font.c index 679da4cc83a..d079c41f35a 100644 --- a/dlls/win32u/font.c +++ b/dlls/win32u/font.c @@ -6548,9 +6548,9 @@ static void load_system_bitmap_fonts(void) static void load_directory_fonts( WCHAR *path, UINT flags ) { + IO_STATUS_BLOCK io = {{0}}; OBJECT_ATTRIBUTES attr; UNICODE_STRING nt_name; - IO_STATUS_BLOCK io; HANDLE handle; char buf[8192]; size_t len; @@ -7101,7 +7101,7 @@ BOOL WINAPI NtGdiGetCharWidthInfo( HDC hdc, struct char_width_info *info ) /*********************************************************************** * DrawTextW (win32u.so) */ -INT WINAPI DrawTextW( HDC hdc, const WCHAR *str, INT count, RECT *rect, UINT flags ) +INT WINAPI DECLSPEC_HIDDEN DrawTextW( HDC hdc, const WCHAR *str, INT count, RECT *rect, UINT flags ) { struct draw_text_params *params; ULONG ret_len, size; diff --git a/dlls/win32u/imm.c b/dlls/win32u/imm.c index db077dbbef0..287596e60ef 100644 --- a/dlls/win32u/imm.c +++ b/dlls/win32u/imm.c @@ -25,6 +25,8 @@ #endif #include +#include "ntstatus.h" +#define WIN32_NO_STATUS #include "win32u_private.h" #include "ntuser_private.h" #include "immdev.h" @@ -277,12 +279,11 @@ BOOL register_imm_window( HWND hwnd ) /* Create default IME window */ if (!thread_data->window_cnt++) { - UNICODE_STRING class_name, name; static const WCHAR imeW[] = {'I','M','E',0}; static const WCHAR default_imeW[] = {'D','e','f','a','u','l','t',' ','I','M','E',0}; + UNICODE_STRING class_name = RTL_CONSTANT_STRING( imeW ); + UNICODE_STRING name = RTL_CONSTANT_STRING( default_imeW ); - RtlInitUnicodeString( &class_name, imeW ); - RtlInitUnicodeString( &name, default_imeW ); thread_data->default_hwnd = NtUserCreateWindowEx( 0, &class_name, &class_name, &name, WS_POPUP | WS_DISABLED | WS_CLIPSIBLINGS, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, FALSE ); @@ -394,7 +395,56 @@ void cleanup_imm_thread(void) NtUserDestroyInputContext( UlongToHandle( thread_info->client_info.default_imc )); } -BOOL WINAPI ImmProcessKey( HWND hwnd, HKL hkl, UINT vkey, LPARAM key_data, DWORD unknown ) +/***************************************************************************** + * NtUserBuildHimcList (win32u.@) + */ +NTSTATUS WINAPI NtUserBuildHimcList( UINT thread_id, UINT count, HIMC *buffer, UINT *size ) +{ + HANDLE handle = 0; + struct imc *imc; + + TRACE( "thread_id %#x, count %u, buffer %p, size %p\n", thread_id, count, buffer, size ); + + if (!buffer) return STATUS_UNSUCCESSFUL; + if (!thread_id) thread_id = GetCurrentThreadId(); + + *size = 0; + user_lock(); + while (count && (imc = next_process_user_handle_ptr( &handle, NTUSER_OBJ_IMC ))) + { + if (thread_id != -1 && imc->thread_id != thread_id) continue; + buffer[(*size)++] = handle; + count--; + } + user_unlock(); + + return STATUS_SUCCESS; +} + +LRESULT ime_driver_call( HWND hwnd, enum wine_ime_call call, WPARAM wparam, LPARAM lparam, + struct ime_driver_call_params *params ) +{ + switch (call) + { + case WINE_IME_PROCESS_KEY: + return user_driver->pImeProcessKey( params->himc, wparam, lparam, params->state ); + case WINE_IME_TO_ASCII_EX: + return user_driver->pImeToAsciiEx( wparam, lparam, params->state, params->compstr, params->himc ); + default: + ERR( "Unknown IME driver call %#x\n", call ); + return 0; + } +} + +/***************************************************************************** + * NtUserNotifyIMEStatus (win32u.@) + */ +void WINAPI NtUserNotifyIMEStatus( HWND hwnd, UINT status ) +{ + user_driver->pNotifyIMEStatus( hwnd, status ); +} + +BOOL WINAPI DECLSPEC_HIDDEN ImmProcessKey( HWND hwnd, HKL hkl, UINT vkey, LPARAM key_data, DWORD unknown ) { struct imm_process_key_params params = { .hwnd = hwnd, .hkl = hkl, .vkey = vkey, .key_data = key_data }; @@ -403,7 +453,7 @@ BOOL WINAPI ImmProcessKey( HWND hwnd, HKL hkl, UINT vkey, LPARAM key_data, DWORD return KeUserModeCallback( NtUserImmProcessKey, ¶ms, sizeof(params), &ret_ptr, &ret_len ); } -BOOL WINAPI ImmTranslateMessage( HWND hwnd, UINT msg, WPARAM wparam, LPARAM key_data ) +BOOL WINAPI DECLSPEC_HIDDEN ImmTranslateMessage( HWND hwnd, UINT msg, WPARAM wparam, LPARAM key_data ) { struct imm_translate_message_params params = { .hwnd = hwnd, .msg = msg, .wparam = wparam, .key_data = key_data }; diff --git a/dlls/win32u/input.c b/dlls/win32u/input.c index 94554fa4c5d..c3ee888557a 100644 --- a/dlls/win32u/input.c +++ b/dlls/win32u/input.c @@ -135,8 +135,8 @@ static const VK_TO_BIT vk_to_bit[] = static const MODIFIERS modifiers = { .pVkToBit = (VK_TO_BIT *)vk_to_bit, - .wMaxModBits = 3, - .ModNumber = {0, 1, 2, 3}, + .wMaxModBits = 7, + .ModNumber = {0, 1, 2, 3, 0, 1, 0, 0}, }; static const VK_TO_WCHARS2 vk_to_wchars2[] = @@ -398,12 +398,16 @@ static const KBDTABLES kbdus_tables = .pKeyNames = (VSC_LPWSTR *)key_names, .pKeyNamesExt = (VSC_LPWSTR *)key_names_ext, .pusVSCtoVK = (USHORT *)vsc_to_vk, - .bMaxVSCtoVK = sizeof(vsc_to_vk) / sizeof(vsc_to_vk[0]), + .bMaxVSCtoVK = ARRAY_SIZE(vsc_to_vk), .pVSCtoVK_E0 = (VSC_VK *)vsc_to_vk_e0, .pVSCtoVK_E1 = (VSC_VK *)vsc_to_vk_e1, .fLocaleFlags = MAKELONG(0, KBD_VERSION), }; +static LONG clipping_cursor; /* clipping thread counter */ + +BOOL grab_pointer = TRUE; +BOOL grab_fullscreen = FALSE; static void kbd_tables_init_vsc2vk( const KBDTABLES *tables, BYTE vsc2vk[0x300] ) { @@ -534,6 +538,49 @@ static WCHAR kbd_tables_vkey_to_wchar( const KBDTABLES *tables, UINT vkey, const BOOL enable_mouse_in_pointer = FALSE; +/******************************************************************* + * NtUserGetForegroundWindow (win32u.@) + */ +HWND WINAPI NtUserGetForegroundWindow(void) +{ + volatile struct input_shared_memory *shared = get_foreground_shared_memory(); + HWND ret = 0; + + if (!shared) return 0; + + SHARED_READ_BEGIN( &shared->seq ) + { + ret = wine_server_ptr_handle( shared->active ); + } + SHARED_READ_END( &shared->seq ); + + return ret; +} + +/* see GetActiveWindow */ +HWND get_active_window(void) +{ + GUITHREADINFO info; + info.cbSize = sizeof(info); + return NtUserGetGUIThreadInfo( GetCurrentThreadId(), &info ) ? info.hwndActive : 0; +} + +/* see GetCapture */ +HWND get_capture(void) +{ + GUITHREADINFO info; + info.cbSize = sizeof(info); + return NtUserGetGUIThreadInfo( GetCurrentThreadId(), &info ) ? info.hwndCapture : 0; +} + +/* see GetFocus */ +HWND get_focus(void) +{ + GUITHREADINFO info; + info.cbSize = sizeof(info); + return NtUserGetGUIThreadInfo( GetCurrentThreadId(), &info ) ? info.hwndFocus : 0; +} + /********************************************************************** * NtUserAttachThreadInput (win32u.@) */ @@ -1152,6 +1199,8 @@ HKL WINAPI NtUserActivateKeyboardLayout( HKL layout, UINT flags ) { struct user_thread_info *info = get_user_thread_info(); HKL old_layout; + LCID locale; + HWND focus; TRACE_(keyboard)( "layout %p, flags %x\n", layout, flags ); @@ -1164,12 +1213,41 @@ HKL WINAPI NtUserActivateKeyboardLayout( HKL layout, UINT flags ) return 0; } + if (LOWORD(layout) != MAKELANGID(LANG_INVARIANT, SUBLANG_DEFAULT) && + (NtQueryDefaultLocale( TRUE, &locale ) || LOWORD(layout) != locale)) + { + RtlSetLastWin32Error( ERROR_CALL_NOT_IMPLEMENTED ); + FIXME_(keyboard)( "Changing user locale is not supported\n" ); + return 0; + } + if (!user_driver->pActivateKeyboardLayout( layout, flags )) return 0; old_layout = info->kbd_layout; - info->kbd_layout = layout; - if (old_layout != layout) info->kbd_layout_id = 0; + if (old_layout != layout) + { + HWND ime_hwnd = get_default_ime_window( 0 ); + const NLS_LOCALE_DATA *data; + CHARSETINFO cs = {0}; + + if (ime_hwnd) send_message( ime_hwnd, WM_IME_INTERNAL, IME_INTERNAL_HKL_DEACTIVATE, HandleToUlong(old_layout) ); + + if (HIWORD(layout) & 0x8000) + FIXME( "Aliased keyboard layout not yet implemented\n" ); + else if (!(data = get_locale_data( HIWORD(layout) ))) + WARN( "Failed to find locale data for %04x\n", HIWORD(layout) ); + else + translate_charset_info( ULongToPtr(data->idefaultansicodepage), &cs, TCI_SRCCODEPAGE ); + + info->kbd_layout = layout; + info->kbd_layout_id = 0; + + if (ime_hwnd) send_message( ime_hwnd, WM_IME_INTERNAL, IME_INTERNAL_HKL_ACTIVATE, HandleToUlong(layout) ); + + if ((focus = get_focus()) && get_window_thread( focus, NULL ) == GetCurrentThreadId()) + send_message( focus, WM_INPUTLANGCHANGE, cs.ciCharset, (LPARAM)layout ); + } if (!old_layout) return get_locale_kbd_layout(); return old_layout; @@ -1216,10 +1294,10 @@ UINT WINAPI NtUserGetKeyboardLayoutList( INT size, HKL *layouts ) tmp = wcstoul( key_info->Name, NULL, 16 ); if (query_reg_ascii_value( subkey, "Layout Id", value_info, sizeof(buffer) ) && value_info->Type == REG_SZ) - tmp = MAKELONG( LOWORD( tmp ), - 0xf000 | (wcstoul( (const WCHAR *)value_info->Data, NULL, 16 ) & 0xfff) ); + tmp = 0xf000 | (wcstoul( (const WCHAR *)value_info->Data, NULL, 16 ) & 0xfff); NtClose( subkey ); + tmp = MAKELONG( LOWORD( layout ), LOWORD( tmp ) ); if (layout == UlongToHandle( tmp )) continue; count++; @@ -1711,49 +1789,6 @@ BOOL WINAPI release_capture(void) return ret; } -/******************************************************************* - * NtUserGetForegroundWindow (win32u.@) - */ -HWND WINAPI NtUserGetForegroundWindow(void) -{ - volatile struct input_shared_memory *shared = get_foreground_shared_memory(); - HWND ret = 0; - - if (!shared) return 0; - - SHARED_READ_BEGIN( &shared->seq ) - { - ret = wine_server_ptr_handle( shared->active ); - } - SHARED_READ_END( &shared->seq ); - - return ret; -} - -/* see GetActiveWindow */ -HWND get_active_window(void) -{ - GUITHREADINFO info; - info.cbSize = sizeof(info); - return NtUserGetGUIThreadInfo( GetCurrentThreadId(), &info ) ? info.hwndActive : 0; -} - -/* see GetCapture */ -HWND get_capture(void) -{ - GUITHREADINFO info; - info.cbSize = sizeof(info); - return NtUserGetGUIThreadInfo( GetCurrentThreadId(), &info ) ? info.hwndCapture : 0; -} - -/* see GetFocus */ -HWND get_focus(void) -{ - GUITHREADINFO info; - info.cbSize = sizeof(info); - return NtUserGetGUIThreadInfo( GetCurrentThreadId(), &info ) ? info.hwndFocus : 0; -} - /***************************************************************** * set_focus_window * @@ -1821,7 +1856,7 @@ static BOOL set_active_window( HWND hwnd, HWND *prev, BOOL mouse, BOOL focus ) if (previous == hwnd) { if (prev) *prev = hwnd; - return TRUE; + goto done; } if (prev) *prev = previous; @@ -1918,6 +1953,7 @@ static BOOL set_active_window( HWND hwnd, HWND *prev, BOOL mouse, BOOL focus ) done: win_set_flags( hwnd, 0, WIN_IS_ACTIVATING ); + if (ret && hwnd) clip_fullscreen_window( hwnd, FALSE ); return ret; } @@ -2050,7 +2086,7 @@ BOOL set_foreground_window( HWND hwnd, BOOL mouse ) return ret; } -struct +static struct { HBITMAP bitmap; unsigned int timeout; @@ -2444,6 +2480,60 @@ BOOL WINAPI NtUserIsMouseInPointerEnabled(void) return FALSE; } +/*********************************************************************** + * clip_fullscreen_window + * + * Turn on clipping if the active window is fullscreen. + */ +BOOL clip_fullscreen_window( HWND hwnd, BOOL reset ) +{ + struct user_thread_info *thread_info = get_user_thread_info(); + MONITORINFO monitor_info = {.cbSize = sizeof(MONITORINFO)}; + RECT rect; + HMONITOR monitor; + DWORD style; + BOOL ret; + + if (hwnd == NtUserGetDesktopWindow()) return FALSE; + if (hwnd != NtUserGetForegroundWindow()) return FALSE; + + style = NtUserGetWindowLongW( hwnd, GWL_STYLE ); + if (!(style & WS_VISIBLE)) return FALSE; + if ((style & (WS_POPUP | WS_CHILD)) == WS_CHILD) return FALSE; + /* maximized windows don't count as full screen */ + if ((style & WS_MAXIMIZE) && (style & WS_CAPTION) == WS_CAPTION) return FALSE; + + if (!NtUserGetWindowRect( hwnd, &rect )) return FALSE; + if (!NtUserIsWindowRectFullScreen( &rect )) return FALSE; + + if (!reset && NtGetTickCount() - thread_info->clipping_reset < 1000) return FALSE; + if (!reset && clipping_cursor && thread_info->clipping_cursor) return FALSE; /* already clipping */ + + if (!(monitor = NtUserMonitorFromWindow( hwnd, MONITOR_DEFAULTTONEAREST ))) return FALSE; + if (!NtUserGetMonitorInfo( monitor, &monitor_info )) return FALSE; + if (!grab_fullscreen) + { + RECT virtual_rect = NtUserGetVirtualScreenRect(); + if (!EqualRect( &monitor_info.rcMonitor, &virtual_rect )) return FALSE; + if (is_virtual_desktop()) return FALSE; + } + + TRACE( "win %p clipping fullscreen\n", hwnd ); + + SERVER_START_REQ( set_cursor ) + { + req->flags = SET_CURSOR_CLIP | SET_CURSOR_FSCLIP; + req->clip.left = monitor_info.rcMonitor.left; + req->clip.top = monitor_info.rcMonitor.top; + req->clip.right = monitor_info.rcMonitor.right; + req->clip.bottom = monitor_info.rcMonitor.bottom; + ret = !wine_server_call( req ); + } + SERVER_END_REQ; + + return ret; +} + /********************************************************************** * NtUserIsTouchWindow (win32u.@) */ @@ -2477,121 +2567,127 @@ BOOL WINAPI NtUserGetPointerInfoList( UINT32 id, POINTER_INPUT_TYPE type, UINT_P UINT32 *entry_count, UINT32 *pointer_count, void *pointer_info ) { FIXME( "id %#x, type %#x, unk0 %#zx, unk1 %#zx, size %#zx, entry_count %p, pointer_count %p, pointer_info %p stub!\n", - id, (int)type, unk0, unk1, (size_t)size, entry_count, pointer_count, pointer_info ); + id, (int)type, (size_t)unk0, (size_t)unk1, (size_t)size, entry_count, pointer_count, pointer_info ); RtlSetLastWin32Error( ERROR_CALL_NOT_IMPLEMENTED ); return FALSE; } -HWND get_shell_window(void) +BOOL get_clip_cursor( RECT *rect ) { - HWND hwnd = 0; + volatile struct desktop_shared_memory *shared = get_desktop_shared_memory(); + UINT dpi; + + if (!rect || !shared) return FALSE; - SERVER_START_REQ(set_global_windows) + SHARED_READ_BEGIN( &shared->seq ) { - req->flags = 0; - if (!wine_server_call_err(req)) - hwnd = wine_server_ptr_handle( reply->old_shell_window ); + rect->left = shared->cursor.clip.left; + rect->top = shared->cursor.clip.top; + rect->right = shared->cursor.clip.right; + rect->bottom = shared->cursor.clip.bottom; } - SERVER_END_REQ; + SHARED_READ_END( &shared->seq ); - return hwnd; + if ((dpi = get_thread_dpi())) + { + HMONITOR monitor = monitor_from_rect( rect, MONITOR_DEFAULTTOPRIMARY, 0 ); + *rect = map_dpi_rect( *rect, get_monitor_dpi( monitor ), dpi ); + } + return TRUE; } -/*********************************************************************** -* NtUserSetShellWindowEx (win32u.@) -*/ -BOOL WINAPI NtUserSetShellWindowEx( HWND shell, HWND list_view ) +BOOL process_wine_clipcursor( HWND hwnd, UINT flags, BOOL reset ) { - BOOL ret; - - /* shell = Progman[Program Manager] - * |-> SHELLDLL_DefView - * list_view = | |-> SysListView32 - * | | |-> tooltips_class32 - * | | - * | |-> SysHeader32 - * | - * |-> ProxyTarget - */ - - if (get_shell_window()) - return FALSE; - - if (get_window_long( shell, GWL_EXSTYLE ) & WS_EX_TOPMOST) - return FALSE; - - if (list_view != shell && (get_window_long( list_view, GWL_EXSTYLE ) & WS_EX_TOPMOST)) - return FALSE; + struct user_thread_info *thread_info = get_user_thread_info(); + RECT rect, virtual_rect = NtUserGetVirtualScreenRect(); + BOOL empty = !!(flags & SET_CURSOR_NOCLIP); - if (list_view && list_view != shell) - NtUserSetWindowPos( list_view, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE ); + TRACE( "hwnd %p, flags %#x, reset %u\n", hwnd, flags, reset ); - NtUserSetWindowPos( shell, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE ); + if (thread_info->clipping_cursor) InterlockedDecrement( &clipping_cursor ); + thread_info->clipping_cursor = FALSE; - SERVER_START_REQ(set_global_windows) + if (reset) { - req->flags = SET_GLOBAL_SHELL_WINDOWS; - req->shell_window = wine_server_user_handle( shell ); - req->shell_listview = wine_server_user_handle( list_view ); - ret = !wine_server_call_err(req); + thread_info->clipping_reset = NtGetTickCount(); + return user_driver->pClipCursor( NULL, TRUE ); } - SERVER_END_REQ; - return ret; -} -HWND get_progman_window(void) -{ - HWND ret = 0; + if (!grab_pointer) return TRUE; - SERVER_START_REQ(set_global_windows) + /* we are clipping if the clip rectangle is smaller than the screen */ + get_clip_cursor( &rect ); + intersect_rect( &rect, &rect, &virtual_rect ); + if (EqualRect( &rect, &virtual_rect )) empty = TRUE; + if (empty && !(flags & SET_CURSOR_FSCLIP)) { - req->flags = 0; - if (!wine_server_call_err(req)) - ret = wine_server_ptr_handle( reply->old_progman_window ); + /* if currently clipping, check if we should switch to fullscreen clipping */ + if (clip_fullscreen_window( hwnd, TRUE )) return TRUE; + return user_driver->pClipCursor( NULL, FALSE ); } - SERVER_END_REQ; - return ret; + + if (!user_driver->pClipCursor( &rect, FALSE )) return FALSE; + InterlockedIncrement( &clipping_cursor ); + thread_info->clipping_cursor = TRUE; + return TRUE; } -HWND set_progman_window( HWND hwnd ) +/*********************************************************************** + * NtUserClipCursor (win32u.@) + */ +BOOL WINAPI NtUserClipCursor( const RECT *rect ) { - SERVER_START_REQ(set_global_windows) + HWND foreground = NtUserGetForegroundWindow(); + UINT dpi; + BOOL ret; + RECT new_rect, full_rect; + + TRACE( "Clipping to %s\n", wine_dbgstr_rect(rect) ); + + if (foreground == NtUserGetDesktopWindow()) { - req->flags = SET_GLOBAL_PROGMAN_WINDOW; - req->progman_window = wine_server_user_handle( hwnd ); - if (wine_server_call_err( req )) hwnd = 0; + WARN( "desktop is foreground, ignoring ClipCursor\n" ); + rect = NULL; } - SERVER_END_REQ; - return hwnd; -} - -HWND get_taskman_window(void) -{ - HWND ret = 0; - SERVER_START_REQ(set_global_windows) + if (rect) { - req->flags = 0; - if (!wine_server_call_err(req)) - ret = wine_server_ptr_handle( reply->old_taskman_window ); + if (rect->left > rect->right || rect->top > rect->bottom) return FALSE; + if ((dpi = get_thread_dpi())) + { + HMONITOR monitor = monitor_from_rect( rect, MONITOR_DEFAULTTOPRIMARY, dpi ); + new_rect = map_dpi_rect( *rect, dpi, get_monitor_dpi( monitor )); + rect = &new_rect; + } + + /* keep the mouse clipped inside of a fullscreen foreground window */ + if (NtUserGetWindowRect( foreground, &full_rect ) && is_window_rect_full_screen( &full_rect )) + { + full_rect.left = max( full_rect.left, min( full_rect.right - 1, rect->left ) ); + full_rect.right = max( full_rect.left, min( full_rect.right - 1, rect->right ) ); + full_rect.top = max( full_rect.top, min( full_rect.bottom - 1, rect->top ) ); + full_rect.bottom = max( full_rect.top, min( full_rect.bottom - 1, rect->bottom ) ); + rect = &full_rect; + } } - SERVER_END_REQ; - return ret; -} -HWND set_taskman_window( HWND hwnd ) -{ - /* hwnd = MSTaskSwWClass - * |-> SysTabControl32 - */ - SERVER_START_REQ(set_global_windows) + SERVER_START_REQ( set_cursor ) { - req->flags = SET_GLOBAL_TASKMAN_WINDOW; - req->taskman_window = wine_server_user_handle( hwnd ); - if (wine_server_call_err( req )) hwnd = 0; + if (rect) + { + req->flags = SET_CURSOR_CLIP; + req->clip.left = rect->left; + req->clip.top = rect->top; + req->clip.right = rect->right; + req->clip.bottom = rect->bottom; + } + else req->flags = SET_CURSOR_NOCLIP; + + ret = !wine_server_call( req ); } SERVER_END_REQ; - return hwnd; + + return ret; } /***************************************************************************** diff --git a/dlls/win32u/message.c b/dlls/win32u/message.c index fc405f39d4c..6a9e77f796c 100644 --- a/dlls/win32u/message.c +++ b/dlls/win32u/message.c @@ -1287,13 +1287,13 @@ static LRESULT handle_internal_message( HWND hwnd, UINT msg, WPARAM wparam, LPAR return call_current_hook( h_extra->handle, HC_ACTION, wparam, h_extra->lparam ); } case WM_WINE_CLIPCURSOR: - if (wparam) - { - RECT rect; - get_clip_cursor( &rect ); - return user_driver->pClipCursor( &rect ); - } - return user_driver->pClipCursor( NULL ); + /* non-hardware message, posted on display mode change to trigger fullscreen + clipping or to the desktop window to forcefully release the cursor grabs */ + if (wparam & SET_CURSOR_FSCLIP) return clip_fullscreen_window( hwnd, FALSE ); + return process_wine_clipcursor( hwnd, wparam, lparam ); + case WM_WINE_SETCURSOR: + FIXME( "Unexpected non-hardware WM_WINE_SETCURSOR message\n" ); + return FALSE; case WM_WINE_UPDATEWINDOWSTATE: update_window_state( hwnd ); return 0; @@ -1873,6 +1873,10 @@ static BOOL process_hardware_message( MSG *msg, UINT hw_id, const struct hardwar ret = process_keyboard_message( msg, hw_id, hwnd_filter, first, last, remove ); else if (is_mouse_message( msg->message )) ret = process_mouse_message( msg, hw_id, msg_data->info, hwnd_filter, first, last, remove ); + else if (msg->message == WM_WINE_CLIPCURSOR) + process_wine_clipcursor( msg->hwnd, msg->wParam, msg->lParam ); + else if (msg->message == WM_WINE_SETCURSOR) + process_wine_setcursor( msg->hwnd, (HWND)msg->wParam, (HCURSOR)msg->lParam ); else ERR( "unknown message type %x\n", msg->message ); SetThreadDpiAwarenessContext( context ); @@ -2763,6 +2767,9 @@ NTSTATUS send_hardware_message( HWND hwnd, const INPUT *input, const RAWINPUT *r info.timeout = 0; info.params = NULL; + if (input->type == INPUT_MOUSE && (input->mi.dwFlags & (MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_RIGHTDOWN))) + clip_fullscreen_window( hwnd, FALSE ); + if (input->type == INPUT_HARDWARE && rawinput->header.dwType == RIM_TYPEHID) { if (input->hi.uMsg == WM_INPUT_DEVICE_CHANGE) @@ -3099,8 +3106,8 @@ static BOOL map_wparam_AtoW( UINT message, WPARAM *wparam, enum wm_char_mapping * * Call a window procedure, translating args from Ansi to Unicode. */ -LRESULT call_messageAtoW( winproc_callback_t callback, HWND hwnd, UINT msg, WPARAM wparam, - LPARAM lparam, LRESULT *result, void *arg, enum wm_char_mapping mapping ) +static LRESULT call_messageAtoW( winproc_callback_t callback, HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam, LRESULT *result, void *arg, enum wm_char_mapping mapping ) { LRESULT ret = 0; @@ -3688,6 +3695,9 @@ LRESULT WINAPI NtUserMessageCall( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpa spy_exit_message( ansi, hwnd, msg, (LPARAM)result_info, wparam, lparam ); return 0; + case NtUserImeDriverCall: + return ime_driver_call( hwnd, msg, wparam, lparam, result_info ); + default: FIXME( "%p %x %lx %lx %p %x %x\n", hwnd, msg, (long)wparam, lparam, result_info, (int)type, ansi ); } diff --git a/dlls/win32u/ntgdi_private.h b/dlls/win32u/ntgdi_private.h index be3e4d8cd56..3262f5c2e2a 100644 --- a/dlls/win32u/ntgdi_private.h +++ b/dlls/win32u/ntgdi_private.h @@ -152,7 +152,7 @@ extern DWORD stretch_bits( const BITMAPINFO *src_info, struct bitblt_coords *src extern void get_mono_dc_colors( DC *dc, int color_table_size, BITMAPINFO *info, int count ) DECLSPEC_HIDDEN; /* brush.c */ -extern HBRUSH create_brush( const LOGBRUSH *brush ); +extern HBRUSH create_brush( const LOGBRUSH *brush ) DECLSPEC_HIDDEN; extern BOOL store_brush_pattern( LOGBRUSH *brush, struct brush_pattern *pattern ) DECLSPEC_HIDDEN; extern void free_brush_pattern( struct brush_pattern *pattern ) DECLSPEC_HIDDEN; @@ -160,7 +160,7 @@ extern void free_brush_pattern( struct brush_pattern *pattern ) DECLSPEC_HIDDEN; extern BOOL clip_device_rect( DC *dc, RECT *dst, const RECT *src ) DECLSPEC_HIDDEN; extern BOOL clip_visrect( DC *dc, RECT *dst, const RECT *src ) DECLSPEC_HIDDEN; extern void set_visible_region( HDC hdc, HRGN hrgn, const RECT *vis_rect, - const RECT *device_rect, struct window_surface *surface ); + const RECT *device_rect, struct window_surface *surface ) DECLSPEC_HIDDEN; extern void update_dc_clipping( DC * dc ) DECLSPEC_HIDDEN; /* Return the total DC region (if any) */ @@ -368,7 +368,6 @@ extern BOOL opentype_enum_full_names( const struct tt_name_v0 *tt_name_v0, extern BOOL opentype_get_properties( const void *data, size_t size, const struct ttc_sfnt_v1 *ttc_sfnt_v1, DWORD *version, FONTSIGNATURE *fs, DWORD *ntm_flags ) DECLSPEC_HIDDEN; -extern BOOL translate_charset_info( DWORD *src, CHARSETINFO *cs, DWORD flags ) DECLSPEC_HIDDEN; /* gdiobj.c */ extern HGDIOBJ alloc_gdi_handle( struct gdi_obj_header *obj, DWORD type, @@ -529,15 +528,6 @@ static inline DC *get_physdev_dc( PHYSDEV dev ) return get_nulldrv_dc( dev ); } -static inline BOOL intersect_rect( RECT *dst, const RECT *src1, const RECT *src2 ) -{ - dst->left = max( src1->left, src2->left ); - dst->top = max( src1->top, src2->top ); - dst->right = min( src1->right, src2->right ); - dst->bottom = min( src1->bottom, src2->bottom ); - return !IsRectEmpty( dst ); -} - static inline void order_rect( RECT *rect ) { if (rect->left > rect->right) diff --git a/dlls/win32u/ntuser_private.h b/dlls/win32u/ntuser_private.h index 44af8158c6f..3b49ff48f35 100644 --- a/dlls/win32u/ntuser_private.h +++ b/dlls/win32u/ntuser_private.h @@ -52,11 +52,6 @@ struct user_object #define OBJ_OTHER_PROCESS ((void *)1) /* returned by get_user_handle_ptr on unknown handles */ -HANDLE alloc_user_handle( struct user_object *ptr, unsigned int type ) DECLSPEC_HIDDEN; -void *get_user_handle_ptr( HANDLE handle, unsigned int type ) DECLSPEC_HIDDEN; -void release_user_handle_ptr( void *ptr ) DECLSPEC_HIDDEN; -void *free_user_handle( HANDLE handle, unsigned int type ) DECLSPEC_HIDDEN; - typedef struct tagWND { struct user_object obj; /* object header */ @@ -145,6 +140,8 @@ struct user_thread_info struct rawinput_thread_data *rawinput; /* RawInput thread local data / buffer */ struct touchinput_thread_data *touchinput; /* touch input thread local buffer */ UINT spy_indent; /* Current spy indent */ + BOOL clipping_cursor; /* thread is currently clipping */ + DWORD clipping_reset; /* time when clipping was last reset */ struct desktop_shared_memory *desktop_shared_memory; /* Ptr to server's desktop shared memory */ struct queue_shared_memory *queue_shared_memory; /* Ptr to server's thread queue shared memory */ struct input_shared_memory *input_shared_memory; /* Ptr to server's thread input shared memory */ @@ -241,6 +238,10 @@ BOOL needs_ime_window( HWND hwnd ) DECLSPEC_HIDDEN; extern void register_builtin_classes(void) DECLSPEC_HIDDEN; extern void register_desktop_class(void) DECLSPEC_HIDDEN; +/* imm.c */ +extern LRESULT ime_driver_call( HWND hwnd, enum wine_ime_call call, WPARAM wparam, LPARAM lparam, + struct ime_driver_call_params *params ) DECLSPEC_HIDDEN; + /* cursoricon.c */ HICON alloc_cursoricon_handle( BOOL is_icon ) DECLSPEC_HIDDEN; @@ -253,6 +254,7 @@ HANDLE alloc_user_handle( struct user_object *ptr, unsigned int type ) DECLSPEC_ void *free_user_handle( HANDLE handle, unsigned int type ) DECLSPEC_HIDDEN; void *get_user_handle_ptr( HANDLE handle, unsigned int type ) DECLSPEC_HIDDEN; void release_user_handle_ptr( void *ptr ) DECLSPEC_HIDDEN; +void *next_process_user_handle_ptr( HANDLE *handle, unsigned int type ) DECLSPEC_HIDDEN; UINT win_set_flags( HWND hwnd, UINT set_mask, UINT clear_mask ) DECLSPEC_HIDDEN; /* winstation.c */ @@ -273,7 +275,7 @@ static inline UINT win_get_flags( HWND hwnd ) } WND *get_win_ptr( HWND hwnd ) DECLSPEC_HIDDEN; -BOOL is_child( HWND parent, HWND child ); +BOOL is_child( HWND parent, HWND child ) DECLSPEC_HIDDEN; BOOL is_window( HWND hwnd ) DECLSPEC_HIDDEN; #if defined(__i386__) || defined(__x86_64__) diff --git a/dlls/win32u/rawinput.c b/dlls/win32u/rawinput.c index f27bc092102..e5eab049fa3 100644 --- a/dlls/win32u/rawinput.c +++ b/dlls/win32u/rawinput.c @@ -220,11 +220,11 @@ static struct device *add_device( HKEY key, DWORD type ) static const RID_DEVICE_INFO_MOUSE mouse_info = {1, 5, 0, FALSE}; struct hid_preparsed_data *preparsed = NULL; HID_COLLECTION_INFORMATION hid_info; + IO_STATUS_BLOCK io = {{0}}; OBJECT_ATTRIBUTES attr; UNICODE_STRING string; struct device *device; RID_DEVICE_INFO info; - IO_STATUS_BLOCK io; unsigned int status; UINT32 handle; void *buffer; diff --git a/dlls/win32u/scroll.c b/dlls/win32u/scroll.c index ce3af5189d3..19a9a1379f4 100644 --- a/dlls/win32u/scroll.c +++ b/dlls/win32u/scroll.c @@ -282,7 +282,7 @@ static BOOL get_scroll_bar_rect( HWND hwnd, int bar, RECT *rect, int *arrow_size * * Redraw the whole scrollbar. */ -void draw_scroll_bar( HWND hwnd, HDC hdc, int bar, enum SCROLL_HITTEST hit_test, +static void draw_scroll_bar( HWND hwnd, HDC hdc, int bar, enum SCROLL_HITTEST hit_test, const struct SCROLL_TRACKING_INFO *tracking_info, BOOL draw_arrows, BOOL draw_interior ) { diff --git a/dlls/win32u/syscall.c b/dlls/win32u/syscall.c index e802b3d2ec0..3581bc4c27d 100644 --- a/dlls/win32u/syscall.c +++ b/dlls/win32u/syscall.c @@ -106,6 +106,7 @@ static void * const syscalls[] = NtUserAssociateInputContext, NtUserAttachThreadInput, NtUserBeginPaint, + NtUserBuildHimcList, NtUserBuildHwndList, NtUserCallHwnd, NtUserCallHwndParam, @@ -234,6 +235,7 @@ static void * const syscalls[] = NtUserMessageCall, NtUserMoveWindow, NtUserMsgWaitForMultipleObjectsEx, + NtUserNotifyIMEStatus, NtUserNotifyWinEvent, NtUserOpenClipboard, NtUserOpenDesktop, diff --git a/dlls/win32u/sysparams.c b/dlls/win32u/sysparams.c index 2ecc317b509..6630250b667 100644 --- a/dlls/win32u/sysparams.c +++ b/dlls/win32u/sysparams.c @@ -2075,7 +2075,7 @@ RECT get_virtual_screen_rect( UINT dpi ) return rect; } -static BOOL is_window_rect_full_screen( const RECT *rect ) +BOOL is_window_rect_full_screen( const RECT *rect ) { struct monitor *monitor; BOOL ret = FALSE; @@ -2726,11 +2726,14 @@ static LONG apply_display_settings( const WCHAR *devname, const DEVMODEW *devmod if (!adapter_get_current_settings( adapter, ¤t_mode )) WARN( "Failed to get primary adapter current display settings.\n" ); adapter_release( adapter ); + NtUserClipCursor( NULL ); send_notify_message( NtUserGetDesktopWindow(), WM_DISPLAYCHANGE, current_mode.dmBitsPerPel, MAKELPARAM( current_mode.dmPelsWidth, current_mode.dmPelsHeight ), FALSE ); send_message_timeout( HWND_BROADCAST, WM_DISPLAYCHANGE, current_mode.dmBitsPerPel, MAKELPARAM( current_mode.dmPelsWidth, current_mode.dmPelsHeight ), SMTO_ABORTIFHUNG, 2000, FALSE ); + /* post clip_fullscreen_window request to the foreground window */ + NtUserPostMessage( NtUserGetForegroundWindow(), WM_WINE_CLIPCURSOR, SET_CURSOR_FSCLIP, 0 ); } return ret; @@ -2850,6 +2853,7 @@ static unsigned int active_monitor_count(void) INT get_display_depth( UNICODE_STRING *name ) { struct display_device *device; + BOOL is_primary; INT depth; if (!lock_display_devices()) @@ -2866,8 +2870,16 @@ INT get_display_depth( UNICODE_STRING *name ) return 32; } - depth = user_driver->pGetDisplayDepth( device->device_name, - !!(device->state_flags & DISPLAY_DEVICE_PRIMARY_DEVICE) ); + is_primary = !!(device->state_flags & DISPLAY_DEVICE_PRIMARY_DEVICE); + if ((depth = user_driver->pGetDisplayDepth( device->device_name, is_primary )) < 0) + { + struct adapter *adapter = CONTAINING_RECORD( device, struct adapter, dev ); + DEVMODEW current_mode = {.dmSize = sizeof(DEVMODEW)}; + + if (!adapter_get_current_settings( adapter, ¤t_mode )) depth = 32; + else depth = current_mode.dmBitsPerPel; + } + unlock_display_devices(); return depth; } @@ -4117,13 +4129,47 @@ static union sysparam_all_entry * const default_entries[] = (union sysparam_all_entry *)&entry_AUDIODESC_ON, }; -void sysparams_init(void) +/*********************************************************************** + * get_config_key + * + * Get a config key from either the app-specific or the default config + */ +static DWORD get_config_key( HKEY defkey, HKEY appkey, const char *name, + WCHAR *buffer, DWORD size ) { + WCHAR nameW[128]; + char buf[2048]; + KEY_VALUE_PARTIAL_INFORMATION *info = (void *)buf; + + asciiz_to_unicode( nameW, name ); + + if (appkey && query_reg_value( appkey, nameW, info, sizeof(buf) )) + { + size = min( info->DataLength, size - sizeof(WCHAR) ); + memcpy( buffer, info->Data, size ); + buffer[size / sizeof(WCHAR)] = 0; + return 0; + } + + if (defkey && query_reg_value( defkey, nameW, info, sizeof(buf) )) + { + size = min( info->DataLength, size - sizeof(WCHAR) ); + memcpy( buffer, info->Data, size ); + buffer[size / sizeof(WCHAR)] = 0; + return 0; + } + return ERROR_FILE_NOT_FOUND; +} + +void sysparams_init(void) +{ + WCHAR buffer[MAX_PATH+16], *p, *appname; DWORD i, dispos, dpi_scaling; WCHAR layout[KL_NAMELENGTH]; pthread_mutexattr_t attr; - HKEY hkey; + HKEY hkey, appkey = 0; + DWORD len; static const WCHAR software_wineW[] = {'S','o','f','t','w','a','r','e','\\','W','i','n','e'}; static const WCHAR temporary_system_parametersW[] = @@ -4132,6 +4178,7 @@ void sysparams_init(void) static const WCHAR oneW[] = {'1',0}; static const WCHAR kl_preloadW[] = {'K','e','y','b','o','a','r','d',' ','L','a','y','o','u','t','\\','P','r','e','l','o','a','d'}; + static const WCHAR x11driverW[] = {'\\','X','1','1',' ','D','r','i','v','e','r',0}; pthread_mutexattr_init( &attr ); pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_RECURSIVE ); @@ -4191,6 +4238,44 @@ void sysparams_init(void) for (i = 0; i < ARRAY_SIZE( default_entries ); i++) default_entries[i]->hdr.init( default_entries[i] ); } + + /* @@ Wine registry key: HKCU\Software\Wine\X11 Driver */ + hkey = reg_open_hkcu_key( "Software\\Wine\\X11 Driver" ); + + /* open the app-specific key */ + + appname = NtCurrentTeb()->Peb->ProcessParameters->ImagePathName.Buffer; + if ((p = wcsrchr( appname, '/' ))) appname = p + 1; + if ((p = wcsrchr( appname, '\\' ))) appname = p + 1; + len = lstrlenW( appname ); + + if (len && len < MAX_PATH) + { + HKEY tmpkey; + int i; + + for (i = 0; appname[i]; i++) buffer[i] = RtlDowncaseUnicodeChar( appname[i] ); + buffer[i] = 0; + appname = buffer; + memcpy( appname + i, x11driverW, sizeof(x11driverW) ); + + /* @@ Wine registry key: HKCU\Software\Wine\AppDefaults\app.exe\X11 Driver */ + if ((tmpkey = reg_open_hkcu_key( "Software\\Wine\\AppDefaults" ))) + { + appkey = reg_open_key( tmpkey, appname, lstrlenW( appname ) * sizeof(WCHAR) ); + NtClose( tmpkey ); + } + } + +#define IS_OPTION_TRUE(ch) \ + ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1') + + if (!get_config_key( hkey, appkey, "GrabPointer", buffer, sizeof(buffer) )) + grab_pointer = IS_OPTION_TRUE( buffer[0] ); + if (!get_config_key( hkey, appkey, "GrabFullscreen", buffer, sizeof(buffer) )) + grab_fullscreen = IS_OPTION_TRUE( buffer[0] ); + +#undef IS_OPTION_TRUE } static BOOL update_desktop_wallpaper(void) diff --git a/dlls/win32u/tests/win32u.c b/dlls/win32u/tests/win32u.c index 60ef2d38b5f..04e5a2e7932 100644 --- a/dlls/win32u/tests/win32u.c +++ b/dlls/win32u/tests/win32u.c @@ -221,6 +221,223 @@ static void test_class(void) } +static void test_NtUserCreateInputContext(void) +{ + UINT_PTR value, attr3; + HIMC himc; + UINT ret; + + SetLastError( 0xdeadbeef ); + himc = NtUserCreateInputContext( 0 ); + todo_wine + ok( !himc, "NtUserCreateInputContext succeeded\n" ); + todo_wine + ok( GetLastError() == ERROR_INVALID_PARAMETER, "got error %lu\n", GetLastError() ); + SetLastError( 0xdeadbeef ); + ret = NtUserDestroyInputContext( himc ); + todo_wine + ok( !ret, "NtUserDestroyInputContext succeeded\n" ); + todo_wine + ok( GetLastError() == ERROR_INVALID_HANDLE, "got error %lu\n", GetLastError() ); + + + himc = NtUserCreateInputContext( 0xdeadbeef ); + ok( !!himc, "NtUserCreateInputContext failed, error %lu\n", GetLastError() ); + + SetLastError( 0xdeadbeef ); + value = NtUserQueryInputContext( himc, 0 ); + todo_wine + ok( value == GetCurrentProcessId(), "NtUserQueryInputContext 0 returned %#Ix\n", value ); + ok( GetLastError() == 0xdeadbeef, "got error %lu\n", GetLastError() ); + SetLastError( 0xdeadbeef ); + value = NtUserQueryInputContext( himc, 1 ); + ok( value == GetCurrentThreadId(), "NtUserQueryInputContext 1 returned %#Ix\n", value ); + ok( GetLastError() == 0xdeadbeef, "got error %lu\n", GetLastError() ); + SetLastError( 0xdeadbeef ); + value = NtUserQueryInputContext( himc, 2 ); + ok( value == 0, "NtUserQueryInputContext 2 returned %#Ix\n", value ); + ok( GetLastError() == 0xdeadbeef, "got error %lu\n", GetLastError() ); + SetLastError( 0xdeadbeef ); + value = NtUserQueryInputContext( himc, 3 ); + todo_wine + ok( !!value, "NtUserQueryInputContext 3 returned %#Ix\n", value ); + ok( GetLastError() == 0xdeadbeef, "got error %lu\n", GetLastError() ); + attr3 = value; + SetLastError( 0xdeadbeef ); + value = NtUserQueryInputContext( himc, 4 ); + todo_wine + ok( GetLastError() == ERROR_INVALID_PARAMETER, "got error %lu\n", GetLastError() ); + + SetLastError( 0xdeadbeef ); + ret = NtUserUpdateInputContext( himc, 0, 0 ); + todo_wine + ok( !ret, "NtUserUpdateInputContext 0 succeeded\n" ); + todo_wine + ok( GetLastError() == ERROR_ALREADY_INITIALIZED, "got error %lu\n", GetLastError() ); + SetLastError( 0xdeadbeef ); + ret = NtUserUpdateInputContext( himc, 1, 0xdeadbeef ); + todo_wine + ok( !!ret, "NtUserUpdateInputContext 1 failed\n" ); + ok( GetLastError() == 0xdeadbeef, "got error %lu\n", GetLastError() ); + SetLastError( 0xdeadbeef ); + ret = NtUserUpdateInputContext( himc, 2, 0xdeadbeef ); + ok( !ret, "NtUserUpdateInputContext 2 succeeded\n" ); + ok( GetLastError() == 0xdeadbeef, "got error %lu\n", GetLastError() ); + SetLastError( 0xdeadbeef ); + ret = NtUserUpdateInputContext( himc, 3, 0x0badf00d ); + ok( !ret, "NtUserUpdateInputContext 3 succeeded\n" ); + ok( GetLastError() == 0xdeadbeef, "got error %lu\n", GetLastError() ); + SetLastError( 0xdeadbeef ); + ret = NtUserUpdateInputContext( himc, 4, 0xdeadbeef ); + ok( !ret, "NtUserUpdateInputContext 4 succeeded\n" ); + ok( GetLastError() == 0xdeadbeef, "got error %lu\n", GetLastError() ); + + SetLastError( 0xdeadbeef ); + value = NtUserQueryInputContext( himc, 0 ); + todo_wine + ok( value == GetCurrentProcessId(), "NtUserQueryInputContext 0 returned %#Ix\n", value ); + ok( GetLastError() == 0xdeadbeef, "got error %lu\n", GetLastError() ); + SetLastError( 0xdeadbeef ); + value = NtUserQueryInputContext( himc, 1 ); + ok( value == GetCurrentThreadId(), "NtUserQueryInputContext 1 returned %#Ix\n", value ); + ok( GetLastError() == 0xdeadbeef, "got error %lu\n", GetLastError() ); + SetLastError( 0xdeadbeef ); + value = NtUserQueryInputContext( himc, 2 ); + ok( value == 0, "NtUserQueryInputContext 2 returned %#Ix\n", value ); + ok( GetLastError() == 0xdeadbeef, "got error %lu\n", GetLastError() ); + SetLastError( 0xdeadbeef ); + value = NtUserQueryInputContext( himc, 3 ); + ok( value == attr3, "NtUserQueryInputContext 3 returned %#Ix\n", value ); + ok( GetLastError() == 0xdeadbeef, "got error %lu\n", GetLastError() ); + SetLastError( 0xdeadbeef ); + value = NtUserQueryInputContext( himc, 4 ); + todo_wine + ok( GetLastError() == ERROR_INVALID_PARAMETER, "got error %lu\n", GetLastError() ); + + ret = NtUserDestroyInputContext( himc ); + ok( !!ret, "NtUserDestroyInputContext failed, error %lu\n", GetLastError() ); +} + +static int himc_compare( const void *a, const void *b ) +{ + return (UINT_PTR)*(HIMC *)a - (UINT_PTR)*(HIMC *)b; +} + +static DWORD CALLBACK test_NtUserBuildHimcList_thread( void *arg ) +{ + HIMC buf[8], *himc = arg; + NTSTATUS status; + UINT size; + + size = 0xdeadbeef; + memset( buf, 0xcd, sizeof(buf) ); + status = NtUserBuildHimcList( GetCurrentThreadId(), ARRAYSIZE( buf ), buf, &size ); + ok( !status, "NtUserBuildHimcList failed: %#lx\n", status ); + todo_wine + ok( size == 1, "size = %u\n", size ); + ok( !!buf[0], "buf[0] = %p\n", buf[0] ); + + ok( buf[0] != himc[0], "buf[0] = %p\n", buf[0] ); + ok( buf[0] != himc[1], "buf[0] = %p\n", buf[0] ); + himc[2] = buf[0]; + qsort( himc, 3, sizeof(*himc), himc_compare ); + + size = 0xdeadbeef; + memset( buf, 0xcd, sizeof(buf) ); + status = NtUserBuildHimcList( -1, ARRAYSIZE( buf ), buf, &size ); + ok( !status, "NtUserBuildHimcList failed: %#lx\n", status ); + todo_wine + ok( size == 3, "size = %u\n", size ); + + qsort( buf, size, sizeof(*buf), himc_compare ); + /* FIXME: Wine only lazily creates a default thread IMC */ + todo_wine + ok( buf[0] == himc[0], "buf[0] = %p\n", buf[0] ); + todo_wine + ok( buf[1] == himc[1], "buf[1] = %p\n", buf[1] ); + todo_wine + ok( buf[2] == himc[2], "buf[2] = %p\n", buf[2] ); + + return 0; +} + +static void test_NtUserBuildHimcList(void) +{ + HIMC buf[8], himc[3], new_himc; + NTSTATUS status; + UINT size, ret; + HANDLE thread; + + size = 0xdeadbeef; + memset( buf, 0xcd, sizeof(buf) ); + status = NtUserBuildHimcList( GetCurrentThreadId(), ARRAYSIZE( buf ), buf, &size ); + ok( !status, "NtUserBuildHimcList failed: %#lx\n", status ); + ok( size == 1, "size = %u\n", size ); + ok( !!buf[0], "buf[0] = %p\n", buf[0] ); + himc[0] = buf[0]; + + + new_himc = NtUserCreateInputContext( 0xdeadbeef ); + ok( !!new_himc, "NtUserCreateInputContext failed, error %lu\n", GetLastError() ); + + himc[1] = new_himc; + qsort( himc, 2, sizeof(*himc), himc_compare ); + + size = 0xdeadbeef; + memset( buf, 0xcd, sizeof(buf) ); + status = NtUserBuildHimcList( GetCurrentThreadId(), ARRAYSIZE( buf ), buf, &size ); + ok( !status, "NtUserBuildHimcList failed: %#lx\n", status ); + ok( size == 2, "size = %u\n", size ); + + qsort( buf, size, sizeof(*buf), himc_compare ); + ok( buf[0] == himc[0], "buf[0] = %p\n", buf[0] ); + ok( buf[1] == himc[1], "buf[1] = %p\n", buf[1] ); + + size = 0xdeadbeef; + memset( buf, 0xcd, sizeof(buf) ); + status = NtUserBuildHimcList( 0, ARRAYSIZE( buf ), buf, &size ); + ok( !status, "NtUserBuildHimcList failed: %#lx\n", status ); + ok( size == 2, "size = %u\n", size ); + + qsort( buf, size, sizeof(*buf), himc_compare ); + ok( buf[0] == himc[0], "buf[0] = %p\n", buf[0] ); + ok( buf[1] == himc[1], "buf[1] = %p\n", buf[1] ); + + size = 0xdeadbeef; + memset( buf, 0xcd, sizeof(buf) ); + status = NtUserBuildHimcList( -1, ARRAYSIZE( buf ), buf, &size ); + ok( !status, "NtUserBuildHimcList failed: %#lx\n", status ); + ok( size == 2, "size = %u\n", size ); + + qsort( buf, size, sizeof(*buf), himc_compare ); + ok( buf[0] == himc[0], "buf[0] = %p\n", buf[0] ); + ok( buf[1] == himc[1], "buf[1] = %p\n", buf[1] ); + + thread = CreateThread( NULL, 0, test_NtUserBuildHimcList_thread, himc, 0, NULL ); + ok( !!thread, "CreateThread failed, error %lu\n", GetLastError() ); + ret = WaitForSingleObject( thread, 5000 ); + ok( !ret, "WaitForSingleObject returned %#x\n", ret ); + + size = 0xdeadbeef; + status = NtUserBuildHimcList( 1, ARRAYSIZE( buf ), buf, &size ); + todo_wine + ok( status == STATUS_INVALID_PARAMETER, "NtUserBuildHimcList returned %#lx\n", status ); + size = 0xdeadbeef; + status = NtUserBuildHimcList( GetCurrentProcessId(), ARRAYSIZE( buf ), buf, &size ); + todo_wine + ok( status == STATUS_INVALID_PARAMETER, "NtUserBuildHimcList returned %#lx\n", status ); + size = 0xdeadbeef; + status = NtUserBuildHimcList( GetCurrentThreadId(), 1, NULL, &size ); + ok( status == STATUS_UNSUCCESSFUL, "NtUserBuildHimcList returned %#lx\n", status ); + size = 0xdeadbeef; + status = NtUserBuildHimcList( GetCurrentThreadId(), 0, buf, &size ); + ok( !status, "NtUserBuildHimcList failed: %#lx\n", status ); + ok( size == 0, "size = %u\n", size ); + + ret = NtUserDestroyInputContext( new_himc ); + ok( !!ret, "NtUserDestroyInputContext failed, error %lu\n", GetLastError() ); +} + static BOOL WINAPI count_win( HWND hwnd, LPARAM lparam ) { ULONG *cnt = (ULONG *)lparam; @@ -1148,6 +1365,8 @@ START_TEST(win32u) test_NtUserEnumDisplayDevices(); test_window_props(); test_class(); + test_NtUserCreateInputContext(); + test_NtUserBuildHimcList(); test_NtUserBuildHwndList(); test_cursoricon(); test_message_call(); diff --git a/dlls/win32u/win32u.spec b/dlls/win32u/win32u.spec index d450d07635e..7b0629d9d65 100644 --- a/dlls/win32u/win32u.spec +++ b/dlls/win32u/win32u.spec @@ -762,7 +762,7 @@ @ stub NtUserBitBltSysBmp @ stub NtUserBlockInput @ stub NtUserBroadcastThemeChangeEvent -@ stub NtUserBuildHimcList +@ stdcall -syscall NtUserBuildHimcList(long long ptr ptr) @ stdcall -syscall NtUserBuildHwndList(long long long long long long ptr ptr) @ stub NtUserBuildNameList @ stub NtUserBuildPropList @@ -1087,7 +1087,7 @@ @ stdcall -syscall NtUserMoveWindow(long long long long long long) @ stdcall -syscall NtUserMsgWaitForMultipleObjectsEx(long ptr long long long) @ stub NtUserNavigateFocus -@ stub NtUserNotifyIMEStatus +@ stdcall -syscall NtUserNotifyIMEStatus(long long) @ stub NtUserNotifyProcessCreate @ stdcall -syscall NtUserNotifyWinEvent(long long long long) @ stdcall -syscall NtUserOpenClipboard(long long) diff --git a/dlls/win32u/win32u_private.h b/dlls/win32u/win32u_private.h index 87c76e7422c..3d1c7c99a43 100644 --- a/dlls/win32u/win32u_private.h +++ b/dlls/win32u/win32u_private.h @@ -215,8 +215,8 @@ extern UINT enum_clipboard_formats( UINT format ) DECLSPEC_HIDDEN; extern void release_clipboard_owner( HWND hwnd ) DECLSPEC_HIDDEN; /* cursoricon.c */ +extern BOOL process_wine_setcursor( HWND hwnd, HWND window, HCURSOR handle ) DECLSPEC_HIDDEN; extern HICON alloc_cursoricon_handle( BOOL is_icon ) DECLSPEC_HIDDEN; -extern BOOL get_clip_cursor( RECT *rect ) DECLSPEC_HIDDEN; extern ULONG_PTR get_icon_param( HICON handle ) DECLSPEC_HIDDEN; extern ULONG_PTR set_icon_param( HICON handle, ULONG_PTR param ) DECLSPEC_HIDDEN; @@ -264,6 +264,8 @@ extern BOOL register_imm_window( HWND hwnd ) DECLSPEC_HIDDEN; extern void unregister_imm_window( HWND hwnd ) DECLSPEC_HIDDEN; /* input.c */ +extern BOOL grab_pointer DECLSPEC_HIDDEN; +extern BOOL grab_fullscreen DECLSPEC_HIDDEN; extern BOOL destroy_caret(void) DECLSPEC_HIDDEN; extern BOOL enable_mouse_in_pointer DECLSPEC_HIDDEN; extern HWND get_active_window(void) DECLSPEC_HIDDEN; @@ -271,20 +273,18 @@ extern HWND get_capture(void) DECLSPEC_HIDDEN; extern BOOL get_cursor_pos( POINT *pt ) DECLSPEC_HIDDEN; extern HWND get_focus(void) DECLSPEC_HIDDEN; extern DWORD get_input_state(void) DECLSPEC_HIDDEN; -extern HWND get_progman_window(void) DECLSPEC_HIDDEN; -extern HWND get_shell_window(void) DECLSPEC_HIDDEN; -extern HWND get_taskman_window(void) DECLSPEC_HIDDEN; extern BOOL register_touch_window( HWND hwnd, UINT flags ) DECLSPEC_HIDDEN; extern BOOL WINAPI release_capture(void) DECLSPEC_HIDDEN; extern BOOL set_capture_window( HWND hwnd, UINT gui_flags, HWND *prev_ret ) DECLSPEC_HIDDEN; extern BOOL set_caret_blink_time( unsigned int time ) DECLSPEC_HIDDEN; extern BOOL set_caret_pos( int x, int y ) DECLSPEC_HIDDEN; extern BOOL set_foreground_window( HWND hwnd, BOOL mouse ) DECLSPEC_HIDDEN; -extern HWND set_progman_window( HWND hwnd ) DECLSPEC_HIDDEN; -extern HWND set_taskman_window( HWND hwnd ) DECLSPEC_HIDDEN; extern void toggle_caret( HWND hwnd ) DECLSPEC_HIDDEN; extern BOOL unregister_touch_window( HWND hwnd ) DECLSPEC_HIDDEN; extern void update_mouse_tracking_info( HWND hwnd ) DECLSPEC_HIDDEN; +extern BOOL get_clip_cursor( RECT *rect ) DECLSPEC_HIDDEN; +extern BOOL process_wine_clipcursor( HWND hwnd, UINT flags, BOOL reset ) DECLSPEC_HIDDEN; +extern BOOL clip_fullscreen_window( HWND hwnd, BOOL reset ) DECLSPEC_HIDDEN; /* menu.c */ extern HMENU create_menu( BOOL is_popup ) DECLSPEC_HIDDEN; @@ -316,7 +316,7 @@ extern LRESULT send_internal_message_timeout( DWORD dest_pid, DWORD dest_tid, UI extern LRESULT send_message( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) DECLSPEC_HIDDEN; extern BOOL send_notify_message( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, BOOL ansi ) DECLSPEC_HIDDEN; extern LRESULT send_message_timeout( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, - UINT flags, UINT timeout, BOOL ansi ); + UINT flags, UINT timeout, BOOL ansi ) DECLSPEC_HIDDEN; /* rawinput.c */ extern BOOL process_rawinput_message( MSG *msg, UINT hw_id, const struct hardware_msg_data *msg_data ) DECLSPEC_HIDDEN; @@ -351,6 +351,7 @@ extern int get_system_metrics( int index ) DECLSPEC_HIDDEN; extern UINT get_thread_dpi(void) DECLSPEC_HIDDEN; extern DPI_AWARENESS get_thread_dpi_awareness(void) DECLSPEC_HIDDEN; extern RECT get_virtual_screen_rect( UINT dpi ) DECLSPEC_HIDDEN; +extern BOOL is_window_rect_full_screen( const RECT *rect ) DECLSPEC_HIDDEN; extern BOOL is_exiting_thread( DWORD tid ) DECLSPEC_HIDDEN; extern POINT map_dpi_point( POINT pt, UINT dpi_from, UINT dpi_to ) DECLSPEC_HIDDEN; extern RECT map_dpi_rect( RECT rect, UINT dpi_from, UINT dpi_to ) DECLSPEC_HIDDEN; @@ -365,6 +366,9 @@ extern void user_lock(void) DECLSPEC_HIDDEN; extern void user_unlock(void) DECLSPEC_HIDDEN; extern void user_check_not_lock(void) DECLSPEC_HIDDEN; +/* winstation.c */ +extern BOOL is_virtual_desktop(void) DECLSPEC_HIDDEN; + /* window.c */ struct tagWND; extern HDWP begin_defer_window_pos( INT count ) DECLSPEC_HIDDEN; @@ -411,6 +415,11 @@ extern ULONG set_window_style( HWND hwnd, ULONG set_bits, ULONG clear_bits ) DEC extern BOOL show_owned_popups( HWND owner, BOOL show ) DECLSPEC_HIDDEN; extern void update_window_state( HWND hwnd ) DECLSPEC_HIDDEN; extern HWND window_from_point( HWND hwnd, POINT pt, INT *hittest ) DECLSPEC_HIDDEN; +extern HWND get_shell_window(void) DECLSPEC_HIDDEN; +extern HWND get_progman_window(void) DECLSPEC_HIDDEN; +extern HWND set_progman_window( HWND hwnd ) DECLSPEC_HIDDEN; +extern HWND get_taskman_window(void) DECLSPEC_HIDDEN; +extern HWND set_taskman_window( HWND hwnd ) DECLSPEC_HIDDEN; /* to release pointers retrieved by win_get_ptr */ static inline void release_win_ptr( struct tagWND *ptr ) @@ -458,6 +467,7 @@ extern CPTABLEINFO ansi_cp DECLSPEC_HIDDEN; CPTABLEINFO *get_cptable( WORD cp ) DECLSPEC_HIDDEN; const NLS_LOCALE_DATA *get_locale_data( LCID lcid ) DECLSPEC_HIDDEN; +extern BOOL translate_charset_info( DWORD *src, CHARSETINFO *cs, DWORD flags ) DECLSPEC_HIDDEN; DWORD win32u_mbtowc( CPTABLEINFO *info, WCHAR *dst, DWORD dstlen, const char *src, DWORD srclen ) DECLSPEC_HIDDEN; DWORD win32u_wctomb( CPTABLEINFO *info, char *dst, DWORD dstlen, const WCHAR *src, @@ -525,4 +535,13 @@ static inline const char *debugstr_color( COLORREF color ) return wine_dbg_sprintf( "RGB(%02x,%02x,%02x)", GetRValue(color), GetGValue(color), GetBValue(color) ); } +static inline BOOL intersect_rect( RECT *dst, const RECT *src1, const RECT *src2 ) +{ + dst->left = max( src1->left, src2->left ); + dst->top = max( src1->top, src2->top ); + dst->right = min( src1->right, src2->right ); + dst->bottom = min( src1->bottom, src2->bottom ); + return !IsRectEmpty( dst ); +} + #endif /* __WINE_WIN32U_PRIVATE */ diff --git a/dlls/win32u/window.c b/dlls/win32u/window.c index a07be55e883..81a13c7ad65 100644 --- a/dlls/win32u/window.c +++ b/dlls/win32u/window.c @@ -101,6 +101,26 @@ void *get_user_handle_ptr( HANDLE handle, unsigned int type ) return ptr; } +/*********************************************************************** + * next_process_user_handle_ptr + * + * user_lock must be held by caller. + */ +void *next_process_user_handle_ptr( HANDLE *handle, unsigned int type ) +{ + struct user_object *ptr; + WORD index = *handle ? USER_HANDLE_TO_INDEX( *handle ) + 1 : 0; + + while (index < NB_USER_HANDLES) + { + if (!(ptr = user_handles[index++])) continue; /* OBJ_OTHER_PROCESS */ + if (ptr->type != type) continue; + *handle = ptr->handle; + return ptr; + } + return NULL; +} + /*********************************************************************** * set_user_handle_ptr */ @@ -142,27 +162,6 @@ void *free_user_handle( HANDLE handle, unsigned int type ) return ptr; } -/*********************************************************************** - * next_thread_window - */ -static WND *next_thread_window_ptr( HWND *hwnd ) -{ - struct user_object *ptr; - WND *win; - WORD index = *hwnd ? USER_HANDLE_TO_INDEX( *hwnd ) + 1 : 0; - - while (index < NB_USER_HANDLES) - { - if (!(ptr = user_handles[index++])) continue; - if (ptr->type != NTUSER_OBJ_WINDOW) continue; - win = (WND *)ptr; - if (win->tid != GetCurrentThreadId()) continue; - *hwnd = ptr->handle; - return win; - } - return NULL; -} - /******************************************************************* * get_hwnd_message_parent * @@ -4865,13 +4864,14 @@ BOOL WINAPI NtUserDestroyWindow( HWND hwnd ) void destroy_thread_windows(void) { WND *win, *free_list = NULL; - HWND hwnd = 0; + HANDLE handle = 0; user_lock(); - while ((win = next_thread_window_ptr( &hwnd ))) + while ((win = next_process_user_handle_ptr( &handle, NTUSER_OBJ_WINDOW ))) { + if (win->tid != GetCurrentThreadId()) continue; free_dce( win->dce, win->obj.handle ); - set_user_handle_ptr( hwnd, NULL ); + set_user_handle_ptr( handle, NULL ); win->obj.handle = free_list; free_list = win; } @@ -4969,12 +4969,10 @@ static WND *create_window_handle( HWND parent, HWND owner, UNICODE_STRING *name, if (name->Buffer == (const WCHAR *)DESKTOP_CLASS_ATOM) { - if (!thread_info->top_window) - thread_info->top_window = HandleToUlong( full_parent ? full_parent : handle ); + if (!thread_info->top_window) thread_info->top_window = HandleToUlong( full_parent ? full_parent : handle ); else assert( full_parent == UlongToHandle( thread_info->top_window )); - if (full_parent && - !user_driver->pCreateDesktopWindow( UlongToHandle( thread_info->top_window ))) - ERR( "failed to create desktop window\n" ); + if (!thread_info->top_window) ERR_(win)( "failed to create desktop window\n" ); + else user_driver->pSetDesktopWindow( UlongToHandle( thread_info->top_window )); register_builtin_classes(); } else /* HWND_MESSAGE parent */ @@ -5684,3 +5682,116 @@ DWORD WINAPI NtUserDragObject( HWND parent, HWND hwnd, UINT fmt, ULONG_PTR data, return 0; } + + +HWND get_shell_window(void) +{ + HWND hwnd = 0; + + SERVER_START_REQ(set_global_windows) + { + req->flags = 0; + if (!wine_server_call_err(req)) + hwnd = wine_server_ptr_handle( reply->old_shell_window ); + } + SERVER_END_REQ; + + return hwnd; +} + +/*********************************************************************** +* NtUserSetShellWindowEx (win32u.@) +*/ +BOOL WINAPI NtUserSetShellWindowEx( HWND shell, HWND list_view ) +{ + BOOL ret; + + /* shell = Progman[Program Manager] + * |-> SHELLDLL_DefView + * list_view = | |-> SysListView32 + * | | |-> tooltips_class32 + * | | + * | |-> SysHeader32 + * | + * |-> ProxyTarget + */ + + if (get_shell_window()) + return FALSE; + + if (get_window_long( shell, GWL_EXSTYLE ) & WS_EX_TOPMOST) + return FALSE; + + if (list_view != shell && (get_window_long( list_view, GWL_EXSTYLE ) & WS_EX_TOPMOST)) + return FALSE; + + if (list_view && list_view != shell) + NtUserSetWindowPos( list_view, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE ); + + NtUserSetWindowPos( shell, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE ); + + SERVER_START_REQ(set_global_windows) + { + req->flags = SET_GLOBAL_SHELL_WINDOWS; + req->shell_window = wine_server_user_handle( shell ); + req->shell_listview = wine_server_user_handle( list_view ); + ret = !wine_server_call_err(req); + } + SERVER_END_REQ; + return ret; +} + +HWND get_progman_window(void) +{ + HWND ret = 0; + + SERVER_START_REQ(set_global_windows) + { + req->flags = 0; + if (!wine_server_call_err(req)) + ret = wine_server_ptr_handle( reply->old_progman_window ); + } + SERVER_END_REQ; + return ret; +} + +HWND set_progman_window( HWND hwnd ) +{ + SERVER_START_REQ(set_global_windows) + { + req->flags = SET_GLOBAL_PROGMAN_WINDOW; + req->progman_window = wine_server_user_handle( hwnd ); + if (wine_server_call_err( req )) hwnd = 0; + } + SERVER_END_REQ; + return hwnd; +} + +HWND get_taskman_window(void) +{ + HWND ret = 0; + + SERVER_START_REQ(set_global_windows) + { + req->flags = 0; + if (!wine_server_call_err(req)) + ret = wine_server_ptr_handle( reply->old_taskman_window ); + } + SERVER_END_REQ; + return ret; +} + +HWND set_taskman_window( HWND hwnd ) +{ + /* hwnd = MSTaskSwWClass + * |-> SysTabControl32 + */ + SERVER_START_REQ(set_global_windows) + { + req->flags = SET_GLOBAL_TASKMAN_WINDOW; + req->taskman_window = wine_server_user_handle( hwnd ); + if (wine_server_call_err( req )) hwnd = 0; + } + SERVER_END_REQ; + return hwnd; +} diff --git a/dlls/win32u/winstation.c b/dlls/win32u/winstation.c index 79808010598..9ddc67fcf8f 100644 --- a/dlls/win32u/winstation.c +++ b/dlls/win32u/winstation.c @@ -40,6 +40,16 @@ WINE_DECLARE_DEBUG_CHANNEL(win); #define DESKTOP_ALL_ACCESS 0x01ff +BOOL is_virtual_desktop(void) +{ + HANDLE desktop = NtUserGetThreadDesktop( GetCurrentThreadId() ); + USEROBJECTFLAGS flags = {0}; + DWORD len; + + if (!NtUserGetObjectInformation( desktop, UOI_FLAGS, &flags, sizeof(flags), &len )) return FALSE; + return !!(flags.dwFlags & DF_WINE_CREATE_DESKTOP); +} + /*********************************************************************** * NtUserCreateWindowStation (win32u.@) */ @@ -141,9 +151,10 @@ HDESK WINAPI NtUserCreateDesktopEx( OBJECT_ATTRIBUTES *attr, UNICODE_STRING *dev DEVMODEW *devmode, DWORD flags, ACCESS_MASK access, ULONG heap_size ) { + WCHAR buffer[MAX_PATH]; HANDLE ret; - if ((device && device->Length) || devmode) + if ((device && device->Length) || (devmode && !(flags & DF_WINE_CREATE_DESKTOP))) { RtlSetLastWin32Error( ERROR_INVALID_PARAMETER ); return 0; @@ -163,6 +174,15 @@ HDESK WINAPI NtUserCreateDesktopEx( OBJECT_ATTRIBUTES *attr, UNICODE_STRING *dev ret = wine_server_ptr_handle( reply->handle ); } SERVER_END_REQ; + if (!devmode) return ret; + + lstrcpynW( buffer, attr->ObjectName->Buffer, attr->ObjectName->Length / sizeof(WCHAR) + 1 ); + if (!user_driver->pCreateDesktop( buffer, devmode->dmPelsWidth, devmode->dmPelsHeight )) + { + NtUserCloseDesktop( ret ); + return 0; + } + return ret; } @@ -520,9 +540,8 @@ HWND get_desktop_window(void) SERVER_END_REQ; } - if (!thread_info->top_window || - !user_driver->pCreateDesktopWindow( UlongToHandle( thread_info->top_window ))) - ERR_(win)( "failed to create desktop window\n" ); + if (!thread_info->top_window) ERR_(win)( "failed to create desktop window\n" ); + else user_driver->pSetDesktopWindow( UlongToHandle( thread_info->top_window )); register_builtin_classes(); return UlongToHandle( thread_info->top_window ); diff --git a/dlls/windows.media.speech/Makefile.in b/dlls/windows.media.speech/Makefile.in index 10903cb1d7b..455f81c0840 100644 --- a/dlls/windows.media.speech/Makefile.in +++ b/dlls/windows.media.speech/Makefile.in @@ -1,5 +1,6 @@ MODULE = windows.media.speech.dll -IMPORTS = combase uuid +UNIXLIB = windows.media.speech.so +IMPORTS = combase uuid user32 C_SRCS = \ async.c \ @@ -8,6 +9,7 @@ C_SRCS = \ main.c \ recognizer.c \ synthesizer.c \ + unixlib.c \ vector.c IDL_SRCS = classes.idl diff --git a/dlls/windows.media.speech/main.c b/dlls/windows.media.speech/main.c index e772a791588..d53e1599eb8 100644 --- a/dlls/windows.media.speech/main.c +++ b/dlls/windows.media.speech/main.c @@ -20,10 +20,36 @@ #include "initguid.h" #include "private.h" +#include "unixlib.h" + #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(speech); +BOOL WINAPI DllMain( HINSTANCE instance, DWORD reason, void *reserved ) +{ + NTSTATUS status; + + switch (reason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(instance); + + if ((status = __wine_init_unix_call())) + ERR("loading the unixlib failed with status %#lx.\n", status); + + if ((status = WINE_UNIX_CALL(unix_process_attach, NULL))) + WARN("initializing the unixlib failed with status %#lx.\n", status); + + break; + case DLL_PROCESS_DETACH: + WINE_UNIX_CALL(unix_process_detach, NULL); + break; + } + + return TRUE; +} + HRESULT WINAPI DllGetClassObject(REFCLSID clsid, REFIID riid, void **out) { FIXME("clsid %s, riid %s, out %p stub!\n", debugstr_guid(clsid), debugstr_guid(riid), out); diff --git a/dlls/windows.media.speech/private.h b/dlls/windows.media.speech/private.h index adc3987b031..60d09c9f7d1 100644 --- a/dlls/windows.media.speech/private.h +++ b/dlls/windows.media.speech/private.h @@ -22,10 +22,16 @@ #include +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "winerror.h" +#include "winternl.h" #define COBJMACROS +#include "corerror.h" #include "windef.h" #include "winbase.h" #include "winstring.h" +#include "winuser.h" #include "objbase.h" #include "activation.h" @@ -42,6 +48,8 @@ #include "wine/list.h" +#define SPERR_WINRT_INTERNAL_ERROR 0x800455a0 + /* * * Windows.Media.SpeechRecognition @@ -69,8 +77,8 @@ struct vector_iids const GUID *view; }; -typedef HRESULT (WINAPI *async_action_callback)( IInspectable *invoker ); -typedef HRESULT (WINAPI *async_operation_inspectable_callback)( IInspectable *invoker, IInspectable **result ); +typedef HRESULT (*async_action_callback)( IInspectable *invoker ); +typedef HRESULT (*async_operation_inspectable_callback)( IInspectable *invoker, IInspectable **result ); HRESULT async_action_create( IInspectable *invoker, async_action_callback callback, IAsyncAction **out ); HRESULT async_operation_inspectable_create( const GUID *iid, IInspectable *invoker, async_operation_inspectable_callback callback, diff --git a/dlls/windows.media.speech/recognizer.c b/dlls/windows.media.speech/recognizer.c index bdcc57f883e..218453a2203 100644 --- a/dlls/windows.media.speech/recognizer.c +++ b/dlls/windows.media.speech/recognizer.c @@ -19,9 +19,624 @@ #include "private.h" +#include "initguid.h" +#include "audioclient.h" +#include "mmdeviceapi.h" + #include "wine/debug.h" -WINE_DEFAULT_DEBUG_CHANNEL(speech); +#include "unixlib.h" +#include "wine/unixlib.h" + +WINE_DEFAULT_DEBUG_CHANNEL(speech); + +static const char *debugstr_hstring(HSTRING hstr) +{ + const WCHAR *str; + UINT32 len; + if (hstr && !((ULONG_PTR)hstr >> 16)) return "(invalid)"; + str = WindowsGetStringRawBuffer(hstr, &len); + return wine_dbgstr_wn(str, len); +} + +struct map_view_hstring_vector_view_hstring +{ + IMapView_HSTRING_IVectorView_HSTRING IMapView_HSTRING_IVectorView_HSTRING_iface; + LONG ref; +}; + +static inline struct map_view_hstring_vector_view_hstring *impl_from_IMapView_HSTRING_IVectorView_HSTRING( IMapView_HSTRING_IVectorView_HSTRING *iface ) +{ + return CONTAINING_RECORD(iface, struct map_view_hstring_vector_view_hstring, IMapView_HSTRING_IVectorView_HSTRING_iface); +} + +HRESULT WINAPI map_view_hstring_vector_view_hstring_QueryInterface( IMapView_HSTRING_IVectorView_HSTRING *iface, REFIID iid, void **out ) +{ + struct map_view_hstring_vector_view_hstring *impl = impl_from_IMapView_HSTRING_IVectorView_HSTRING(iface); + + TRACE("iface %p, iid %s, out %p.\n", iface, debugstr_guid(iid), out); + + if (IsEqualGUID(iid, &IID_IUnknown) || + IsEqualGUID(iid, &IID_IInspectable) || + IsEqualGUID(iid, &IID_IMapView_HSTRING_IVectorView_HSTRING)) + { + IInspectable_AddRef((*out = &impl->IMapView_HSTRING_IVectorView_HSTRING_iface)); + return S_OK; + } + + WARN("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); + *out = NULL; + return E_NOINTERFACE; +} + +ULONG WINAPI map_view_hstring_vector_view_hstring_AddRef( IMapView_HSTRING_IVectorView_HSTRING *iface ) +{ + struct map_view_hstring_vector_view_hstring *impl = impl_from_IMapView_HSTRING_IVectorView_HSTRING(iface); + ULONG ref = InterlockedIncrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + return ref; +} + +ULONG WINAPI map_view_hstring_vector_view_hstring_Release( IMapView_HSTRING_IVectorView_HSTRING *iface ) +{ + struct map_view_hstring_vector_view_hstring *impl = impl_from_IMapView_HSTRING_IVectorView_HSTRING(iface); + + ULONG ref = InterlockedDecrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + + if(!ref) + free(impl); + + return ref; +} + +HRESULT WINAPI map_view_hstring_vector_view_hstring_GetIids( IMapView_HSTRING_IVectorView_HSTRING *iface, ULONG *iidCount, IID **iids ) +{ + FIXME("iface %p stub!\n", iface); + return E_NOTIMPL; +} + +HRESULT WINAPI map_view_hstring_vector_view_hstring_GetRuntimeClassName( IMapView_HSTRING_IVectorView_HSTRING *iface, HSTRING *className ) +{ + FIXME("iface %p stub!\n", iface); + return E_NOTIMPL; +} + +HRESULT WINAPI map_view_hstring_vector_view_hstring_GetTrustLevel( IMapView_HSTRING_IVectorView_HSTRING *iface, TrustLevel *trustLevel ) +{ + FIXME("iface %p stub!\n", iface); + return E_NOTIMPL; +} + +HRESULT WINAPI map_view_hstring_vector_view_hstring_Lookup( IMapView_HSTRING_IVectorView_HSTRING *iface, HSTRING key, IVectorView_HSTRING **value ) +{ + FIXME("iface %p stub!\n", iface); + return E_NOTIMPL; +} + +HRESULT WINAPI map_view_hstring_vector_view_hstring_get_Size( IMapView_HSTRING_IVectorView_HSTRING *iface, unsigned int *size ) +{ + FIXME("iface %p stub!\n", iface); + *size = 0; + return S_OK; +} + +HRESULT WINAPI map_view_hstring_vector_view_hstring_HasKey( IMapView_HSTRING_IVectorView_HSTRING *iface, HSTRING key, boolean *found ) +{ + FIXME("iface %p stub!\n", iface); + return E_NOTIMPL; +} + +HRESULT WINAPI map_view_hstring_vector_view_hstring_Split( IMapView_HSTRING_IVectorView_HSTRING *iface, IMapView_HSTRING_IVectorView_HSTRING **first, IMapView_HSTRING_IVectorView_HSTRING **second ) +{ + FIXME("iface %p stub!\n", iface); + return E_NOTIMPL; +} + +static const struct IMapView_HSTRING_IVectorView_HSTRINGVtbl map_view_hstring_vector_view_hstring_vtbl = +{ + /* IUnknown methods */ + map_view_hstring_vector_view_hstring_QueryInterface, + map_view_hstring_vector_view_hstring_AddRef, + map_view_hstring_vector_view_hstring_Release, + /* IInspectable methods */ + map_view_hstring_vector_view_hstring_GetIids, + map_view_hstring_vector_view_hstring_GetRuntimeClassName, + map_view_hstring_vector_view_hstring_GetTrustLevel, + /* IMapView* > methods */ + map_view_hstring_vector_view_hstring_Lookup, + map_view_hstring_vector_view_hstring_get_Size, + map_view_hstring_vector_view_hstring_HasKey, + map_view_hstring_vector_view_hstring_Split +}; + + +static HRESULT map_view_hstring_vector_view_hstring_create( IMapView_HSTRING_IVectorView_HSTRING **out ) +{ + struct map_view_hstring_vector_view_hstring *impl; + + TRACE("out %p.\n", out); + + if (!(impl = calloc(1, sizeof(*impl)))) + { + *out = NULL; + return E_OUTOFMEMORY; + } + + impl->IMapView_HSTRING_IVectorView_HSTRING_iface.lpVtbl = &map_view_hstring_vector_view_hstring_vtbl; + impl->ref = 1; + + *out = &impl->IMapView_HSTRING_IVectorView_HSTRING_iface; + TRACE("created %p\n", *out); + return S_OK; +} + +struct semantic_interpretation +{ + ISpeechRecognitionSemanticInterpretation ISpeechRecognitionSemanticInterpretation_iface; + LONG ref; +}; + +static inline struct semantic_interpretation *impl_from_ISpeechRecognitionSemanticInterpretation( ISpeechRecognitionSemanticInterpretation *iface ) +{ + return CONTAINING_RECORD(iface, struct semantic_interpretation, ISpeechRecognitionSemanticInterpretation_iface); +} + +HRESULT WINAPI semantic_interpretation_QueryInterface( ISpeechRecognitionSemanticInterpretation *iface, REFIID iid, void **out ) +{ + struct semantic_interpretation *impl = impl_from_ISpeechRecognitionSemanticInterpretation(iface); + + TRACE("iface %p, iid %s, out %p.\n", iface, debugstr_guid(iid), out); + + if (IsEqualGUID(iid, &IID_IUnknown) || + IsEqualGUID(iid, &IID_IInspectable) || + IsEqualGUID(iid, &IID_ISpeechRecognitionSemanticInterpretation)) + { + IInspectable_AddRef((*out = &impl->ISpeechRecognitionSemanticInterpretation_iface)); + return S_OK; + } + + WARN("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); + *out = NULL; + return E_NOINTERFACE; +} + +ULONG WINAPI semantic_interpretation_AddRef( ISpeechRecognitionSemanticInterpretation *iface ) +{ + struct semantic_interpretation *impl = impl_from_ISpeechRecognitionSemanticInterpretation(iface); + ULONG ref = InterlockedIncrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + return ref; +} + +ULONG WINAPI semantic_interpretation_Release( ISpeechRecognitionSemanticInterpretation *iface ) +{ + struct semantic_interpretation *impl = impl_from_ISpeechRecognitionSemanticInterpretation(iface); + + ULONG ref = InterlockedDecrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + + if(!ref) + free(impl); + + return ref; +} + +HRESULT WINAPI semantic_interpretation_GetIids( ISpeechRecognitionSemanticInterpretation *iface, ULONG *iid_count, IID **iids ) +{ + FIXME("iface %p, iid_count %p, iids %p stub!\n", iface, iid_count, iids); + return E_NOTIMPL; +} + +HRESULT WINAPI semantic_interpretation_GetRuntimeClassName( ISpeechRecognitionSemanticInterpretation *iface, HSTRING *class_name ) +{ + FIXME("iface %p, class_name %p stub!\n", iface, class_name); + return E_NOTIMPL; +} + +HRESULT WINAPI semantic_interpretation_GetTrustLevel( ISpeechRecognitionSemanticInterpretation *iface, TrustLevel *trust_level ) +{ + FIXME("iface %p, trust_level %p stub!\n", iface, trust_level); + return E_NOTIMPL; +} + +HRESULT WINAPI semantic_interpretation_get_Properties( ISpeechRecognitionSemanticInterpretation *iface, IMapView_HSTRING_IVectorView_HSTRING **value ) +{ + FIXME("iface %p stub!\n", iface); + return map_view_hstring_vector_view_hstring_create(value); +} + +static const struct ISpeechRecognitionSemanticInterpretationVtbl semantic_interpretation_vtbl = +{ + /* IUnknown methods */ + semantic_interpretation_QueryInterface, + semantic_interpretation_AddRef, + semantic_interpretation_Release, + /* IInspectable methods */ + semantic_interpretation_GetIids, + semantic_interpretation_GetRuntimeClassName, + semantic_interpretation_GetTrustLevel, + /* ISpeechRecognitionSemanticInterpretation methods */ + semantic_interpretation_get_Properties +}; + + +static HRESULT semantic_interpretation_create( ISpeechRecognitionSemanticInterpretation **out ) +{ + struct semantic_interpretation *impl; + + TRACE("out %p.\n", out); + + if (!(impl = calloc(1, sizeof(*impl)))) + { + *out = NULL; + return E_OUTOFMEMORY; + } + + impl->ISpeechRecognitionSemanticInterpretation_iface.lpVtbl = &semantic_interpretation_vtbl; + impl->ref = 1; + + *out = &impl->ISpeechRecognitionSemanticInterpretation_iface; + TRACE("created %p\n", *out); + return S_OK; +} + +struct recognition_result +{ + ISpeechRecognitionResult ISpeechRecognitionResult_iface; + ISpeechRecognitionResult2 ISpeechRecognitionResult2_iface; + LONG ref; + + ISpeechRecognitionConstraint *constraint; + HSTRING text; +}; + +static inline struct recognition_result *impl_from_ISpeechRecognitionResult( ISpeechRecognitionResult *iface ) +{ + return CONTAINING_RECORD(iface, struct recognition_result, ISpeechRecognitionResult_iface); +} + +static HRESULT WINAPI recognition_result_QueryInterface( ISpeechRecognitionResult *iface, REFIID iid, void **out ) +{ + struct recognition_result *impl = impl_from_ISpeechRecognitionResult(iface); + + TRACE("iface %p, iid %s, out %p.\n", iface, debugstr_guid(iid), out); + + if (IsEqualGUID(iid, &IID_IUnknown) || + IsEqualGUID(iid, &IID_IInspectable) || + IsEqualGUID(iid, &IID_IAgileObject) || + IsEqualGUID(iid, &IID_ISpeechRecognitionResult)) + { + IInspectable_AddRef((*out = &impl->ISpeechRecognitionResult_iface)); + return S_OK; + } + + if (IsEqualGUID(iid, &IID_ISpeechRecognitionResult2)) + { + IInspectable_AddRef((*out = &impl->ISpeechRecognitionResult2_iface)); + return S_OK; + } + + WARN("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); + *out = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI recognition_result_AddRef( ISpeechRecognitionResult *iface ) +{ + struct recognition_result *impl = impl_from_ISpeechRecognitionResult(iface); + ULONG ref = InterlockedIncrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + return ref; +} + +static ULONG WINAPI recognition_result_Release( ISpeechRecognitionResult *iface ) +{ + struct recognition_result *impl = impl_from_ISpeechRecognitionResult(iface); + + ULONG ref = InterlockedDecrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + + if(!ref) + { + ISpeechRecognitionConstraint_Release(impl->constraint); + WindowsDeleteString(impl->text); + free(impl); + } + + return ref; +} + +static HRESULT WINAPI recognition_result_GetIids( ISpeechRecognitionResult *iface, ULONG *iid_count, IID **iids ) +{ + FIXME("iface %p, iid_count %p, iids %p stub!\n", iface, iid_count, iids); + return E_NOTIMPL; +} + +static HRESULT WINAPI recognition_result_GetRuntimeClassName( ISpeechRecognitionResult *iface, HSTRING *class_name ) +{ + FIXME("iface %p, class_name %p stub!\n", iface, class_name); + return E_NOTIMPL; +} + +static HRESULT WINAPI recognition_result_GetTrustLevel( ISpeechRecognitionResult *iface, TrustLevel *trust_level ) +{ + FIXME("iface %p, trust_level %p stub!\n", iface, trust_level); + return E_NOTIMPL; +} + +static HRESULT WINAPI recognition_result_get_Status( ISpeechRecognitionResult *iface, SpeechRecognitionResultStatus *value ) +{ + FIXME("iface %p, operation %p stub!\n", iface, value); + *value = SpeechRecognitionResultStatus_Success; + return S_OK; +} + +static HRESULT WINAPI recognition_result_get_Text( ISpeechRecognitionResult *iface, HSTRING *value ) +{ + struct recognition_result *impl = impl_from_ISpeechRecognitionResult(iface); + TRACE("iface %p, operation %p, text: %s.\n", iface, value, debugstr_hstring(impl->text)); + return WindowsDuplicateString(impl->text, value); +} + +static HRESULT WINAPI recognition_result_get_Confidence( ISpeechRecognitionResult *iface, SpeechRecognitionConfidence *value ) +{ + FIXME("iface %p, operation %p semi stub!\n", iface, value); + *value = SpeechRecognitionConfidence_High; + return S_OK; +} + +static HRESULT WINAPI recognition_result_get_SemanticInterpretation( ISpeechRecognitionResult *iface, + ISpeechRecognitionSemanticInterpretation **value ) +{ + FIXME("iface %p, operation %p stub!\n", iface, value); + return semantic_interpretation_create(value); +} + +static HRESULT WINAPI recognition_result_GetAlternates( ISpeechRecognitionResult *iface, + UINT32 max_amount, + IVectorView_SpeechRecognitionResult **results ) +{ + IVector_IInspectable *vector; + struct vector_iids constraints_iids = + { + .iterable = &IID_IVectorView_SpeechRecognitionResult, + .iterator = &IID_IVectorView_SpeechRecognitionResult, + .vector = &IID_IVector_IInspectable, + .view = &IID_IVectorView_SpeechRecognitionResult, + }; + + FIXME("iface %p, max_amount %u, results %p stub!\n", iface, max_amount, results); + + vector_inspectable_create(&constraints_iids, (IVector_IInspectable **)&vector); + IVector_IInspectable_GetView(vector, (IVectorView_IInspectable **)results); + IVector_IInspectable_Release(vector); + return S_OK; +} + +static HRESULT WINAPI recognition_result_get_Constraint( ISpeechRecognitionResult *iface, ISpeechRecognitionConstraint **value ) +{ + struct recognition_result *impl = impl_from_ISpeechRecognitionResult(iface); + TRACE("iface %p, operation %p.\n", iface, value); + ISpeechRecognitionConstraint_AddRef((*value = impl->constraint)); + return S_OK; +} + +static HRESULT WINAPI recognition_result_get_RulePath( ISpeechRecognitionResult *iface, IVectorView_HSTRING **value ) +{ + FIXME("iface %p stub!\n", iface); + return E_NOTIMPL; +} + +static HRESULT WINAPI recognition_result_get_RawConfidence( ISpeechRecognitionResult *iface, DOUBLE *value ) +{ + FIXME("iface %p stub!\n", iface); + return E_NOTIMPL; +} + +static const struct ISpeechRecognitionResultVtbl recognition_result_vtbl = +{ + /* IUnknown methods */ + recognition_result_QueryInterface, + recognition_result_AddRef, + recognition_result_Release, + /* IInspectable methods */ + recognition_result_GetIids, + recognition_result_GetRuntimeClassName, + recognition_result_GetTrustLevel, + /* ISpeechRecognitionResult methods */ + recognition_result_get_Status, + recognition_result_get_Text, + recognition_result_get_Confidence, + recognition_result_get_SemanticInterpretation, + recognition_result_GetAlternates, + recognition_result_get_Constraint, + recognition_result_get_RulePath, + recognition_result_get_RawConfidence +}; + +DEFINE_IINSPECTABLE(recognition_result2, ISpeechRecognitionResult2, struct recognition_result, ISpeechRecognitionResult_iface) + +static HRESULT WINAPI recognition_result2_get_PhraseStartTime( ISpeechRecognitionResult2 *iface, DateTime *value ) +{ + DateTime dt = { .UniversalTime = 0 }; + FIXME("iface %p, value %p stub!\n", iface, value); + *value = dt; + return S_OK; +} + + +static HRESULT WINAPI recognition_result2_get_PhraseDuration( ISpeechRecognitionResult2 *iface, TimeSpan *value ) +{ + TimeSpan ts = { .Duration = 50000000LL }; /* Use 5 seconds as stub value. */ + FIXME("iface %p, value %p stub!\n", iface, value); + *value = ts; + return S_OK; +} + +static const struct ISpeechRecognitionResult2Vtbl recognition_result2_vtbl = +{ + /* IUnknown methods */ + recognition_result2_QueryInterface, + recognition_result2_AddRef, + recognition_result2_Release, + /* IInspectable methods */ + recognition_result2_GetIids, + recognition_result2_GetRuntimeClassName, + recognition_result2_GetTrustLevel, + /* ISpeechRecognitionResult2 methods */ + recognition_result2_get_PhraseStartTime, + recognition_result2_get_PhraseDuration +}; + +static HRESULT WINAPI recognition_result_create( ISpeechRecognitionConstraint *constraint, + HSTRING result_text, + ISpeechRecognitionResult **out ) +{ + struct recognition_result *impl; + + TRACE("out %p.\n", out); + + if (!(impl = calloc(1, sizeof(*impl)))) + { + *out = NULL; + return E_OUTOFMEMORY; + } + + impl->ISpeechRecognitionResult_iface.lpVtbl = &recognition_result_vtbl; + impl->ISpeechRecognitionResult2_iface.lpVtbl = &recognition_result2_vtbl; + impl->ref = 1; + + if (constraint) ISpeechRecognitionConstraint_AddRef((impl->constraint = constraint)); + WindowsDuplicateString(result_text, &impl->text); + + *out = &impl->ISpeechRecognitionResult_iface; + + TRACE("created %p.\n", *out); + + return S_OK; +} + +struct recognition_result_event_args +{ + ISpeechContinuousRecognitionResultGeneratedEventArgs ISpeechContinuousRecognitionResultGeneratedEventArgs_iface; + LONG ref; + + ISpeechRecognitionResult *result; +}; + +static inline struct recognition_result_event_args *impl_from_ISpeechContinuousRecognitionResultGeneratedEventArgs( ISpeechContinuousRecognitionResultGeneratedEventArgs *iface ) +{ + return CONTAINING_RECORD(iface, struct recognition_result_event_args, ISpeechContinuousRecognitionResultGeneratedEventArgs_iface); +} + +static HRESULT WINAPI recognition_result_event_args_QueryInterface( ISpeechContinuousRecognitionResultGeneratedEventArgs *iface, REFIID iid, void **out ) +{ + struct recognition_result_event_args *impl = impl_from_ISpeechContinuousRecognitionResultGeneratedEventArgs(iface); + + TRACE("iface %p, iid %s, out %p.\n", iface, debugstr_guid(iid), out); + + if (IsEqualGUID(iid, &IID_IUnknown) || + IsEqualGUID(iid, &IID_IInspectable) || + IsEqualGUID(iid, &IID_IAgileObject) || + IsEqualGUID(iid, &IID_ISpeechContinuousRecognitionResultGeneratedEventArgs)) + { + IInspectable_AddRef((*out = &impl->ISpeechContinuousRecognitionResultGeneratedEventArgs_iface)); + return S_OK; + } + + WARN("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); + *out = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI recognition_result_event_args_AddRef( ISpeechContinuousRecognitionResultGeneratedEventArgs *iface ) +{ + struct recognition_result_event_args *impl = impl_from_ISpeechContinuousRecognitionResultGeneratedEventArgs(iface); + ULONG ref = InterlockedIncrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + return ref; +} + +static ULONG WINAPI recognition_result_event_args_Release( ISpeechContinuousRecognitionResultGeneratedEventArgs *iface ) +{ + struct recognition_result_event_args *impl = impl_from_ISpeechContinuousRecognitionResultGeneratedEventArgs(iface); + + ULONG ref = InterlockedDecrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + + if (!ref) + { + if (impl->result) ISpeechRecognitionResult_Release(impl->result); + free(impl); + } + + return ref; +} + +static HRESULT WINAPI recognition_result_event_args_GetIids( ISpeechContinuousRecognitionResultGeneratedEventArgs *iface, ULONG *iid_count, IID **iids ) +{ + FIXME("iface %p, iid_count %p, iids %p stub!\n", iface, iid_count, iids); + return E_NOTIMPL; +} + +static HRESULT WINAPI recognition_result_event_args_GetRuntimeClassName( ISpeechContinuousRecognitionResultGeneratedEventArgs *iface, HSTRING *class_name ) +{ + FIXME("iface %p, class_name %p stub!\n", iface, class_name); + return E_NOTIMPL; +} + +static HRESULT WINAPI recognition_result_event_args_GetTrustLevel( ISpeechContinuousRecognitionResultGeneratedEventArgs *iface, TrustLevel *trust_level ) +{ + FIXME("iface %p, trust_level %p stub!\n", iface, trust_level); + return E_NOTIMPL; +} + +static HRESULT WINAPI recognition_result_event_args_get_Result( ISpeechContinuousRecognitionResultGeneratedEventArgs *iface, + ISpeechRecognitionResult **value ) +{ + struct recognition_result_event_args *impl = impl_from_ISpeechContinuousRecognitionResultGeneratedEventArgs(iface); + FIXME("iface %p value %p stub!\n", iface, value); + ISpeechRecognitionResult_AddRef((*value = impl->result)); + return S_OK; +} + +static const struct ISpeechContinuousRecognitionResultGeneratedEventArgsVtbl recognition_result_event_args_vtbl = +{ + /* IUnknown methods */ + recognition_result_event_args_QueryInterface, + recognition_result_event_args_AddRef, + recognition_result_event_args_Release, + /* IInspectable methods */ + recognition_result_event_args_GetIids, + recognition_result_event_args_GetRuntimeClassName, + recognition_result_event_args_GetTrustLevel, + /* ISpeechContinuousRecognitionResultGeneratedEventArgs methods */ + recognition_result_event_args_get_Result +}; + +static HRESULT WINAPI recognition_result_event_args_create( ISpeechRecognitionResult *result, + ISpeechContinuousRecognitionResultGeneratedEventArgs **out ) +{ + struct recognition_result_event_args *impl; + + TRACE("out %p.\n", out); + + if (!(impl = calloc(1, sizeof(*impl)))) + { + *out = NULL; + return E_OUTOFMEMORY; + } + + impl->ISpeechContinuousRecognitionResultGeneratedEventArgs_iface.lpVtbl = &recognition_result_event_args_vtbl; + impl->ref = 1; + if (result) ISpeechRecognitionResult_AddRef((impl->result = result)); + + *out = &impl->ISpeechContinuousRecognitionResultGeneratedEventArgs_iface; + + TRACE("created %p.\n", *out); + return S_OK; +} /* * @@ -156,8 +771,22 @@ struct session ISpeechContinuousRecognitionSession ISpeechContinuousRecognitionSession_iface; LONG ref; + IVector_ISpeechRecognitionConstraint *constraints; + + SpeechRecognizerState recognizer_state; + struct list completed_handlers; struct list result_handlers; + + IAudioClient *audio_client; + IAudioCaptureClient *capture_client; + WAVEFORMATEX capture_wfx; + + speech_recognizer_handle unix_handle; + + HANDLE worker_thread, worker_control_event, audio_buf_event; + BOOLEAN worker_running, worker_paused; + CRITICAL_SECTION cs; }; /* @@ -171,6 +800,262 @@ static inline struct session *impl_from_ISpeechContinuousRecognitionSession( ISp return CONTAINING_RECORD(iface, struct session, ISpeechContinuousRecognitionSession_iface); } +static HRESULT session_find_constraint_by_string(struct session *session, WCHAR *str, HSTRING *hstr_out, ISpeechRecognitionConstraint **out) +{ + ISpeechRecognitionListConstraint *list_constraint; + IIterable_IInspectable *constraints_iterable; + IIterator_IInspectable *constraints_iterator; + ISpeechRecognitionConstraint *constraint; + IIterable_HSTRING *commands_iterable; + IIterator_HSTRING *commands_iterator; + BOOL has_constraint, has_command; + IVector_HSTRING *commands; + const WCHAR *command_str; + HSTRING command; + HRESULT hr; + + TRACE("session %p, str %s, out %p.\n", session, debugstr_w(str), out); + + if (FAILED(hr = IVector_ISpeechRecognitionConstraint_QueryInterface(session->constraints, &IID_IIterable_ISpeechRecognitionConstraint, (void **)&constraints_iterable))) + return hr; + + if (FAILED(hr = IIterable_IInspectable_First(constraints_iterable, &constraints_iterator))) + { + IIterable_IInspectable_Release(constraints_iterable); + return hr; + } + + *out = NULL; + + for (hr = IIterator_IInspectable_get_HasCurrent(constraints_iterator, &has_constraint); SUCCEEDED(hr) && has_constraint && !(*out); hr = IIterator_IInspectable_MoveNext(constraints_iterator, &has_constraint)) + { + list_constraint = NULL; + commands_iterable = NULL; + commands_iterator = NULL; + commands = NULL; + + if (FAILED(IIterator_IInspectable_get_Current(constraints_iterator, (IInspectable **)&constraint))) + goto skip; + + if (FAILED(ISpeechRecognitionConstraint_QueryInterface(constraint, &IID_ISpeechRecognitionListConstraint, (void**)&list_constraint))) + goto skip; + + if (FAILED(ISpeechRecognitionListConstraint_get_Commands(list_constraint, &commands))) + goto skip; + + if (FAILED(IVector_HSTRING_QueryInterface(commands, &IID_IIterable_HSTRING, (void **)&commands_iterable))) + goto skip; + + if (FAILED(IIterable_HSTRING_First(commands_iterable, &commands_iterator))) + goto skip; + + for (hr = IIterator_HSTRING_get_HasCurrent(commands_iterator, &has_command); SUCCEEDED(hr) && has_command && !(*out); hr = IIterator_HSTRING_MoveNext(commands_iterator, &has_command)) + { + if (FAILED(IIterator_HSTRING_get_Current(commands_iterator, &command))) + continue; + + command_str = WindowsGetStringRawBuffer(command, NULL); + + TRACE("Comparing str %s to command_str %s.\n", debugstr_w(str), debugstr_w(command_str)); + + if (!wcsicmp(str, command_str)) + { + TRACE("constraint %p has str %s.\n", constraint, debugstr_w(str)); + ISpeechRecognitionConstraint_AddRef((*out = constraint)); + WindowsDuplicateString(command, hstr_out); + } + + WindowsDeleteString(command); + } + +skip: + if (commands_iterator) IIterator_HSTRING_Release(commands_iterator); + if (commands_iterable) IIterable_HSTRING_Release(commands_iterable); + if (commands) IVector_HSTRING_Release(commands); + + if (list_constraint) ISpeechRecognitionListConstraint_Release(list_constraint); + if (constraint) ISpeechRecognitionConstraint_Release(constraint); + } + + IIterator_IInspectable_Release(constraints_iterator); + IIterable_IInspectable_Release(constraints_iterable); + + hr = (*out) ? S_OK : COR_E_KEYNOTFOUND; + return hr; +} + +static DWORD CALLBACK session_worker_thread_cb( void *args ) +{ + ISpeechContinuousRecognitionSession *iface = args; + struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + struct speech_get_recognition_result_params recognition_result_params; + struct speech_recognize_audio_params recognize_audio_params; + ISpeechContinuousRecognitionResultGeneratedEventArgs *event_args; + ISpeechRecognitionConstraint *constraint; + ISpeechRecognitionResult *result; + BOOLEAN running = TRUE, paused = FALSE; + UINT32 frame_count, tmp_buf_size; + BYTE *audio_buf, *tmp_buf; + WCHAR *recognized_text; + DWORD flags, status; + NTSTATUS nt_status; + HANDLE events[2]; + HSTRING hstring; + HRESULT hr; + + SetThreadDescription(GetCurrentThread(), L"wine_speech_recognition_session_worker"); + + if (FAILED(hr = IAudioClient_Start(impl->audio_client))) + goto error; + + if (FAILED(hr = IAudioClient_GetBufferSize(impl->audio_client, &frame_count))) + goto error; + + tmp_buf_size = sizeof(*tmp_buf) * frame_count * impl->capture_wfx.nBlockAlign; + if (!(tmp_buf = malloc(tmp_buf_size))) + { + ERR("Memory allocation failed.\n"); + return 1; + } + + while (running) + { + BOOLEAN old_paused = paused; + UINT32 count = 0; + + events[count++] = impl->worker_control_event; + if (!paused) events[count++] = impl->audio_buf_event; + + status = WaitForMultipleObjects(count, events, FALSE, INFINITE); + if (status == 0) /* worker_control_event signaled */ + { + EnterCriticalSection(&impl->cs); + paused = impl->worker_paused; + running = impl->worker_running; + LeaveCriticalSection(&impl->cs); + + if (old_paused < paused) + { + if (FAILED(hr = IAudioClient_Stop(impl->audio_client))) goto error; + if (FAILED(hr = IAudioClient_Reset(impl->audio_client))) goto error; + TRACE("session worker paused.\n"); + } + else if (old_paused > paused) + { + if (FAILED(hr = IAudioClient_Start(impl->audio_client))) goto error; + TRACE("session worker resumed.\n"); + } + } + else if (status == 1) /* audio_buf_event signaled */ + { + SIZE_T packet_size = 0, tmp_buf_offset = 0; + UINT32 frames_available = 0; + INT recognized_text_len = 0; + + while (tmp_buf_offset < tmp_buf_size + && IAudioCaptureClient_GetBuffer(impl->capture_client, &audio_buf, &frames_available, &flags, NULL, NULL) == S_OK) + { + packet_size = frames_available * impl->capture_wfx.nBlockAlign; + if (tmp_buf_offset + packet_size > tmp_buf_size) + { + /* Defer processing until the next iteration of the worker loop. */ + IAudioCaptureClient_ReleaseBuffer(impl->capture_client, 0); + SetEvent(impl->audio_buf_event); + break; + } + + memcpy(tmp_buf + tmp_buf_offset, audio_buf, packet_size); + tmp_buf_offset += packet_size; + + IAudioCaptureClient_ReleaseBuffer(impl->capture_client, frames_available); + } + + recognize_audio_params.handle = impl->unix_handle; + recognize_audio_params.samples = tmp_buf; + recognize_audio_params.samples_size = tmp_buf_offset; + recognize_audio_params.status = RECOGNITION_STATUS_EXCEPTION; + + if (NT_ERROR(nt_status = WINE_UNIX_CALL(unix_speech_recognize_audio, &recognize_audio_params))) + WARN("unix_speech_recognize_audio failed with status %#lx.\n", nt_status); + + if (recognize_audio_params.status != RECOGNITION_STATUS_RESULT_AVAILABLE) + continue; + + recognition_result_params.handle = impl->unix_handle; + recognition_result_params.result_buf = NULL; + recognition_result_params.result_buf_size = 512; + + do + { + recognition_result_params.result_buf = realloc(recognition_result_params.result_buf, recognition_result_params.result_buf_size); + } + while (WINE_UNIX_CALL(unix_speech_get_recognition_result, &recognition_result_params) == STATUS_BUFFER_TOO_SMALL && + recognition_result_params.result_buf); + + if (!recognition_result_params.result_buf) + { + WARN("memory allocation failed.\n"); + break; + } + + /* Silence was recognized. */ + if (!strcmp(recognition_result_params.result_buf, "")) + { + free(recognition_result_params.result_buf); + continue; + } + + recognized_text_len = MultiByteToWideChar(CP_UTF8, 0, recognition_result_params.result_buf, -1, NULL, 0); + + if (!(recognized_text = malloc(recognized_text_len * sizeof(WCHAR)))) + { + free(recognition_result_params.result_buf); + WARN("memory allocation failed.\n"); + break; + } + + MultiByteToWideChar(CP_UTF8, 0, recognition_result_params.result_buf, -1, recognized_text, recognized_text_len); + + if (SUCCEEDED(hr = session_find_constraint_by_string(impl, recognized_text, &hstring, &constraint))) + { + recognition_result_create(constraint, hstring, &result); + recognition_result_event_args_create(result, &event_args); + + typed_event_handlers_notify(&impl->result_handlers, + (IInspectable *)&impl->ISpeechContinuousRecognitionSession_iface, + (IInspectable *)event_args); + + ISpeechContinuousRecognitionResultGeneratedEventArgs_Release(event_args); + ISpeechRecognitionResult_Release(result); + WindowsDeleteString(hstring); + ISpeechRecognitionConstraint_Release(constraint); + } + + free(recognized_text); + free(recognition_result_params.result_buf); + } + else + { + ERR("Unexpected state entered. Aborting worker.\n"); + break; + } + } + + if (FAILED(hr = IAudioClient_Stop(impl->audio_client))) + ERR("IAudioClient_Stop failed with %#lx.\n", hr); + + if (FAILED(hr = IAudioClient_Reset(impl->audio_client))) + ERR("IAudioClient_Reset failed with %#lx.\n", hr); + + free(tmp_buf); + + return 0; + +error: + ERR("The recognition session worker encountered a serious error and needs to stop. hr: %lx.\n", hr); + return 1; +} + static HRESULT WINAPI session_QueryInterface( ISpeechContinuousRecognitionSession *iface, REFIID iid, void **out ) { struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); @@ -201,13 +1086,38 @@ static ULONG WINAPI session_AddRef( ISpeechContinuousRecognitionSession *iface ) static ULONG WINAPI session_Release( ISpeechContinuousRecognitionSession *iface ) { struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + struct speech_release_recognizer_params release_params; ULONG ref = InterlockedDecrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); if (!ref) { + HANDLE thread; + + EnterCriticalSection(&impl->cs); + thread = impl->worker_thread; + impl->worker_running = FALSE; + impl->worker_thread = INVALID_HANDLE_VALUE; + LeaveCriticalSection(&impl->cs); + + SetEvent(impl->worker_control_event); + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + typed_event_handlers_clear(&impl->completed_handlers); typed_event_handlers_clear(&impl->result_handlers); + + IAudioCaptureClient_Release(impl->capture_client); + IAudioClient_Release(impl->audio_client); + + impl->cs.DebugInfo->Spare[0] = 0; + DeleteCriticalSection(&impl->cs); + + release_params.handle = impl->unix_handle; + WINE_UNIX_CALL(unix_speech_release_recognizer, &release_params); + + IVector_ISpeechRecognitionConstraint_Release(impl->constraints); free(impl); } @@ -244,15 +1154,45 @@ static HRESULT WINAPI session_set_AutoStopSilenceTimeout( ISpeechContinuousRecog return E_NOTIMPL; } -static HRESULT WINAPI start_callback( IInspectable *invoker ) +static HRESULT session_start_async( IInspectable *invoker ) { return S_OK; } static HRESULT WINAPI session_StartAsync( ISpeechContinuousRecognitionSession *iface, IAsyncAction **action ) { - FIXME("iface %p, action %p stub!\n", iface, action); - return async_action_create(NULL, start_callback, action); + struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + HRESULT hr; + + TRACE("iface %p, action %p.\n", iface, action); + + if (FAILED(hr = async_action_create(NULL, session_start_async, action))) + return hr; + + EnterCriticalSection(&impl->cs); + if (impl->worker_running || impl->worker_thread) + { + hr = COR_E_INVALIDOPERATION; + } + else if (!(impl->worker_thread = CreateThread(NULL, 0, session_worker_thread_cb, impl, 0, NULL))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + impl->worker_running = FALSE; + } + else + { + impl->worker_running = TRUE; + impl->recognizer_state = SpeechRecognizerState_Capturing; + } + LeaveCriticalSection(&impl->cs); + + if (FAILED(hr)) + { + IAsyncAction_Release(*action); + *action = NULL; + } + + return hr; } static HRESULT WINAPI session_StartWithModeAsync( ISpeechContinuousRecognitionSession *iface, @@ -263,10 +1203,54 @@ static HRESULT WINAPI session_StartWithModeAsync( ISpeechContinuousRecognitionSe return E_NOTIMPL; } +static HRESULT session_stop_async( IInspectable *invoker ) +{ + return S_OK; +} + static HRESULT WINAPI session_StopAsync( ISpeechContinuousRecognitionSession *iface, IAsyncAction **action ) { - FIXME("iface %p, action %p stub!\n", iface, action); - return E_NOTIMPL; + struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + HANDLE thread; + HRESULT hr; + + TRACE("iface %p, action %p.\n", iface, action); + + if (FAILED(hr = async_action_create(NULL, session_stop_async, action))) + return hr; + + EnterCriticalSection(&impl->cs); + if (impl->worker_running && impl->worker_thread) + { + thread = impl->worker_thread; + impl->worker_thread = INVALID_HANDLE_VALUE; + impl->worker_running = FALSE; + impl->worker_paused = FALSE; + impl->recognizer_state = SpeechRecognizerState_Idle; + } + else + { + hr = COR_E_INVALIDOPERATION; + } + LeaveCriticalSection(&impl->cs); + + if (SUCCEEDED(hr)) + { + SetEvent(impl->worker_control_event); + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + + EnterCriticalSection(&impl->cs); + impl->worker_thread = NULL; + LeaveCriticalSection(&impl->cs); + } + else + { + IAsyncAction_Release(*action); + *action = NULL; + } + + return hr; } static HRESULT WINAPI session_CancelAsync( ISpeechContinuousRecognitionSession *iface, IAsyncAction **action ) @@ -275,16 +1259,53 @@ static HRESULT WINAPI session_CancelAsync( ISpeechContinuousRecognitionSession * return E_NOTIMPL; } +static HRESULT session_pause_async( IInspectable *invoker ) +{ + return S_OK; +} + static HRESULT WINAPI session_PauseAsync( ISpeechContinuousRecognitionSession *iface, IAsyncAction **action ) { - FIXME("iface %p, action %p stub!\n", iface, action); - return E_NOTIMPL; + struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + HRESULT hr = S_OK; + + TRACE("iface %p, action %p.\n", iface, action); + + *action = NULL; + + if (FAILED(hr = async_action_create(NULL, session_pause_async, action))) + return hr; + + EnterCriticalSection(&impl->cs); + if (impl->worker_running) + { + impl->worker_paused = TRUE; + impl->recognizer_state = SpeechRecognizerState_Paused; + } + LeaveCriticalSection(&impl->cs); + + SetEvent(impl->worker_control_event); + + return hr; } static HRESULT WINAPI session_Resume( ISpeechContinuousRecognitionSession *iface ) { - FIXME("iface %p stub!\n", iface); - return E_NOTIMPL; + struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + + TRACE("iface %p.\n", iface); + + EnterCriticalSection(&impl->cs); + if (impl->worker_running) + { + impl->worker_paused = FALSE; + impl->recognizer_state = SpeechRecognizerState_Capturing; + } + LeaveCriticalSection(&impl->cs); + + SetEvent(impl->worker_control_event); + + return S_OK; } static HRESULT WINAPI session_add_Completed( ISpeechContinuousRecognitionSession *iface, @@ -360,7 +1381,6 @@ struct recognizer LONG ref; ISpeechContinuousRecognitionSession *session; - IVector_ISpeechRecognitionConstraint *constraints; }; /* @@ -424,7 +1444,6 @@ static ULONG WINAPI recognizer_Release( ISpeechRecognizer *iface ) if (!ref) { ISpeechContinuousRecognitionSession_Release(impl->session); - IVector_ISpeechRecognitionConstraint_Release(impl->constraints); free(impl); } @@ -452,8 +1471,11 @@ static HRESULT WINAPI recognizer_GetTrustLevel( ISpeechRecognizer *iface, TrustL static HRESULT WINAPI recognizer_get_Constraints( ISpeechRecognizer *iface, IVector_ISpeechRecognitionConstraint **vector ) { struct recognizer *impl = impl_from_ISpeechRecognizer(iface); + struct session *session = impl_from_ISpeechContinuousRecognitionSession(impl->session); + TRACE("iface %p, operation %p.\n", iface, vector); - IVector_ISpeechRecognitionConstraint_AddRef((*vector = impl->constraints)); + + IVector_ISpeechRecognitionConstraint_AddRef((*vector = session->constraints)); return S_OK; } @@ -475,17 +1497,155 @@ static HRESULT WINAPI recognizer_get_UIOptions( ISpeechRecognizer *iface, ISpeec return E_NOTIMPL; } -static HRESULT WINAPI compile_callback( IInspectable *invoker, IInspectable **result ) +static HRESULT recognizer_create_unix_instance( struct session *session, const char **grammar, UINT32 grammar_size ) { - return compilation_result_create(SpeechRecognitionResultStatus_Success, (ISpeechRecognitionCompilationResult **) result); + struct speech_create_recognizer_params create_params = { 0 }; + WCHAR locale[LOCALE_NAME_MAX_LENGTH]; + NTSTATUS status; + INT len; + + if (!(len = GetUserDefaultLocaleName(locale, LOCALE_NAME_MAX_LENGTH))) + return E_FAIL; + + if (CharLowerBuffW(locale, len) != len) + return E_FAIL; + + if (!WideCharToMultiByte(CP_ACP, 0, locale, len, create_params.locale, ARRAY_SIZE(create_params.locale), NULL, NULL)) + return HRESULT_FROM_WIN32(GetLastError()); + + create_params.sample_rate = (FLOAT)session->capture_wfx.nSamplesPerSec; + create_params.grammar = grammar; + create_params.grammar_size = grammar_size; + + if ((status = WINE_UNIX_CALL(unix_speech_create_recognizer, &create_params))) + { + ERR("Unable to create Vosk instance for locale %s, status %#lx. Speech recognition won't work.\n", debugstr_a(create_params.locale), status); + return SPERR_WINRT_INTERNAL_ERROR; + } + + session->unix_handle = create_params.handle; + + return S_OK; +} + +static HRESULT recognizer_compile_constraints_async( IInspectable *invoker, IInspectable **result ) +{ + struct recognizer *impl = impl_from_ISpeechRecognizer((ISpeechRecognizer *)invoker); + struct session *session = impl_from_ISpeechContinuousRecognitionSession(impl->session); + struct speech_release_recognizer_params release_params; + ISpeechRecognitionListConstraint *list_constraint; + IIterable_IInspectable *constraints_iterable; + IIterator_IInspectable *constraints_iterator; + ISpeechRecognitionConstraint *constraint; + IIterable_HSTRING *commands_iterable; + IIterator_HSTRING *commands_iterator; + BOOL has_constraint, has_command; + IVector_HSTRING *commands; + const WCHAR *command_str; + UINT32 grammar_size = 0, i = 0; + char **grammar = NULL; + HSTRING command; + UINT32 size = 0; + HRESULT hr; + + if (FAILED(hr = IVector_ISpeechRecognitionConstraint_QueryInterface(session->constraints, &IID_IIterable_ISpeechRecognitionConstraint, (void **)&constraints_iterable))) + return hr; + + if (FAILED(hr = IIterable_IInspectable_First(constraints_iterable, &constraints_iterator))) + { + IIterable_IInspectable_Release(constraints_iterable); + return hr; + } + + for (hr = IIterator_IInspectable_get_HasCurrent(constraints_iterator, &has_constraint); SUCCEEDED(hr) && has_constraint; hr = IIterator_IInspectable_MoveNext(constraints_iterator, &has_constraint)) + { + list_constraint = NULL; + commands_iterable = NULL; + commands_iterator = NULL; + commands = NULL; + + if (FAILED(IIterator_IInspectable_get_Current(constraints_iterator, (IInspectable **)&constraint))) + goto skip; + + if (FAILED(ISpeechRecognitionConstraint_QueryInterface(constraint, &IID_ISpeechRecognitionListConstraint, (void**)&list_constraint))) + goto skip; + + if (FAILED(ISpeechRecognitionListConstraint_get_Commands(list_constraint, &commands))) + goto skip; + + if (FAILED(IVector_HSTRING_QueryInterface(commands, &IID_IIterable_HSTRING, (void **)&commands_iterable))) + goto skip; + + if (FAILED(IIterable_HSTRING_First(commands_iterable, &commands_iterator))) + goto skip; + + if (FAILED(IVector_HSTRING_get_Size(commands, &size))) + goto skip; + + grammar_size += size; + grammar = realloc(grammar, grammar_size * sizeof(char *)); + + for (hr = IIterator_HSTRING_get_HasCurrent(commands_iterator, &has_command); SUCCEEDED(hr) && has_command; hr = IIterator_HSTRING_MoveNext(commands_iterator, &has_command)) + { + if (FAILED(IIterator_HSTRING_get_Current(commands_iterator, &command))) + continue; + + command_str = WindowsGetStringRawBuffer(command, NULL); + + if (command_str) + { + WCHAR *wstr = wcsdup(command_str); + size_t len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, grammar[i], 0, NULL, NULL); + grammar[i] = malloc(len * sizeof(char)); + + CharLowerW(wstr); + WideCharToMultiByte(CP_UTF8, 0, wstr, -1, grammar[i], len, NULL, NULL); + free(wstr); + i++; + } + + WindowsDeleteString(command); + } + +skip: + if (commands_iterator) IIterator_HSTRING_Release(commands_iterator); + if (commands_iterable) IIterable_HSTRING_Release(commands_iterable); + if (commands) IVector_HSTRING_Release(commands); + + if (list_constraint) ISpeechRecognitionListConstraint_Release(list_constraint); + if (constraint) ISpeechRecognitionConstraint_Release(constraint); + } + + IIterator_IInspectable_Release(constraints_iterator); + IIterable_IInspectable_Release(constraints_iterable); + + if (session->unix_handle) + { + release_params.handle = session->unix_handle; + WINE_UNIX_CALL(unix_speech_release_recognizer, &release_params); + session->unix_handle = 0; + } + + hr = recognizer_create_unix_instance(session, (const char **)grammar, grammar_size); + + for(i = 0; i < grammar_size; ++i) + free(grammar[i]); + free(grammar); + + if (FAILED(hr)) + { + WARN("Failed to created recognizer instance with grammar.\n"); + return compilation_result_create(SpeechRecognitionResultStatus_GrammarCompilationFailure, (ISpeechRecognitionCompilationResult **) result); + } + else return compilation_result_create(SpeechRecognitionResultStatus_Success, (ISpeechRecognitionCompilationResult **) result); } static HRESULT WINAPI recognizer_CompileConstraintsAsync( ISpeechRecognizer *iface, IAsyncOperation_SpeechRecognitionCompilationResult **operation ) { IAsyncOperation_IInspectable **value = (IAsyncOperation_IInspectable **)operation; - FIXME("iface %p, operation %p semi-stub!\n", iface, operation); - return async_operation_inspectable_create(&IID_IAsyncOperation_SpeechRecognitionCompilationResult, NULL, compile_callback, value); + TRACE("iface %p, operation %p semi-stub!\n", iface, operation); + return async_operation_inspectable_create(&IID_IAsyncOperation_SpeechRecognitionCompilationResult, (IInspectable *)iface, recognizer_compile_constraints_async, value); } static HRESULT WINAPI recognizer_RecognizeAsync( ISpeechRecognizer *iface, @@ -601,8 +1761,19 @@ static HRESULT WINAPI recognizer2_get_ContinuousRecognitionSession( ISpeechRecog static HRESULT WINAPI recognizer2_get_State( ISpeechRecognizer2 *iface, SpeechRecognizerState *state ) { - FIXME("iface %p, state %p stub!\n", iface, state); - return E_NOTIMPL; + struct recognizer *impl = impl_from_ISpeechRecognizer2(iface); + struct session *session = impl_from_ISpeechContinuousRecognitionSession(impl->session); + + FIXME("iface %p, state %p not all states are supported, yet.\n", iface, state); + + if (!state) + return E_POINTER; + + EnterCriticalSection(&session->cs); + *state = session->recognizer_state; + LeaveCriticalSection(&session->cs); + + return S_OK; } static HRESULT WINAPI recognizer2_StopRecognitionAsync( ISpeechRecognizer2 *iface, IAsyncAction **action ) @@ -770,6 +1941,55 @@ static const struct IActivationFactoryVtbl activation_factory_vtbl = DEFINE_IINSPECTABLE(recognizer_factory, ISpeechRecognizerFactory, struct recognizer_statics, IActivationFactory_iface) +static HRESULT recognizer_factory_create_audio_capture(struct session *session) +{ + const REFERENCE_TIME buffer_duration = 5000000; /* 0.5 second */ + IMMDeviceEnumerator *mm_enum = NULL; + IMMDevice *mm_device = NULL; + WAVEFORMATEX wfx = { 0 }; + WCHAR *str = NULL; + HRESULT hr = S_OK; + + if (!(session->audio_buf_event = CreateEventW(NULL, FALSE, FALSE, NULL))) + return HRESULT_FROM_WIN32(GetLastError()); + + if (FAILED(hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void **)&mm_enum))) + goto cleanup; + + if (FAILED(hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(mm_enum, eCapture, eMultimedia, &mm_device))) + goto cleanup; + + if (FAILED(hr = IMMDevice_Activate(mm_device, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, (void **)&session->audio_client))) + goto cleanup; + + hr = IMMDevice_GetId(mm_device, &str); + TRACE("selected capture device ID: %s, hr %#lx\n", debugstr_w(str), hr); + + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nSamplesPerSec = 16000; + wfx.nChannels = 1; + wfx.wBitsPerSample = 16; + wfx.nBlockAlign = (wfx.wBitsPerSample + 7) / 8 * wfx.nChannels; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + TRACE("wfx tag %u, channels %u, samples %lu, bits %u, align %u.\n", wfx.wFormatTag, wfx.nChannels, wfx.nSamplesPerSec, wfx.wBitsPerSample, wfx.nBlockAlign); + + if (FAILED(hr = IAudioClient_Initialize(session->audio_client, AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buffer_duration, 0, &wfx, NULL))) + goto cleanup; + + if (FAILED(hr = IAudioClient_SetEventHandle(session->audio_client, session->audio_buf_event))) + goto cleanup; + + hr = IAudioClient_GetService(session->audio_client, &IID_IAudioCaptureClient, (void **)&session->capture_client); + + session->capture_wfx = wfx; + +cleanup: + if (mm_device) IMMDevice_Release(mm_device); + if (mm_enum) IMMDeviceEnumerator_Release(mm_enum); + CoTaskMemFree(str); + return hr; +} + static HRESULT WINAPI recognizer_factory_Create( ISpeechRecognizerFactory *iface, ILanguage *language, ISpeechRecognizer **speechrecognizer ) { struct recognizer *impl; @@ -797,25 +2017,45 @@ static HRESULT WINAPI recognizer_factory_Create( ISpeechRecognizerFactory *iface if (language) FIXME("language parameter unused. Stub!\n"); + /* Init ISpeechContinuousRecognitionSession */ session->ISpeechContinuousRecognitionSession_iface.lpVtbl = &session_vtbl; session->ref = 1; + list_init(&session->completed_handlers); list_init(&session->result_handlers); + if (!(session->worker_control_event = CreateEventW(NULL, FALSE, FALSE, NULL))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto error; + } + + if (FAILED(hr = vector_inspectable_create(&constraints_iids, (IVector_IInspectable**)&session->constraints))) + goto error; + + if (FAILED(hr = recognizer_factory_create_audio_capture(session))) + goto error; + + InitializeCriticalSection(&session->cs); + session->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": recognition_session.cs"); + + /* Init ISpeechRecognizer */ impl->ISpeechRecognizer_iface.lpVtbl = &speech_recognizer_vtbl; impl->IClosable_iface.lpVtbl = &closable_vtbl; impl->ISpeechRecognizer2_iface.lpVtbl = &speech_recognizer2_vtbl; impl->session = &session->ISpeechContinuousRecognitionSession_iface; impl->ref = 1; - if (FAILED(hr = vector_inspectable_create(&constraints_iids, (IVector_IInspectable**)&impl->constraints))) - goto error; - - TRACE("created SpeechRecognizer %p.\n", impl); *speechrecognizer = &impl->ISpeechRecognizer_iface; + TRACE("created SpeechRecognizer %p.\n", *speechrecognizer); return S_OK; error: + if (session->capture_client) IAudioCaptureClient_Release(session->capture_client); + if (session->audio_client) IAudioClient_Release(session->audio_client); + if (session->audio_buf_event) CloseHandle(session->audio_buf_event); + if (session->constraints) IVector_ISpeechRecognitionConstraint_Release(session->constraints); + if (session->worker_control_event) CloseHandle(session->worker_control_event); free(session); free(impl); diff --git a/dlls/windows.media.speech/synthesizer.c b/dlls/windows.media.speech/synthesizer.c index 5e5d20b8364..bdf7325f669 100644 --- a/dlls/windows.media.speech/synthesizer.c +++ b/dlls/windows.media.speech/synthesizer.c @@ -929,7 +929,7 @@ static HRESULT WINAPI synthesizer_GetTrustLevel( ISpeechSynthesizer *iface, Trus return E_NOTIMPL; } -static HRESULT CALLBACK text_to_stream_operation( IInspectable *invoker, IInspectable **result ) +static HRESULT synthesizer_synthesize_text_to_stream_async( IInspectable *invoker, IInspectable **result ) { return synthesis_stream_create((ISpeechSynthesisStream **)result); } @@ -939,10 +939,10 @@ static HRESULT WINAPI synthesizer_SynthesizeTextToStreamAsync( ISpeechSynthesize { TRACE("iface %p, text %p, operation %p.\n", iface, text, operation); return async_operation_inspectable_create(&IID_IAsyncOperation_SpeechSynthesisStream, NULL, - text_to_stream_operation, (IAsyncOperation_IInspectable **)operation); + synthesizer_synthesize_text_to_stream_async, (IAsyncOperation_IInspectable **)operation); } -static HRESULT CALLBACK ssml_to_stream_operation( IInspectable *invoker, IInspectable **result ) +static HRESULT synthesizer_synthesize_ssml_to_stream_async( IInspectable *invoker, IInspectable **result ) { return synthesis_stream_create((ISpeechSynthesisStream **)result); } @@ -952,7 +952,7 @@ static HRESULT WINAPI synthesizer_SynthesizeSsmlToStreamAsync( ISpeechSynthesize { TRACE("iface %p, ssml %p, operation %p.\n", iface, ssml, operation); return async_operation_inspectable_create(&IID_IAsyncOperation_SpeechSynthesisStream, NULL, - ssml_to_stream_operation, (IAsyncOperation_IInspectable **)operation); + synthesizer_synthesize_ssml_to_stream_async, (IAsyncOperation_IInspectable **)operation); } static HRESULT WINAPI synthesizer_put_Voice( ISpeechSynthesizer *iface, IVoiceInformation *value ) diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index f234dc9d643..1b9198ac10a 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -18,6 +18,7 @@ #define COBJMACROS #include +#include "corerror.h" #include "windef.h" #include "winbase.h" #include "winerror.h" @@ -41,7 +42,6 @@ #define AsyncStatus_Closed 4 #define SPERR_WINRT_INTERNAL_ERROR 0x800455a0 -#define SPERR_WINRT_INCORRECT_FORMAT 0x80131537 #define IHandler_RecognitionResult ITypedEventHandler_SpeechContinuousRecognitionSession_SpeechContinuousRecognitionResultGeneratedEventArgs #define IHandler_RecognitionResultVtbl ITypedEventHandler_SpeechContinuousRecognitionSession_SpeechContinuousRecognitionResultGeneratedEventArgsVtbl @@ -211,7 +211,23 @@ HRESULT WINAPI recognition_result_handler_Invoke( IHandler_RecognitionResult *if ISpeechContinuousRecognitionSession *sender, ISpeechContinuousRecognitionResultGeneratedEventArgs *args ) { - trace("iface %p, sender %p, args %p.\n", iface, sender, args); + ISpeechRecognitionResult *result; + HSTRING hstring; + HRESULT hr; + + if (!args) return S_OK; + + hr = ISpeechContinuousRecognitionResultGeneratedEventArgs_get_Result(args, &result); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + hr = ISpeechRecognitionResult_get_Text(result, &hstring); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + trace("iface %p, sender %p, args %p, text %s.\n", iface, sender, args, debugstr_w(WindowsGetStringRawBuffer(hstring, NULL))); + + WindowsDeleteString(hstring); + ISpeechRecognitionResult_Release(result); + return S_OK; } @@ -1090,7 +1106,7 @@ static void test_SpeechSynthesizer(void) operation_ss_stream = (void *)0xdeadbeef; hr = ISpeechSynthesizer_SynthesizeSsmlToStreamAsync(synthesizer, str, &operation_ss_stream); /* Broken on Win 8 + 8.1 */ - ok(hr == S_OK || broken(hr == SPERR_WINRT_INCORRECT_FORMAT), "ISpeechSynthesizer_SynthesizeSsmlToStreamAsync failed, hr %#lx\n", hr); + ok(hr == S_OK || broken(hr == COR_E_FORMAT), "ISpeechSynthesizer_SynthesizeSsmlToStreamAsync failed, hr %#lx\n", hr); if (hr == S_OK) { @@ -1316,7 +1332,7 @@ static void test_SpeechRecognizer(void) ok(ref == 1, "Got unexpected ref %lu.\n", ref); hr = RoActivateInstance(hstr, &inspectable); - ok(hr == S_OK || broken(hr == SPERR_WINRT_INTERNAL_ERROR), "Got unexpected hr %#lx.\n", hr); + ok(hr == S_OK || hr == SPERR_WINRT_INTERNAL_ERROR, "Got unexpected hr %#lx.\n", hr); if (hr == S_OK) { @@ -1535,7 +1551,7 @@ static void test_SpeechRecognizer(void) } else if (hr == SPERR_WINRT_INTERNAL_ERROR) /* Not sure when this triggers. Probably if a language pack is not installed. */ { - win_skip("Could not init SpeechRecognizer with default language!\n"); + skip("Could not init SpeechRecognizer with default language!\n"); } done: @@ -1723,7 +1739,7 @@ static void test_Recognition(void) static const WCHAR *list_constraint_name = L"Windows.Media.SpeechRecognition.SpeechRecognitionListConstraint"; static const WCHAR *recognizer_name = L"Windows.Media.SpeechRecognition.SpeechRecognizer"; static const WCHAR *speech_constraint_tag = L"test_message"; - static const WCHAR *speech_constraints[] = { L"This is a test.", L"Number 5!", L"What time is it?" }; + static const WCHAR *speech_constraints[] = { L"This is a test", L"Number 5", L"What time is it" }; ISpeechRecognitionListConstraintFactory *listconstraint_factory = NULL; IAsyncOperation_SpeechRecognitionCompilationResult *operation = NULL; IVector_ISpeechRecognitionConstraint *constraints = NULL; @@ -1743,6 +1759,7 @@ static void test_Recognition(void) struct iterator_hstring iterator_hstring; struct iterable_hstring iterable_hstring; EventRegistrationToken token = { .value = 0 }; + SpeechRecognizerState recog_state; HSTRING commands[3], hstr, tag; HANDLE put_thread; LONG ref, old_ref; @@ -1774,12 +1791,12 @@ static void test_Recognition(void) ok(hr == S_OK, "WindowsCreateString failed, hr %#lx.\n", hr); hr = RoActivateInstance(hstr, &inspectable); - ok(hr == S_OK || broken(hr == SPERR_WINRT_INTERNAL_ERROR || hr == REGDB_E_CLASSNOTREG), "Got unexpected hr %#lx.\n", hr); + ok(hr == S_OK || hr == SPERR_WINRT_INTERNAL_ERROR || broken(hr == REGDB_E_CLASSNOTREG), "Got unexpected hr %#lx.\n", hr); WindowsDeleteString(hstr); - if (FAILED(hr)) /* Win 8 and 8.1 and Win10 without enabled SR. */ + if (FAILED(hr)) /* Win 8 and 8.1 and Win10 without enabled SR. Wine with missing Unix side dependencies. */ { - win_skip("SpeechRecognizer cannot be activated!\n"); + skip("SpeechRecognizer cannot be activated!\n"); goto done; } @@ -1841,6 +1858,11 @@ static void test_Recognition(void) ok(hr == S_OK, "ISpeechContinuousRecognitionSession_add_ResultGenerated failed, hr %#lx.\n", hr); ok(token.value != 0xdeadbeef, "Got unexpexted token: %#I64x.\n", token.value); + recog_state = 0xdeadbeef; + hr = ISpeechRecognizer2_get_State(recognizer2, &recog_state); + ok(hr == S_OK, "ISpeechRecognizer2_get_State failed, hr %#lx.\n", hr); + ok(recog_state == SpeechRecognizerState_Idle, "recog_state was %u.\n", recog_state); + hr = ISpeechRecognizer_CompileConstraintsAsync(recognizer, &operation); ok(hr == S_OK, "ISpeechRecognizer_CompileConstraintsAsync failed, hr %#lx.\n", hr); await_async_inspectable((IAsyncOperation_IInspectable *)operation, @@ -1859,6 +1881,11 @@ static void test_Recognition(void) await_async_void(action, &action_handler); + action2 = (void *)0xdeadbeef; + hr = ISpeechContinuousRecognitionSession_StartAsync(session, &action2); + ok(hr == COR_E_INVALIDOPERATION, "ISpeechContinuousRecognitionSession_StartAsync failed, hr %#lx.\n", hr); + ok(action2 == NULL, "action2 was %p.\n", action2); + hr = IAsyncAction_QueryInterface(action, &IID_IAsyncInfo, (void **)&info); ok(hr == S_OK, "IAsyncAction_QueryInterface failed, hr %#lx.\n", hr); check_async_info((IInspectable *)action, 1, Completed, S_OK); @@ -1881,14 +1908,50 @@ static void test_Recognition(void) IAsyncInfo_Release(info); + recog_state = 0xdeadbeef; + hr = ISpeechRecognizer2_get_State(recognizer2, &recog_state); + ok(hr == S_OK, "ISpeechRecognizer2_get_State failed, hr %#lx.\n", hr); + ok(recog_state == SpeechRecognizerState_Capturing, "recog_state was %u.\n", recog_state); + + + Sleep(10000); /* * TODO: Use a loopback device together with prerecorded audio files to test the recognizer's functionality. */ - hr = ISpeechContinuousRecognitionSession_StopAsync(session, &action2); - todo_wine ok(hr == S_OK, "ISpeechContinuousRecognitionSession_StopAsync failed, hr %#lx.\n", hr); + hr = ISpeechContinuousRecognitionSession_PauseAsync(session, &action2); + ok(hr == S_OK, "ISpeechContinuousRecognitionSession_PauseAsync failed, hr %#lx.\n", hr); + await_async_void(action2, &action_handler); + check_async_info((IInspectable *)action2, 3, Completed, S_OK); + IAsyncAction_Release(action2); + + recog_state = 0xdeadbeef; + hr = ISpeechRecognizer2_get_State(recognizer2, &recog_state); + ok(hr == S_OK, "ISpeechRecognizer2_get_State failed, hr %#lx.\n", hr); + ok(recog_state == SpeechRecognizerState_Paused || + broken(recog_state == SpeechRecognizerState_Capturing) /* Broken on Win10 1507 */, "recog_state was %u.\n", recog_state); + + /* Check what happens if we try to pause again, when the session is already paused. */ + hr = ISpeechContinuousRecognitionSession_PauseAsync(session, &action2); + ok(hr == S_OK, "ISpeechContinuousRecognitionSession_PauseAsync failed, hr %#lx.\n", hr); + await_async_void(action2, &action_handler); + check_async_info((IInspectable *)action2, 4, Completed, S_OK); + IAsyncAction_Release(action2); + + hr = ISpeechContinuousRecognitionSession_Resume(session); + ok(hr == S_OK, "ISpeechContinuousRecognitionSession_Resume failed, hr %#lx.\n", hr); + + /* Resume when already resumed. */ + hr = ISpeechContinuousRecognitionSession_Resume(session); + ok(hr == S_OK, "ISpeechContinuousRecognitionSession_Resume failed, hr %#lx.\n", hr); - if (FAILED(hr)) goto skip_action; + recog_state = 0xdeadbeef; + hr = ISpeechRecognizer2_get_State(recognizer2, &recog_state); + ok(hr == S_OK, "ISpeechRecognizer2_get_State failed, hr %#lx.\n", hr); + ok(recog_state == SpeechRecognizerState_Capturing, "recog_state was %u.\n", recog_state); + + hr = ISpeechContinuousRecognitionSession_StopAsync(session, &action2); + ok(hr == S_OK, "ISpeechContinuousRecognitionSession_StopAsync failed, hr %#lx.\n", hr); async_void_handler_create_static(&action_handler); action_handler.event_block = CreateEventW(NULL, FALSE, FALSE, NULL); @@ -1900,40 +1963,92 @@ static void test_Recognition(void) put_param.handler = &action_handler.IAsyncActionCompletedHandler_iface; put_param.action = action2; put_thread = CreateThread(NULL, 0, action_put_completed_thread, &put_param, 0, NULL); - todo_wine ok(!WaitForSingleObject(action_handler.event_finished , 5000), "Wait for event_finished failed.\n"); + ok(!WaitForSingleObject(action_handler.event_finished , 5000), "Wait for event_finished failed.\n"); handler = (void *)0xdeadbeef; old_ref = action_handler.ref; hr = IAsyncAction_get_Completed(action2, &handler); - todo_wine ok(hr == S_OK, "IAsyncAction_get_Completed failed, hr %#lx.\n", hr); + ok(hr == S_OK, "IAsyncAction_get_Completed failed, hr %#lx.\n", hr); - todo_wine ok(handler == &action_handler.IAsyncActionCompletedHandler_iface || /* Broken on 1507. */ - broken(handler != NULL && handler != (void *)0xdeadbeef), "Handler was %p.\n", handler); + todo_wine ok(handler == &action_handler.IAsyncActionCompletedHandler_iface, "Handler was %p.\n", handler); ref = action_handler.ref - old_ref; todo_wine ok(ref == 1, "The ref was increased by %lu.\n", ref); - IAsyncActionCompletedHandler_Release(handler); + if (handler) IAsyncActionCompletedHandler_Release(handler); hr = IAsyncAction_QueryInterface(action2, &IID_IAsyncInfo, (void **)&info); - todo_wine ok(hr == S_OK, "IAsyncAction_QueryInterface failed, hr %#lx.\n", hr); + ok(hr == S_OK, "IAsyncAction_QueryInterface failed, hr %#lx.\n", hr); hr = IAsyncInfo_Close(info); /* If IAsyncInfo_Close would wait for the handler to finish, the test would get stuck here. */ - todo_wine ok(hr == S_OK, "IAsyncInfo_Close failed, hr %#lx.\n", hr); - check_async_info((IInspectable *)action2, 3, AsyncStatus_Closed, S_OK); + ok(hr == S_OK, "IAsyncInfo_Close failed, hr %#lx.\n", hr); + check_async_info((IInspectable *)action2, 5, AsyncStatus_Closed, S_OK); set = SetEvent(action_handler.event_block); - todo_wine ok(set == TRUE, "Event 'event_block' wasn't set.\n"); - todo_wine ok(!WaitForSingleObject(put_thread , 1000), "Wait for put_thread failed.\n"); + ok(set == TRUE, "Event 'event_block' wasn't set.\n"); + ok(!WaitForSingleObject(put_thread, 1000), "Wait for put_thread failed.\n"); IAsyncInfo_Release(info); CloseHandle(action_handler.event_finished); CloseHandle(action_handler.event_block); CloseHandle(put_thread); - todo_wine ok(action != action2, "actions were the same!\n"); + ok(action != action2, "actions were the same!\n"); IAsyncAction_Release(action2); -skip_action: + IAsyncAction_Release(action); + + recog_state = 0xdeadbeef; + hr = ISpeechRecognizer2_get_State(recognizer2, &recog_state); + ok(hr == S_OK, "ISpeechRecognizer2_get_State failed, hr %#lx.\n", hr); + ok(recog_state == SpeechRecognizerState_Idle, "recog_state was %u.\n", recog_state); + + /* Try stopping, when already stopped. */ + hr = ISpeechContinuousRecognitionSession_StopAsync(session, &action); + ok(hr == COR_E_INVALIDOPERATION, "ISpeechContinuousRecognitionSession_StopAsync failed, hr %#lx.\n", hr); + ok(action == NULL, "action was %p.\n", action); + + /* Test, if Start/StopAsync resets the pause state. */ + hr = ISpeechContinuousRecognitionSession_StartAsync(session, &action); + ok(hr == S_OK, "ISpeechContinuousRecognitionSession_StartAsync failed, hr %#lx.\n", hr); + await_async_void(action, &action_handler); + IAsyncAction_Release(action); + + hr = ISpeechContinuousRecognitionSession_PauseAsync(session, &action); + ok(hr == S_OK, "ISpeechContinuousRecognitionSession_PauseAsync failed, hr %#lx.\n", hr); + await_async_void(action, &action_handler); + IAsyncAction_Release(action); + + recog_state = 0xdeadbeef; + hr = ISpeechRecognizer2_get_State(recognizer2, &recog_state); + ok(hr == S_OK, "ISpeechRecognizer2_get_State failed, hr %#lx.\n", hr); + ok(recog_state == SpeechRecognizerState_Paused || + broken(recog_state == SpeechRecognizerState_Capturing) /* Broken on Win10 1507 */, "recog_state was %u.\n", recog_state); + + hr = ISpeechContinuousRecognitionSession_StopAsync(session, &action); + ok(hr == S_OK, "ISpeechContinuousRecognitionSession_PauseAsync failed, hr %#lx.\n", hr); + await_async_void(action, &action_handler); + IAsyncAction_Release(action); + + recog_state = 0xdeadbeef; + hr = ISpeechRecognizer2_get_State(recognizer2, &recog_state); + ok(hr == S_OK, "ISpeechRecognizer2_get_State failed, hr %#lx.\n", hr); + ok(recog_state == SpeechRecognizerState_Idle, "recog_state was %u.\n", recog_state); + + hr = ISpeechContinuousRecognitionSession_StartAsync(session, &action); + ok(hr == S_OK, "ISpeechContinuousRecognitionSession_PauseAsync failed, hr %#lx.\n", hr); + await_async_void(action, &action_handler); + IAsyncAction_Release(action); + + recog_state = 0xdeadbeef; + hr = ISpeechRecognizer2_get_State(recognizer2, &recog_state); + ok(hr == S_OK, "ISpeechRecognizer2_get_State failed, hr %#lx.\n", hr); + ok(recog_state == SpeechRecognizerState_Capturing + || broken(recog_state == SpeechRecognizerState_Idle) /* Sometimes Windows is a little behind. */, + "recog_state was %u.\n", recog_state); + + hr = ISpeechContinuousRecognitionSession_StopAsync(session, &action); + ok(hr == S_OK, "ISpeechContinuousRecognitionSession_PauseAsync failed, hr %#lx.\n", hr); + await_async_void(action, &action_handler); IAsyncAction_Release(action); hr = ISpeechContinuousRecognitionSession_remove_ResultGenerated(session, token); diff --git a/dlls/windows.media.speech/unixlib.c b/dlls/windows.media.speech/unixlib.c new file mode 100644 index 00000000000..e98e2e69fb3 --- /dev/null +++ b/dlls/windows.media.speech/unixlib.c @@ -0,0 +1,482 @@ +/* + * Unixlib for Windows.Media.Speech + * + * Copyright 2023 Bernhard Kölbl for CodeWeavers + * + * 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 + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SONAME_LIBVOSK +#include +#endif + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "winerror.h" +#include "winternl.h" + +#include "wine/debug.h" + +#include "unixlib.h" + +WINE_DEFAULT_DEBUG_CHANNEL(speech); +#ifdef SONAME_LIBVOSK +WINE_DECLARE_DEBUG_CHANNEL(winediag); + +static void *vosk_handle; +#define MAKE_FUNCPTR( f ) static typeof(f) * p_##f +MAKE_FUNCPTR(vosk_model_new); +MAKE_FUNCPTR(vosk_model_free); +MAKE_FUNCPTR(vosk_recognizer_new); +MAKE_FUNCPTR(vosk_recognizer_new_grm); +MAKE_FUNCPTR(vosk_recognizer_free); +MAKE_FUNCPTR(vosk_recognizer_accept_waveform); +MAKE_FUNCPTR(vosk_recognizer_final_result); +MAKE_FUNCPTR(vosk_recognizer_reset); +#undef MAKE_FUNCPTR + +static NTSTATUS process_attach( void *args ) +{ + if (!(vosk_handle = dlopen(SONAME_LIBVOSK, RTLD_NOW))) + { + ERR_(winediag)("Wine is unable to load the Unix side dependencies for speech recognition. " + "Make sure Vosk is installed and up to date on your system and try again.\n"); + return STATUS_DLL_NOT_FOUND; + } + +#define LOAD_FUNCPTR( f ) \ + if (!(p_##f = dlsym(vosk_handle, #f))) \ + { \ + ERR("failed to load %s\n", #f); \ + goto error; \ + } + LOAD_FUNCPTR(vosk_model_new) + LOAD_FUNCPTR(vosk_recognizer_new) + LOAD_FUNCPTR(vosk_recognizer_new_grm) + LOAD_FUNCPTR(vosk_model_free) + LOAD_FUNCPTR(vosk_recognizer_new) + LOAD_FUNCPTR(vosk_recognizer_free) + LOAD_FUNCPTR(vosk_recognizer_accept_waveform) + LOAD_FUNCPTR(vosk_recognizer_final_result) + LOAD_FUNCPTR(vosk_recognizer_reset) +#undef LOAD_FUNCPTR + + return STATUS_SUCCESS; + +error: + dlclose(vosk_handle); + vosk_handle = NULL; + return STATUS_DLL_NOT_FOUND; +} + +static NTSTATUS process_detach( void *args ) +{ + if (vosk_handle) + { + dlclose(vosk_handle); + vosk_handle = NULL; + } + return STATUS_SUCCESS; +} + +static inline speech_recognizer_handle vosk_recognizer_to_handle( VoskRecognizer *recognizer ) +{ + return (speech_recognizer_handle)(UINT_PTR)recognizer; +} + +static inline VoskRecognizer *vosk_recognizer_from_handle( speech_recognizer_handle handle ) +{ + return (VoskRecognizer *)(UINT_PTR)handle; +} + +static const char* map_lang_to_phasmophobia_dir(const char* lang, size_t len) +{ + if (!strncmp(lang, "ar", len)) + return "Arabic"; + if (!strncmp(lang, "ca", len)) + return "Catalan"; + if (!strncmp(lang, "zn", len)) + return "Chinese"; + if (!strncmp(lang, "cs", len)) + return "Czech"; + if (!strncmp(lang, "nl", len)) + return "Dutch"; + if (!strncmp(lang, "en", len)) + return "English"; + if (!strncmp(lang, "fr", len)) + return "French"; + if (!strncmp(lang, "de", len)) + return "German"; + if (!strncmp(lang, "de", len)) + return "German"; + if (!strncmp(lang, "el", len)) + return "Greek"; + if (!strncmp(lang, "it", len)) + return "Italian"; + if (!strncmp(lang, "ja", len)) + return "Japanese"; + if (!strncmp(lang, "pt", len)) + return "Portuguese"; + if (!strncmp(lang, "ru", len)) + return "Russian"; + if (!strncmp(lang, "es", len)) + return "Spanish"; + if (!strncmp(lang, "sw", len)) + return "Swedish"; + if (!strncmp(lang, "tr", len)) + return "Turkish"; + if (!strncmp(lang, "uk", len)) + return "Ukrainian"; + + return ""; +} + +static NTSTATUS find_model_by_locale_and_path( const char *path, const char *locale, VoskModel **model ) +{ + static const char *vosk_model_identifier_small = "vosk-model-small-"; + static const char *vosk_model_identifier = "vosk-model-"; + size_t ident_small_len = strlen(vosk_model_identifier_small); + size_t ident_len = strlen(vosk_model_identifier); + char *ent_name, *model_path, *best_match, *delim, *appid = getenv("SteamAppId"), *str = NULL; + NTSTATUS status = STATUS_UNSUCCESSFUL; + struct dirent *dirent; + size_t path_len, len; + DIR *dir; + + TRACE("path %s, locale %s, model %p.\n", path, debugstr_a(locale), model); + + if (!path || !locale || (len = strlen(locale)) < 4) + return STATUS_UNSUCCESSFUL; + + if (!(dir = opendir(path))) + return STATUS_UNSUCCESSFUL; + + delim = strchr(locale, '-'); + path_len = strlen(path); + best_match = NULL; + *model = NULL; + + while ((dirent = readdir(dir))) + { + ent_name = dirent->d_name; + + if (!strncmp(ent_name, vosk_model_identifier_small, ident_small_len)) + ent_name += ident_small_len; + else if (!strncmp(ent_name, vosk_model_identifier, ident_len)) + ent_name += ident_len; + else if (strcmp(appid, "739630") != 0) + continue; + + /* + * Find the first matching model for lang and region (en-us). + * If there isn't any, pick the first one just matching lang (en). + */ + if (!strncmp(ent_name, locale, len)) + { + if (best_match) free(best_match); + best_match = strdup(dirent->d_name); + break; + } + + if (!best_match && !strncmp(ent_name, locale, delim - locale)) + best_match = strdup(dirent->d_name); + + if (!best_match && !strcmp(appid, "739630")) + { + if ((str = (char *)map_lang_to_phasmophobia_dir(locale, delim - locale))) + best_match = strdup(str); + } + } + + closedir(dir); + + if (!best_match) + return STATUS_UNSUCCESSFUL; + + if (!(model_path = malloc(path_len + 1 /* '/' */ + strlen(best_match) + 1))) + { + status = STATUS_NO_MEMORY; + goto done; + } + + sprintf(model_path, "%s/%s", path, best_match); + + TRACE("trying to load Vosk model %s.\n", debugstr_a(model_path)); + + if ((*model = p_vosk_model_new(model_path)) != NULL) + status = STATUS_SUCCESS; + +done: + free(model_path); + free(best_match); + + return status; +} + +static NTSTATUS find_model_by_locale( const char *locale, VoskModel **model ) +{ + const char *suffix = NULL; + char *env, *path = NULL, *appid = getenv("SteamAppId"); + NTSTATUS status; + + TRACE("locale %s, model %p.\n", debugstr_a(locale), model); + + if (!model) + return STATUS_UNSUCCESSFUL; + + if (!find_model_by_locale_and_path(getenv("VOSK_MODEL_PATH"), locale, model)) + return STATUS_SUCCESS; + if (!find_model_by_locale_and_path("/usr/share/vosk", locale, model)) + return STATUS_SUCCESS; + + if ((env = getenv("XDG_CACHE_HOME"))) + suffix = "/vosk"; + else if ((env = getenv("HOME"))) + suffix = "/.cache/vosk"; + else + return STATUS_UNSUCCESSFUL; + + if (!(path = malloc(strlen(env) + strlen(suffix) + 1))) + return STATUS_NO_MEMORY; + + sprintf(path, "%s%s", env, suffix); + status = find_model_by_locale_and_path(path, locale, model); + free(path); + + /* Hack to load Vosk models from Phasmophobia, so they don't need to be downloaded separately.*/ + if (status && appid && !strcmp(appid, "739630") && (env = getenv("PWD"))) + { + suffix = "/Phasmophobia_Data/StreamingAssets/LanguageModels"; + + if (!(path = malloc(strlen(env) + strlen(suffix) + 1))) + return STATUS_NO_MEMORY; + + sprintf(path, "%s%s", env, suffix); + status = find_model_by_locale_and_path(path, locale, model); + free(path); + } + + return status; +} + +static NTSTATUS grammar_to_json_array(const char **grammar, UINT32 grammar_size, const char **array) +{ + size_t buf_size = strlen("[]") + 1, len; + char *buf; + UINT32 i; + + for (i = 0; i < grammar_size; ++i) + { + buf_size += strlen(grammar[i]) + 4; /* (4) - two double quotes, a comma and a space */ + } + + if (!(buf = malloc(buf_size))) + return STATUS_NO_MEMORY; + + *array = buf; + + *buf = '['; + buf++; + + for (i = 0; i < grammar_size; ++i) + { + *buf = '\"'; + buf++; + len = strlen(grammar[i]); + memcpy(buf, grammar[i], len); + buf += len; + *buf = '\"'; + buf++; + if (i < (grammar_size - 1)) + { + *buf = ','; + buf++; + *buf = ' '; + buf++; + } + } + + *buf = ']'; + buf++; + *buf = '\0'; + + return STATUS_SUCCESS; +} + +static NTSTATUS speech_create_recognizer( void *args ) +{ + struct speech_create_recognizer_params *params = args; + VoskRecognizer *recognizer = NULL; + VoskModel *model = NULL; + NTSTATUS status = STATUS_SUCCESS; + const char *grammar_json; + + TRACE("args %p.\n", args); + + if (!vosk_handle) + return STATUS_NOT_SUPPORTED; + + if ((status = find_model_by_locale(params->locale, &model))) + return status; + + if (params->grammar && grammar_to_json_array(params->grammar, params->grammar_size, &grammar_json) == STATUS_SUCCESS) + { + if (!(recognizer = p_vosk_recognizer_new_grm(model, params->sample_rate, grammar_json))) + status = STATUS_UNSUCCESSFUL; + } + else + { + if (!(recognizer = p_vosk_recognizer_new(model, params->sample_rate))) + status = STATUS_UNSUCCESSFUL; + } + + /* VoskModel is reference-counted. A VoskRecognizer keeps a reference to its model. */ + p_vosk_model_free(model); + + params->handle = vosk_recognizer_to_handle(recognizer); + return status; +} + +static NTSTATUS speech_release_recognizer( void *args ) +{ + struct speech_release_recognizer_params *params = args; + + TRACE("args %p.\n", args); + + if (!vosk_handle) + return STATUS_NOT_SUPPORTED; + + p_vosk_recognizer_free(vosk_recognizer_from_handle(params->handle)); + + return STATUS_SUCCESS; +} + +static NTSTATUS speech_recognize_audio( void *args ) +{ + struct speech_recognize_audio_params *params = args; + VoskRecognizer *recognizer = vosk_recognizer_from_handle(params->handle); + + if (!vosk_handle) + return STATUS_NOT_SUPPORTED; + + if (!recognizer) + return STATUS_UNSUCCESSFUL; + + params->status = p_vosk_recognizer_accept_waveform(recognizer, (const char *)params->samples, params->samples_size); + + return STATUS_SUCCESS; +} + +static NTSTATUS speech_get_recognition_result( void* args ) +{ + struct speech_get_recognition_result_params *params = args; + VoskRecognizer *recognizer = vosk_recognizer_from_handle(params->handle); + static const char *result_json_start = "{\n \"text\" : \""; + const size_t json_start_len = strlen(result_json_start); + static size_t last_result_len = 0; + static char *last_result = NULL; + const char *tmp = NULL; + + if (!vosk_handle) + return STATUS_NOT_SUPPORTED; + + if (!recognizer) + return STATUS_UNSUCCESSFUL; + + if (!last_result) + { + if ((tmp = p_vosk_recognizer_final_result(recognizer))) + { + last_result = strdup(tmp); + tmp = last_result; + + /* Operations to remove the JSON wrapper "{\n \"text\" : \"some recognized text\"\n}" -> "some recognized text\0" */ + memmove(last_result, last_result + json_start_len, strlen(last_result) - json_start_len + 1); + last_result = strrchr(last_result, '\"'); + last_result[0] = '\0'; + + last_result = (char *)tmp; + last_result_len = strlen(last_result); + } + else return STATUS_NOT_FOUND; + } + else if (params->result_buf_size >= last_result_len + 1) + { + memcpy(params->result_buf, last_result, last_result_len + 1); + p_vosk_recognizer_reset(recognizer); + + free (last_result); + last_result = NULL; + + return STATUS_SUCCESS; + } + + params->result_buf_size = last_result_len + 1; + return STATUS_BUFFER_TOO_SMALL; +} + +#else /* SONAME_LIBVOSK */ + +#define MAKE_UNSUPPORTED_FUNC( f ) \ + static NTSTATUS f( void *args ) \ + { \ + ERR("wine was compiled without Vosk support. Speech recognition won't work.\n"); \ + return STATUS_NOT_SUPPORTED; \ + } + +MAKE_UNSUPPORTED_FUNC(process_attach) +MAKE_UNSUPPORTED_FUNC(process_detach) +MAKE_UNSUPPORTED_FUNC(speech_create_recognizer) +MAKE_UNSUPPORTED_FUNC(speech_release_recognizer) +MAKE_UNSUPPORTED_FUNC(speech_recognize_audio) +MAKE_UNSUPPORTED_FUNC(speech_get_recognition_result) +#undef MAKE_UNSUPPORTED_FUNC + +#endif /* SONAME_LIBVOSK */ + +unixlib_entry_t __wine_unix_call_funcs[] = +{ + process_attach, + process_detach, + speech_create_recognizer, + speech_release_recognizer, + speech_recognize_audio, + speech_get_recognition_result, +}; + +unixlib_entry_t __wine_unix_call_wow64_funcs[] = +{ + process_attach, + process_detach, + speech_create_recognizer, + speech_release_recognizer, + speech_recognize_audio, + speech_get_recognition_result, +}; diff --git a/dlls/windows.media.speech/unixlib.h b/dlls/windows.media.speech/unixlib.h new file mode 100644 index 00000000000..ad2fab738b9 --- /dev/null +++ b/dlls/windows.media.speech/unixlib.h @@ -0,0 +1,81 @@ +/* + * Unix library interface for Windows.Media.Speech + * + * Copyright 2023 Bernhard Kölbl for CodeWeavers + * + * 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 + */ + +#ifndef __WINE_WINDOWS_MEDIA_SPEECH_UNIXLIB_H +#define __WINE_WINDOWS_MEDIA_SPEECH_UNIXLIB_H + +#include +#include + +#include "windef.h" +#include "winternl.h" +#include "wtypes.h" + +#include "wine/unixlib.h" + +typedef UINT64 speech_recognizer_handle; + +struct speech_create_recognizer_params +{ + speech_recognizer_handle handle; + CHAR locale[LOCALE_NAME_MAX_LENGTH]; + FLOAT sample_rate; + const char **grammar; + unsigned int grammar_size; +}; + +struct speech_release_recognizer_params +{ + speech_recognizer_handle handle; +}; + +enum speech_recognition_status +{ + RECOGNITION_STATUS_CONTINUING, + RECOGNITION_STATUS_RESULT_AVAILABLE, + RECOGNITION_STATUS_EXCEPTION, +}; + +struct speech_recognize_audio_params +{ + speech_recognizer_handle handle; + const BYTE *samples; + UINT32 samples_size; + enum speech_recognition_status status; +}; + +struct speech_get_recognition_result_params +{ + speech_recognizer_handle handle; + char *result_buf; + UINT32 result_buf_size; +}; + +enum vosk_funcs +{ + unix_process_attach, + unix_process_detach, + unix_speech_create_recognizer, + unix_speech_release_recognizer, + unix_speech_recognize_audio, + unix_speech_get_recognition_result, +}; + +#endif diff --git a/dlls/wineandroid.drv/android.h b/dlls/wineandroid.drv/android.h index 0d073a63bcc..2eb6a288a72 100644 --- a/dlls/wineandroid.drv/android.h +++ b/dlls/wineandroid.drv/android.h @@ -86,12 +86,12 @@ extern pthread_mutex_t win_data_mutex DECLSPEC_HIDDEN; extern INT ANDROID_GetKeyNameText( LONG lparam, LPWSTR buffer, INT size ) DECLSPEC_HIDDEN; extern UINT ANDROID_MapVirtualKeyEx( UINT code, UINT maptype, HKL hkl ) DECLSPEC_HIDDEN; extern SHORT ANDROID_VkKeyScanEx( WCHAR ch, HKL hkl ) DECLSPEC_HIDDEN; -extern void ANDROID_SetCursor( HCURSOR handle ) DECLSPEC_HIDDEN; +extern void ANDROID_SetCursor( HWND hwnd, HCURSOR handle ) DECLSPEC_HIDDEN; +extern BOOL ANDROID_CreateDesktop( const WCHAR *name, UINT width, UINT height ) DECLSPEC_HIDDEN; extern BOOL ANDROID_CreateWindow( HWND hwnd ) DECLSPEC_HIDDEN; extern void ANDROID_DestroyWindow( HWND hwnd ) DECLSPEC_HIDDEN; extern BOOL ANDROID_ProcessEvents( DWORD mask ) DECLSPEC_HIDDEN; extern LRESULT ANDROID_DesktopWindowProc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp ) DECLSPEC_HIDDEN; -extern void ANDROID_SetCursor( HCURSOR handle ) DECLSPEC_HIDDEN; extern void ANDROID_SetLayeredWindowAttributes( HWND hwnd, COLORREF key, BYTE alpha, DWORD flags ) DECLSPEC_HIDDEN; extern void ANDROID_SetParent( HWND hwnd, HWND parent, HWND old_parent ) DECLSPEC_HIDDEN; @@ -112,7 +112,6 @@ extern void ANDROID_WindowPosChanged( HWND hwnd, HWND insert_after, UINT swp_fla /* unixlib interface */ -extern NTSTATUS android_create_desktop( void *arg ) DECLSPEC_HIDDEN; extern NTSTATUS android_dispatch_ioctl( void *arg ) DECLSPEC_HIDDEN; extern NTSTATUS android_java_init( void *arg ) DECLSPEC_HIDDEN; extern NTSTATUS android_java_uninit( void *arg ) DECLSPEC_HIDDEN; diff --git a/dlls/wineandroid.drv/dllmain.c b/dlls/wineandroid.drv/dllmain.c index 3f6dbd388eb..320e47e88d2 100644 --- a/dlls/wineandroid.drv/dllmain.c +++ b/dlls/wineandroid.drv/dllmain.c @@ -132,12 +132,3 @@ BOOL WINAPI DllMain( HINSTANCE inst, DWORD reason, LPVOID reserved ) return TRUE; } - - -/*********************************************************************** - * wine_create_desktop (wineandroid.@) - */ -BOOL CDECL wine_create_desktop( UINT width, UINT height ) -{ - return ANDROID_CALL( create_desktop, NULL ); -} diff --git a/dlls/wineandroid.drv/init.c b/dlls/wineandroid.drv/init.c index 074aa6c6257..50659171f08 100644 --- a/dlls/wineandroid.drv/init.c +++ b/dlls/wineandroid.drv/init.c @@ -349,6 +349,7 @@ static const struct user_driver_funcs android_drv_funcs = .pChangeDisplaySettings = ANDROID_ChangeDisplaySettings, .pGetCurrentDisplaySettings = ANDROID_GetCurrentDisplaySettings, .pUpdateDisplayDevices = ANDROID_UpdateDisplayDevices, + .pCreateDesktop = ANDROID_CreateDesktop, .pCreateWindow = ANDROID_CreateWindow, .pDesktopWindowProc = ANDROID_DesktopWindowProc, .pDestroyWindow = ANDROID_DestroyWindow, @@ -609,7 +610,6 @@ static HRESULT android_init( void *arg ) const unixlib_entry_t __wine_unix_call_funcs[] = { - android_create_desktop, android_dispatch_ioctl, android_init, android_java_init, diff --git a/dlls/wineandroid.drv/unixlib.h b/dlls/wineandroid.drv/unixlib.h index a180e6660c8..f1ba25720fd 100644 --- a/dlls/wineandroid.drv/unixlib.h +++ b/dlls/wineandroid.drv/unixlib.h @@ -21,7 +21,6 @@ enum android_funcs { - unix_create_desktop, unix_dispatch_ioctl, unix_init, unix_java_init, diff --git a/dlls/wineandroid.drv/window.c b/dlls/wineandroid.drv/window.c index eb96300da89..74ab61b9a4f 100644 --- a/dlls/wineandroid.drv/window.c +++ b/dlls/wineandroid.drv/window.c @@ -1447,44 +1447,35 @@ static BOOL get_icon_info( HICON handle, ICONINFOEXW *ret ) /*********************************************************************** * ANDROID_SetCursor */ -void ANDROID_SetCursor( HCURSOR handle ) +void ANDROID_SetCursor( HWND hwnd, HCURSOR handle ) { - static HCURSOR last_cursor; - static DWORD last_cursor_change; - - if (InterlockedExchangePointer( (void **)&last_cursor, handle ) != handle || - NtGetTickCount() - last_cursor_change > 100) + if (handle) { - last_cursor_change = NtGetTickCount(); + unsigned int width = 0, height = 0, *bits = NULL; + ICONINFOEXW info; + int id; - if (handle) - { - unsigned int width = 0, height = 0, *bits = NULL; - ICONINFOEXW info; - int id; + if (!get_icon_info( handle, &info )) return; - if (!get_icon_info( handle, &info )) return; + if (!(id = get_cursor_system_id( &info ))) + { + HDC hdc = NtGdiCreateCompatibleDC( 0 ); + bits = get_bitmap_argb( hdc, info.hbmColor, info.hbmMask, &width, &height ); + NtGdiDeleteObjectApp( hdc ); - if (!(id = get_cursor_system_id( &info ))) + /* make sure hotspot is valid */ + if (info.xHotspot >= width || info.yHotspot >= height) { - HDC hdc = NtGdiCreateCompatibleDC( 0 ); - bits = get_bitmap_argb( hdc, info.hbmColor, info.hbmMask, &width, &height ); - NtGdiDeleteObjectApp( hdc ); - - /* make sure hotspot is valid */ - if (info.xHotspot >= width || info.yHotspot >= height) - { - info.xHotspot = width / 2; - info.yHotspot = height / 2; - } + info.xHotspot = width / 2; + info.yHotspot = height / 2; } - ioctl_set_cursor( id, width, height, info.xHotspot, info.yHotspot, bits ); - free( bits ); - NtGdiDeleteObjectApp( info.hbmColor ); - NtGdiDeleteObjectApp( info.hbmMask ); } - else ioctl_set_cursor( 0, 0, 0, 0, 0, NULL ); + ioctl_set_cursor( id, width, height, info.xHotspot, info.yHotspot, bits ); + free( bits ); + NtGdiDeleteObjectApp( info.hbmColor ); + NtGdiDeleteObjectApp( info.hbmMask ); } + else ioctl_set_cursor( 0, 0, 0, 0, 0, NULL ); } @@ -1670,9 +1661,9 @@ LRESULT ANDROID_WindowMessage( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp ) /*********************************************************************** - * android_create_desktop + * ANDROID_CreateDesktop */ -NTSTATUS android_create_desktop( void *arg ) +BOOL ANDROID_CreateDesktop( const WCHAR *name, UINT width, UINT height ) { /* wait until we receive the surface changed event */ while (!screen_width) diff --git a/dlls/wineandroid.drv/wineandroid.drv.spec b/dlls/wineandroid.drv/wineandroid.drv.spec index 22b97356521..e69de29bb2d 100644 --- a/dlls/wineandroid.drv/wineandroid.drv.spec +++ b/dlls/wineandroid.drv/wineandroid.drv.spec @@ -1,2 +0,0 @@ -# Desktop -@ cdecl wine_create_desktop(long long) diff --git a/dlls/winegstreamer/Makefile.in b/dlls/winegstreamer/Makefile.in index ea232db3ff1..7624948dba8 100644 --- a/dlls/winegstreamer/Makefile.in +++ b/dlls/winegstreamer/Makefile.in @@ -2,7 +2,7 @@ MODULE = winegstreamer.dll UNIXLIB = winegstreamer.so IMPORTLIB = winegstreamer IMPORTS = strmbase ole32 oleaut32 msdmo -DELAYIMPORTS = mfplat +DELAYIMPORTS = mfplat mf UNIX_CFLAGS = $(GSTREAMER_CFLAGS) UNIX_LIBS = $(GSTREAMER_LIBS) $(PTHREAD_LIBS) @@ -12,11 +12,12 @@ C_SRCS = \ h264_decoder.c \ main.c \ media_source.c \ + media_source_old.c \ + mf_handler.c \ mfplat.c \ quartz_parser.c \ quartz_transform.c \ resampler.c \ - scheme_handler.c \ unixlib.c \ video_decoder.c \ video_processor.c \ @@ -24,6 +25,8 @@ C_SRCS = \ wg_format.c \ wg_parser.c \ wg_sample.c \ + wg_source.c \ + wg_task_pool.c \ wg_transform.c \ wm_reader.c \ wma_decoder.c \ diff --git a/dlls/winegstreamer/aac_decoder.c b/dlls/winegstreamer/aac_decoder.c index 01f07e6b713..bee353a7174 100644 --- a/dlls/winegstreamer/aac_decoder.c +++ b/dlls/winegstreamer/aac_decoder.c @@ -24,6 +24,8 @@ #include "mfobjects.h" #include "mftransform.h" #include "wmcodecdsp.h" +#include "ks.h" +#include "ksmedia.h" #include "wine/debug.h" @@ -48,6 +50,17 @@ static const GUID *const aac_decoder_output_types[] = &MFAudioFormat_Float, }; +static const UINT32 default_channel_mask[7] = +{ + 0, + 0, + 0, + SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_CENTER, + KSAUDIO_SPEAKER_QUAD, + KSAUDIO_SPEAKER_QUAD | SPEAKER_FRONT_CENTER, + KSAUDIO_SPEAKER_5POINT1, +}; + struct aac_decoder { IMFTransform IMFTransform_iface; @@ -67,6 +80,7 @@ static struct aac_decoder *impl_from_IMFTransform(IMFTransform *iface) static HRESULT try_create_wg_transform(struct aac_decoder *decoder) { struct wg_format input_format, output_format; + struct wg_transform_attrs attrs = {0}; if (decoder->wg_transform) wg_transform_destroy(decoder->wg_transform); @@ -80,7 +94,7 @@ static HRESULT try_create_wg_transform(struct aac_decoder *decoder) if (output_format.major_type == WG_MAJOR_TYPE_UNKNOWN) return MF_E_INVALIDMEDIATYPE; - if (!(decoder->wg_transform = wg_transform_create(&input_format, &output_format))) + if (!(decoder->wg_transform = wg_transform_create(&input_format, &output_format, &attrs))) return E_FAIL; return S_OK; @@ -290,6 +304,21 @@ static HRESULT WINAPI transform_GetOutputAvailableType(IMFTransform *iface, DWOR *type = NULL; + if (FAILED(hr = IMFMediaType_GetUINT32(decoder->input_type, &MF_MT_AUDIO_NUM_CHANNELS, &channel_count)) + || !channel_count) + channel_count = 2; + + if (channel_count >= ARRAY_SIZE(default_channel_mask)) + return MF_E_INVALIDMEDIATYPE; + + if (channel_count > 2 && index >= ARRAY_SIZE(aac_decoder_output_types)) + { + /* If there are more than two channels in the input type GetOutputAvailableType additionally lists + * types with 2 channels. */ + index -= ARRAY_SIZE(aac_decoder_output_types); + channel_count = 2; + } + if (index >= ARRAY_SIZE(aac_decoder_output_types)) return MF_E_NO_MORE_TYPES; index = ARRAY_SIZE(aac_decoder_output_types) - index - 1; @@ -317,8 +346,6 @@ static HRESULT WINAPI transform_GetOutputAvailableType(IMFTransform *iface, DWOR if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_BITS_PER_SAMPLE, sample_size))) goto done; - if (FAILED(hr = IMFMediaType_GetUINT32(decoder->input_type, &MF_MT_AUDIO_NUM_CHANNELS, &channel_count))) - goto done; if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_NUM_CHANNELS, channel_count))) goto done; @@ -337,7 +364,10 @@ static HRESULT WINAPI transform_GetOutputAvailableType(IMFTransform *iface, DWOR goto done; if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_FIXED_SIZE_SAMPLES, 1))) goto done; - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_PREFER_WAVEFORMATEX, 1))) + if (channel_count < 3 && FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_PREFER_WAVEFORMATEX, 1))) + goto done; + if (channel_count >= 3 && FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_CHANNEL_MASK, + default_channel_mask[channel_count]))) goto done; done: @@ -352,6 +382,7 @@ static HRESULT WINAPI transform_SetInputType(IMFTransform *iface, DWORD id, IMFM { struct aac_decoder *decoder = impl_from_IMFTransform(iface); MF_ATTRIBUTE_TYPE item_type; + UINT32 channel_count; GUID major, subtype; HRESULT hr; ULONG i; @@ -374,6 +405,10 @@ static HRESULT WINAPI transform_SetInputType(IMFTransform *iface, DWORD id, IMFM if (i == ARRAY_SIZE(aac_decoder_input_types)) return MF_E_INVALIDMEDIATYPE; + if (SUCCEEDED(IMFMediaType_GetUINT32(type, &MF_MT_AUDIO_NUM_CHANNELS, &channel_count)) + && channel_count >= ARRAY_SIZE(default_channel_mask)) + return MF_E_INVALIDMEDIATYPE; + if (FAILED(IMFMediaType_GetItemType(type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, &item_type)) || item_type != MF_ATTRIBUTE_UINT32) return MF_E_INVALIDMEDIATYPE; @@ -542,7 +577,7 @@ static HRESULT WINAPI transform_ProcessMessage(IMFTransform *iface, MFT_MESSAGE_ return MF_E_TRANSFORM_TYPE_NOT_SET; if (message == MFT_MESSAGE_COMMAND_DRAIN) - return wg_transform_drain(decoder->wg_transform, FALSE); + return wg_transform_drain(decoder->wg_transform); FIXME("Ignoring message %#x.\n", message); @@ -636,13 +671,14 @@ HRESULT aac_decoder_create(REFIID riid, void **ret) }, }; static const struct wg_format input_format = {.major_type = WG_MAJOR_TYPE_AUDIO_MPEG4}; + struct wg_transform_attrs attrs = {0}; struct wg_transform *transform; struct aac_decoder *decoder; HRESULT hr; TRACE("riid %s, ret %p.\n", debugstr_guid(riid), ret); - if (!(transform = wg_transform_create(&input_format, &output_format))) + if (!(transform = wg_transform_create(&input_format, &output_format, &attrs))) { ERR_(winediag)("GStreamer doesn't support WMA decoding, please install appropriate plugins\n"); return E_FAIL; diff --git a/dlls/winegstreamer/color_convert.c b/dlls/winegstreamer/color_convert.c index 0eaddc687ee..598b2aa5b43 100644 --- a/dlls/winegstreamer/color_convert.c +++ b/dlls/winegstreamer/color_convert.c @@ -98,6 +98,7 @@ static inline struct color_convert *impl_from_IUnknown(IUnknown *iface) static HRESULT try_create_wg_transform(struct color_convert *impl) { struct wg_format input_format, output_format; + struct wg_transform_attrs attrs = {.input_queue_length = 15}; if (impl->wg_transform) wg_transform_destroy(impl->wg_transform); @@ -111,7 +112,7 @@ static HRESULT try_create_wg_transform(struct color_convert *impl) if (output_format.major_type == WG_MAJOR_TYPE_UNKNOWN) return MF_E_INVALIDMEDIATYPE; - if (!(impl->wg_transform = wg_transform_create(&input_format, &output_format))) + if (!(impl->wg_transform = wg_transform_create(&input_format, &output_format, &attrs))) return E_FAIL; return S_OK; @@ -363,6 +364,7 @@ static HRESULT WINAPI transform_SetInputType(IMFTransform *iface, DWORD id, IMFM struct color_convert *impl = impl_from_IMFTransform(iface); GUID major, subtype; UINT64 frame_size; + UINT32 stride; HRESULT hr; ULONG i; @@ -392,6 +394,19 @@ static HRESULT WINAPI transform_SetInputType(IMFTransform *iface, DWORD id, IMFM IMFMediaType_Release(impl->input_type); impl->input_type = NULL; } + if (FAILED(IMFMediaType_GetUINT32(impl->input_type, &MF_MT_DEFAULT_STRIDE, &stride))) + { + if (FAILED(hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, frame_size >> 32, (LONG *)&stride))) + { + IMFMediaType_Release(impl->input_type); + impl->input_type = NULL; + } + if (FAILED(hr = IMFMediaType_SetUINT32(impl->input_type, &MF_MT_DEFAULT_STRIDE, abs((INT32)stride)))) + { + IMFMediaType_Release(impl->input_type); + impl->input_type = NULL; + } + } if (impl->output_type && FAILED(hr = try_create_wg_transform(impl))) { @@ -411,6 +426,7 @@ static HRESULT WINAPI transform_SetOutputType(IMFTransform *iface, DWORD id, IMF struct color_convert *impl = impl_from_IMFTransform(iface); GUID major, subtype; UINT64 frame_size; + UINT32 stride; HRESULT hr; ULONG i; @@ -440,6 +456,19 @@ static HRESULT WINAPI transform_SetOutputType(IMFTransform *iface, DWORD id, IMF IMFMediaType_Release(impl->output_type); impl->output_type = NULL; } + if (FAILED(IMFMediaType_GetUINT32(impl->output_type, &MF_MT_DEFAULT_STRIDE, &stride))) + { + if (FAILED(hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, frame_size >> 32, (LONG *)&stride))) + { + IMFMediaType_Release(impl->output_type); + impl->output_type = NULL; + } + if (FAILED(hr = IMFMediaType_SetUINT32(impl->output_type, &MF_MT_DEFAULT_STRIDE, abs((INT32)stride)))) + { + IMFMediaType_Release(impl->output_type); + impl->output_type = NULL; + } + } if (impl->input_type && FAILED(hr = try_create_wg_transform(impl))) { @@ -908,13 +937,14 @@ HRESULT color_convert_create(IUnknown *outer, IUnknown **out) .height = 1080, }, }; + struct wg_transform_attrs attrs = {0}; struct wg_transform *transform; struct color_convert *impl; HRESULT hr; TRACE("outer %p, out %p.\n", outer, out); - if (!(transform = wg_transform_create(&input_format, &output_format))) + if (!(transform = wg_transform_create(&input_format, &output_format, &attrs))) { ERR_(winediag)("GStreamer doesn't support video conversion, please install appropriate plugins.\n"); return E_FAIL; diff --git a/dlls/winegstreamer/gst_private.h b/dlls/winegstreamer/gst_private.h index e4d0af8af44..c53483c0ca7 100644 --- a/dlls/winegstreamer/gst_private.h +++ b/dlls/winegstreamer/gst_private.h @@ -101,11 +101,23 @@ void wg_parser_stream_seek(struct wg_parser_stream *stream, double rate, uint64_t start_pos, uint64_t stop_pos, DWORD start_flags, DWORD stop_flags); struct wg_transform *wg_transform_create(const struct wg_format *input_format, - const struct wg_format *output_format); + const struct wg_format *output_format, const struct wg_transform_attrs *attrs); void wg_transform_destroy(struct wg_transform *transform); bool wg_transform_set_output_format(struct wg_transform *transform, struct wg_format *format); bool wg_transform_get_status(struct wg_transform *transform, bool *accepts_input); -HRESULT wg_transform_drain(struct wg_transform *transform, BOOL flush); +HRESULT wg_transform_drain(struct wg_transform *transform); +HRESULT wg_transform_flush(struct wg_transform *transform); + +struct wg_source *wg_source_create(const WCHAR *url, uint64_t file_size, + const void *data, uint32_t size, WCHAR mime_type[256]); +void wg_source_destroy(struct wg_source *source); +bool wg_source_get_status(struct wg_source *source, uint32_t *stream_count, + uint64_t *duration, uint64_t *read_offset); +HRESULT wg_source_push_data(struct wg_source *source, const void *data, uint32_t size); +bool wg_source_get_stream_format(struct wg_source *source, UINT32 index, + struct wg_format *format); +char *wg_source_get_stream_tag(struct wg_source *source, UINT32 index, + enum wg_parser_tag tag); unsigned int wg_format_get_max_size(const struct wg_format *format); @@ -149,12 +161,18 @@ HRESULT wg_transform_read_mf(struct wg_transform *transform, IMFSample *sample, HRESULT wg_transform_read_quartz(struct wg_transform *transform, struct wg_sample *sample); HRESULT winegstreamer_stream_handler_create(REFIID riid, void **obj); -HRESULT winegstreamer_create_media_source_from_uri(const WCHAR *uri, IUnknown **out_media_source) DECLSPEC_HIDDEN; +HRESULT winegstreamer_scheme_handler_create(REFIID riid, void **obj); +HRESULT media_source_create(IMFByteStream *stream, const WCHAR *url, BYTE *data, UINT64 size, IMFMediaSource **out); +HRESULT media_source_create_old(IMFByteStream *stream, const WCHAR *url, IMFMediaSource **out); +HRESULT media_source_create_from_url(const WCHAR *url, IMFMediaSource **out); + +unsigned int wg_format_get_stride(const struct wg_format *format); + +bool wg_video_format_is_rgb(enum wg_video_format format); HRESULT aac_decoder_create(REFIID riid, void **ret); HRESULT h264_decoder_create(REFIID riid, void **ret); HRESULT video_processor_create(REFIID riid, void **ret); -HRESULT gstreamer_scheme_handler_construct(REFIID riid, void **ret) DECLSPEC_HIDDEN; extern const GUID MFAudioFormat_RAW_AAC; diff --git a/dlls/winegstreamer/h264_decoder.c b/dlls/winegstreamer/h264_decoder.c index c5600389be7..1b1646a9ed3 100644 --- a/dlls/winegstreamer/h264_decoder.c +++ b/dlls/winegstreamer/h264_decoder.c @@ -53,16 +53,20 @@ struct h264_decoder IMFAttributes *attributes; IMFAttributes *output_attributes; + UINT64 sample_time; IMFMediaType *input_type; MFT_INPUT_STREAM_INFO input_info; IMFMediaType *output_type; MFT_OUTPUT_STREAM_INFO output_info; + IMFMediaType *stream_type; - UINT64 last_pts; - - struct wg_format wg_format; struct wg_transform *wg_transform; struct wg_sample_queue *wg_sample_queue; + + IMFVideoSampleAllocatorEx *allocator; + BOOL allocator_initialized; + IMFTransform *copier; + IMFMediaBuffer *temp_buffer; }; static struct h264_decoder *impl_from_IMFTransform(IMFTransform *iface) @@ -72,10 +76,20 @@ static struct h264_decoder *impl_from_IMFTransform(IMFTransform *iface) static HRESULT try_create_wg_transform(struct h264_decoder *decoder) { + /* Call of Duty: Black Ops 3 doesn't care about the ProcessInput/ProcessOutput + * return values, it calls them in a specific order and expects the decoder + * transform to be able to queue its input buffers. We need to use a buffer list + * to match its expectations. + */ + struct wg_transform_attrs attrs = + { + .output_plane_align = 15, + .input_queue_length = 15, + }; struct wg_format input_format; struct wg_format output_format; + UINT32 low_latency; - decoder->last_pts = 0; if (decoder->wg_transform) wg_transform_destroy(decoder->wg_transform); decoder->wg_transform = NULL; @@ -96,7 +110,16 @@ static HRESULT try_create_wg_transform(struct h264_decoder *decoder) output_format.u.video.fps_d = 0; output_format.u.video.fps_n = 0; - if (!(decoder->wg_transform = wg_transform_create(&input_format, &output_format))) + if (SUCCEEDED(IMFAttributes_GetUINT32(decoder->attributes, &MF_LOW_LATENCY, &low_latency))) + attrs.low_latency = !!low_latency; + + { + const char *sgi; + if ((sgi = getenv("SteamGameId")) && (!strcmp(sgi, "2009100"))) + attrs.low_latency = FALSE; + } + + if (!(decoder->wg_transform = wg_transform_create(&input_format, &output_format, &attrs))) return E_FAIL; return S_OK; @@ -105,8 +128,8 @@ static HRESULT try_create_wg_transform(struct h264_decoder *decoder) static HRESULT fill_output_media_type(struct h264_decoder *decoder, IMFMediaType *media_type) { IMFMediaType *default_type = decoder->output_type; - struct wg_format *wg_format = &decoder->wg_format; UINT32 value, width, height; + MFVideoArea aperture; UINT64 ratio; GUID subtype; HRESULT hr; @@ -116,7 +139,8 @@ static HRESULT fill_output_media_type(struct h264_decoder *decoder, IMFMediaType if (FAILED(hr = IMFMediaType_GetUINT64(media_type, &MF_MT_FRAME_SIZE, &ratio))) { - ratio = (UINT64)wg_format->u.video.width << 32 | wg_format->u.video.height; + if (FAILED(IMFMediaType_GetUINT64(decoder->stream_type, &MF_MT_FRAME_SIZE, &ratio))) + ratio = (UINT64)1920 << 32 | 1080; if (FAILED(hr = IMFMediaType_SetUINT64(media_type, &MF_MT_FRAME_SIZE, ratio))) return hr; } @@ -125,14 +149,16 @@ static HRESULT fill_output_media_type(struct h264_decoder *decoder, IMFMediaType if (FAILED(hr = IMFMediaType_GetItem(media_type, &MF_MT_FRAME_RATE, NULL))) { - ratio = (UINT64)wg_format->u.video.fps_n << 32 | wg_format->u.video.fps_d; + if (FAILED(IMFMediaType_GetUINT64(decoder->stream_type, &MF_MT_FRAME_RATE, &ratio))) + ratio = (UINT64)30000 << 32 | 1001; if (FAILED(hr = IMFMediaType_SetUINT64(media_type, &MF_MT_FRAME_RATE, ratio))) return hr; } if (FAILED(hr = IMFMediaType_GetItem(media_type, &MF_MT_PIXEL_ASPECT_RATIO, NULL))) { - ratio = (UINT64)1 << 32 | 1; /* FIXME: read it from format */ + if (FAILED(IMFMediaType_GetUINT64(decoder->stream_type, &MF_MT_PIXEL_ASPECT_RATIO, &ratio))) + ratio = (UINT64)1 << 32 | 1; if (FAILED(hr = IMFMediaType_SetUINT64(media_type, &MF_MT_PIXEL_ASPECT_RATIO, ratio))) return hr; } @@ -186,16 +212,9 @@ static HRESULT fill_output_media_type(struct h264_decoder *decoder, IMFMediaType } if (FAILED(hr = IMFMediaType_GetItem(media_type, &MF_MT_MINIMUM_DISPLAY_APERTURE, NULL)) - && !IsRectEmpty(&wg_format->u.video.padding)) + && SUCCEEDED(hr = IMFMediaType_GetBlob(decoder->stream_type, &MF_MT_MINIMUM_DISPLAY_APERTURE, + (BYTE *)&aperture, sizeof(aperture), &value))) { - MFVideoArea aperture = - { - .OffsetX = {.value = wg_format->u.video.padding.left}, - .OffsetY = {.value = wg_format->u.video.padding.top}, - .Area.cx = wg_format->u.video.width - wg_format->u.video.padding.right - wg_format->u.video.padding.left, - .Area.cy = wg_format->u.video.height - wg_format->u.video.padding.bottom - wg_format->u.video.padding.top, - }; - if (FAILED(hr = IMFMediaType_SetBlob(media_type, &MF_MT_MINIMUM_DISPLAY_APERTURE, (BYTE *)&aperture, sizeof(aperture)))) return hr; @@ -204,6 +223,31 @@ static HRESULT fill_output_media_type(struct h264_decoder *decoder, IMFMediaType return S_OK; } +static HRESULT init_allocator(struct h264_decoder *decoder) +{ + HRESULT hr; + + if (decoder->allocator_initialized) + return S_OK; + + if (FAILED(hr = IMFTransform_SetInputType(decoder->copier, 0, decoder->output_type, 0))) + return hr; + if (FAILED(hr = IMFTransform_SetOutputType(decoder->copier, 0, decoder->output_type, 0))) + return hr; + + if (FAILED(hr = IMFVideoSampleAllocatorEx_InitializeSampleAllocatorEx(decoder->allocator, 10, 10, + decoder->attributes, decoder->output_type))) + return hr; + decoder->allocator_initialized = TRUE; + return S_OK; +} + +static void uninit_allocator(struct h264_decoder *decoder) +{ + IMFVideoSampleAllocatorEx_UninitializeSampleAllocator(decoder->allocator); + decoder->allocator_initialized = FALSE; +} + static HRESULT WINAPI transform_QueryInterface(IMFTransform *iface, REFIID iid, void **out) { struct h264_decoder *decoder = impl_from_IMFTransform(iface); @@ -243,6 +287,10 @@ static ULONG WINAPI transform_Release(IMFTransform *iface) if (!refcount) { + IMFTransform_Release(decoder->copier); + IMFVideoSampleAllocatorEx_Release(decoder->allocator); + if (decoder->temp_buffer) + IMFMediaBuffer_Release(decoder->temp_buffer); if (decoder->wg_transform) wg_transform_destroy(decoder->wg_transform); if (decoder->input_type) @@ -253,7 +301,6 @@ static ULONG WINAPI transform_Release(IMFTransform *iface) IMFAttributes_Release(decoder->output_attributes); if (decoder->attributes) IMFAttributes_Release(decoder->attributes); - wg_sample_queue_destroy(decoder->wg_sample_queue); free(decoder); } @@ -451,10 +498,9 @@ static HRESULT WINAPI transform_SetInputType(IMFTransform *iface, DWORD id, IMFM if (SUCCEEDED(IMFMediaType_GetUINT64(type, &MF_MT_FRAME_SIZE, &frame_size))) { - decoder->wg_format.u.video.width = frame_size >> 32; - decoder->wg_format.u.video.height = (UINT32)frame_size; - decoder->output_info.cbSize = decoder->wg_format.u.video.width - * decoder->wg_format.u.video.height * 2; + if (FAILED(hr = IMFMediaType_SetUINT64(decoder->stream_type, &MF_MT_FRAME_SIZE, frame_size))) + WARN("Failed to update stream type frame size, hr %#lx\n", hr); + decoder->output_info.cbSize = (frame_size >> 32) * (UINT32)frame_size * 2; } return S_OK; @@ -463,8 +509,8 @@ static HRESULT WINAPI transform_SetInputType(IMFTransform *iface, DWORD id, IMFM static HRESULT WINAPI transform_SetOutputType(IMFTransform *iface, DWORD id, IMFMediaType *type, DWORD flags) { struct h264_decoder *decoder = impl_from_IMFTransform(iface); + UINT64 frame_size, stream_frame_size; GUID major, subtype; - UINT64 frame_size; HRESULT hr; ULONG i; @@ -486,9 +532,10 @@ static HRESULT WINAPI transform_SetOutputType(IMFTransform *iface, DWORD id, IMF if (i == ARRAY_SIZE(h264_decoder_output_types)) return MF_E_INVALIDMEDIATYPE; - if (FAILED(hr = IMFMediaType_GetUINT64(type, &MF_MT_FRAME_SIZE, &frame_size)) - || (frame_size >> 32) != decoder->wg_format.u.video.width - || (UINT32)frame_size != decoder->wg_format.u.video.height) + if (FAILED(hr = IMFMediaType_GetUINT64(type, &MF_MT_FRAME_SIZE, &frame_size))) + return MF_E_INVALIDMEDIATYPE; + if (SUCCEEDED(IMFMediaType_GetUINT64(decoder->stream_type, &MF_MT_FRAME_SIZE, &stream_frame_size)) + && frame_size != stream_frame_size) return MF_E_INVALIDMEDIATYPE; if (flags & MFT_SET_TYPE_TEST_ONLY) return S_OK; @@ -583,20 +630,33 @@ static HRESULT WINAPI transform_ProcessEvent(IMFTransform *iface, DWORD id, IMFM static HRESULT WINAPI transform_ProcessMessage(IMFTransform *iface, MFT_MESSAGE_TYPE message, ULONG_PTR param) { struct h264_decoder *decoder = impl_from_IMFTransform(iface); + HRESULT hr; TRACE("iface %p, message %#x, param %Ix.\n", iface, message, param); - if (!decoder->wg_transform) - return MF_E_TRANSFORM_TYPE_NOT_SET; + switch (message) + { + case MFT_MESSAGE_SET_D3D_MANAGER: + if (FAILED(hr = IMFVideoSampleAllocatorEx_SetDirectXManager(decoder->allocator, (IUnknown *)param))) + return hr; + + uninit_allocator(decoder); + if (param) + decoder->output_info.dwFlags |= MFT_OUTPUT_STREAM_PROVIDES_SAMPLES; + else + decoder->output_info.dwFlags &= ~MFT_OUTPUT_STREAM_PROVIDES_SAMPLES; + return S_OK; - if (message == MFT_MESSAGE_COMMAND_DRAIN) - return wg_transform_drain(decoder->wg_transform, FALSE); - if (message == MFT_MESSAGE_COMMAND_FLUSH) - return wg_transform_drain(decoder->wg_transform, TRUE); + case MFT_MESSAGE_COMMAND_DRAIN: + return wg_transform_drain(decoder->wg_transform); - FIXME("Ignoring message %#x.\n", message); + case MFT_MESSAGE_COMMAND_FLUSH: + return wg_transform_flush(decoder->wg_transform); - return S_OK; + default: + FIXME("Ignoring message %#x.\n", message); + return S_OK; + } } static HRESULT WINAPI transform_ProcessInput(IMFTransform *iface, DWORD id, IMFSample *sample, DWORD flags) @@ -611,15 +671,69 @@ static HRESULT WINAPI transform_ProcessInput(IMFTransform *iface, DWORD id, IMFS return wg_transform_push_mf(decoder->wg_transform, sample, decoder->wg_sample_queue); } +static HRESULT output_sample(struct h264_decoder *decoder, IMFSample **out, IMFSample *src_sample) +{ + MFT_OUTPUT_DATA_BUFFER output[1]; + IMFSample *sample; + DWORD status; + HRESULT hr; + + if (FAILED(hr = init_allocator(decoder))) + { + ERR("Failed to initialize allocator, hr %#lx.\n", hr); + return hr; + } + if (FAILED(hr = IMFVideoSampleAllocatorEx_AllocateSample(decoder->allocator, &sample))) + return hr; + + if (FAILED(hr = IMFTransform_ProcessInput(decoder->copier, 0, src_sample, 0))) + { + IMFSample_Release(sample); + return hr; + } + output[0].pSample = sample; + if (FAILED(hr = IMFTransform_ProcessOutput(decoder->copier, 0, 1, output, &status))) + { + IMFSample_Release(sample); + return hr; + } + *out = sample; + return S_OK; +} + +static HRESULT handle_stream_type_change(struct h264_decoder *decoder, const struct wg_format *format) +{ + UINT64 frame_size, frame_rate; + HRESULT hr; + + if (decoder->stream_type) + IMFMediaType_Release(decoder->stream_type); + if (!(decoder->stream_type = mf_media_type_from_wg_format(format))) + return E_OUTOFMEMORY; + + if (SUCCEEDED(IMFMediaType_GetUINT64(decoder->output_type, &MF_MT_FRAME_RATE, &frame_rate)) + && FAILED(hr = IMFMediaType_SetUINT64(decoder->stream_type, &MF_MT_FRAME_RATE, frame_rate))) + WARN("Failed to update stream type frame size, hr %#lx\n", hr); + + if (FAILED(hr = IMFMediaType_GetUINT64(decoder->stream_type, &MF_MT_FRAME_SIZE, &frame_size))) + return hr; + decoder->output_info.cbSize = (frame_size >> 32) * (UINT32)frame_size * 2; + uninit_allocator(decoder); + + return MF_E_TRANSFORM_STREAM_CHANGE; +} + static HRESULT WINAPI transform_ProcessOutput(IMFTransform *iface, DWORD flags, DWORD count, MFT_OUTPUT_DATA_BUFFER *samples, DWORD *status) { struct h264_decoder *decoder = impl_from_IMFTransform(iface); - UINT64 frame_rate, duration; struct wg_format wg_format; UINT32 sample_size; - LONGLONG time; + LONGLONG duration; + IMFSample *sample; + UINT64 frame_size, frame_rate; GUID subtype; + DWORD size; HRESULT hr; TRACE("iface %p, flags %#lx, count %lu, samples %p, status %p.\n", iface, flags, count, samples, status); @@ -631,46 +745,65 @@ static HRESULT WINAPI transform_ProcessOutput(IMFTransform *iface, DWORD flags, return MF_E_TRANSFORM_TYPE_NOT_SET; *status = samples->dwStatus = 0; - if (!samples->pSample) + if (!(sample = samples->pSample) && !(decoder->output_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES)) return E_INVALIDARG; if (FAILED(hr = IMFMediaType_GetGUID(decoder->output_type, &MF_MT_SUBTYPE, &subtype))) return hr; - if (FAILED(hr = MFCalculateImageSize(&subtype, decoder->wg_format.u.video.width, - decoder->wg_format.u.video.height, &sample_size))) + if (FAILED(hr = IMFMediaType_GetUINT64(decoder->output_type, &MF_MT_FRAME_SIZE, &frame_size))) + return hr; + if (FAILED(hr = MFCalculateImageSize(&subtype, frame_size >> 32, (UINT32)frame_size, &sample_size))) return hr; - if (SUCCEEDED(hr = wg_transform_read_mf(decoder->wg_transform, samples->pSample, - sample_size, &wg_format, &samples->dwStatus))) + if (decoder->output_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) { - wg_sample_queue_flush(decoder->wg_sample_queue, false); - - if (FAILED(IMFSample_GetSampleTime(samples->pSample, &time)) - || FAILED(IMFSample_GetSampleDuration(samples->pSample, &time))) + if (decoder->temp_buffer) + { + if (FAILED(IMFMediaBuffer_GetMaxLength(decoder->temp_buffer, &size)) || size < sample_size) + { + IMFMediaBuffer_Release(decoder->temp_buffer); + decoder->temp_buffer = NULL; + } + } + if (!decoder->temp_buffer && FAILED(hr = MFCreateMemoryBuffer(sample_size, &decoder->temp_buffer))) + return hr; + if (FAILED(hr = MFCreateSample(&sample))) + return hr; + if (FAILED(hr = IMFSample_AddBuffer(sample, decoder->temp_buffer))) { - frame_rate = (UINT64)decoder->wg_format.u.video.fps_n << 32 | decoder->wg_format.u.video.fps_d; - duration = (UINT64)10000000 * (UINT32)frame_rate / (frame_rate >> 32); - IMFSample_SetSampleTime(samples->pSample, decoder->last_pts); - IMFSample_SetSampleDuration(samples->pSample, duration); - decoder->last_pts += duration; + IMFSample_Release(sample); + return hr; } } - if (hr == MF_E_TRANSFORM_STREAM_CHANGE) + if (SUCCEEDED(hr = wg_transform_read_mf(decoder->wg_transform, sample, + sample_size, &wg_format, &samples->dwStatus))) { - decoder->wg_format = wg_format; - decoder->output_info.cbSize = ALIGN_SIZE(decoder->wg_format.u.video.width, 0xf) - * ALIGN_SIZE(decoder->wg_format.u.video.height, 0xf) * 2; + wg_sample_queue_flush(decoder->wg_sample_queue, false); - /* keep the frame rate that was requested, GStreamer doesn't provide any */ - if (SUCCEEDED(IMFMediaType_GetUINT64(decoder->output_type, &MF_MT_FRAME_RATE, &frame_rate))) - { - decoder->wg_format.u.video.fps_n = frame_rate >> 32; - decoder->wg_format.u.video.fps_d = (UINT32)frame_rate; - } + if (FAILED(IMFMediaType_GetUINT64(decoder->input_type, &MF_MT_FRAME_RATE, &frame_rate))) + frame_rate = (UINT64)30000 << 32 | 1001; + duration = (UINT64)10000000 * (UINT32)frame_rate / (frame_rate >> 32); + if (FAILED(IMFSample_SetSampleTime(sample, decoder->sample_time))) + WARN("Failed to set sample time\n"); + if (FAILED(IMFSample_SetSampleDuration(sample, duration))) + WARN("Failed to set sample duration\n"); + decoder->sample_time += duration; + } + + if (hr == MF_E_TRANSFORM_STREAM_CHANGE) + { samples[0].dwStatus |= MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE; *status |= MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE; + hr = handle_stream_type_change(decoder, &wg_format); + } + + if (decoder->output_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) + { + if (hr == S_OK && FAILED(hr = output_sample(decoder, &samples->pSample, sample))) + ERR("Failed to output sample, hr %#lx.\n", hr); + IMFSample_Release(sample); } return hr; @@ -719,13 +852,14 @@ HRESULT h264_decoder_create(REFIID riid, void **ret) }, }; static const struct wg_format input_format = {.major_type = WG_MAJOR_TYPE_VIDEO_H264}; + struct wg_transform_attrs attrs = {0}; struct wg_transform *transform; struct h264_decoder *decoder; HRESULT hr; TRACE("riid %s, ret %p.\n", debugstr_guid(riid), ret); - if (!(transform = wg_transform_create(&input_format, &output_format))) + if (!(transform = wg_transform_create(&input_format, &output_format, &attrs))) { ERR_(winediag)("GStreamer doesn't support H.264 decoding, please install appropriate plugins\n"); return E_FAIL; @@ -737,11 +871,6 @@ HRESULT h264_decoder_create(REFIID riid, void **ret) decoder->IMFTransform_iface.lpVtbl = &transform_vtbl; decoder->refcount = 1; - decoder->wg_format.u.video.format = WG_VIDEO_FORMAT_UNKNOWN; - decoder->wg_format.u.video.width = 1920; - decoder->wg_format.u.video.height = 1080; - decoder->wg_format.u.video.fps_n = 30000; - decoder->wg_format.u.video.fps_d = 1001; decoder->input_info.dwFlags = MFT_INPUT_STREAM_WHOLE_SAMPLES | MFT_INPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER | MFT_INPUT_STREAM_FIXED_SAMPLE_SIZE; @@ -750,26 +879,45 @@ HRESULT h264_decoder_create(REFIID riid, void **ret) | MFT_OUTPUT_STREAM_FIXED_SAMPLE_SIZE; decoder->output_info.cbSize = 1920 * 1088 * 2; + if (FAILED(hr = MFCreateMediaType(&decoder->stream_type))) + goto failed; if (FAILED(hr = MFCreateAttributes(&decoder->attributes, 16))) goto failed; if (FAILED(hr = IMFAttributes_SetUINT32(decoder->attributes, &MF_LOW_LATENCY, 0))) goto failed; if (FAILED(hr = IMFAttributes_SetUINT32(decoder->attributes, &MF_SA_D3D11_AWARE, TRUE))) goto failed; + + { + const char *sgi; + if ((sgi = getenv("SteamGameId")) && (!strcmp(sgi, "2009100"))) + IMFAttributes_SetUINT32(decoder->attributes, &MF_SA_D3D11_AWARE, FALSE); + } + if (FAILED(hr = MFCreateAttributes(&decoder->output_attributes, 0))) goto failed; if (FAILED(hr = wg_sample_queue_create(&decoder->wg_sample_queue))) goto failed; + if (FAILED(hr = MFCreateVideoSampleAllocatorEx(&IID_IMFVideoSampleAllocatorEx, (void **)&decoder->allocator))) + goto failed; + if (FAILED(hr = MFCreateSampleCopierMFT(&decoder->copier))) + goto failed; *ret = &decoder->IMFTransform_iface; TRACE("Created decoder %p\n", *ret); return S_OK; failed: + if (decoder->allocator) + IMFVideoSampleAllocatorEx_Release(decoder->allocator); + if (decoder->wg_sample_queue) + wg_sample_queue_destroy(decoder->wg_sample_queue); if (decoder->output_attributes) IMFAttributes_Release(decoder->output_attributes); if (decoder->attributes) IMFAttributes_Release(decoder->attributes); + if (decoder->stream_type) + IMFMediaType_Release(decoder->stream_type); free(decoder); return hr; } diff --git a/dlls/winegstreamer/main.c b/dlls/winegstreamer/main.c index 232e81a6f27..261d810795f 100644 --- a/dlls/winegstreamer/main.c +++ b/dlls/winegstreamer/main.c @@ -326,12 +326,13 @@ void wg_parser_stream_seek(struct wg_parser_stream *stream, double rate, } struct wg_transform *wg_transform_create(const struct wg_format *input_format, - const struct wg_format *output_format) + const struct wg_format *output_format, const struct wg_transform_attrs *attrs) { struct wg_transform_create_params params = { .input_format = input_format, .output_format = output_format, + .attrs = attrs, }; TRACE("input_format %p, output_format %p.\n", input_format, output_format); @@ -415,17 +416,216 @@ bool wg_transform_set_output_format(struct wg_transform *transform, struct wg_fo return !WINE_UNIX_CALL(unix_wg_transform_set_output_format, ¶ms); } -HRESULT wg_transform_drain(struct wg_transform *transform, BOOL flush) +HRESULT wg_transform_drain(struct wg_transform *transform) { - struct wg_transform_drain_params params = + NTSTATUS status; + + TRACE("transform %p.\n", transform); + + if ((status = WINE_UNIX_CALL(unix_wg_transform_drain, transform))) { - .transform = transform, - .flush = flush, - }; + WARN("wg_transform_drain returned status %#lx\n", status); + return HRESULT_FROM_NT(status); + } + + return S_OK; +} + +HRESULT wg_transform_flush(struct wg_transform *transform) +{ + NTSTATUS status; TRACE("transform %p.\n", transform); - return WINE_UNIX_CALL(unix_wg_transform_drain, ¶ms); + if ((status = WINE_UNIX_CALL(unix_wg_transform_flush, transform))) + { + WARN("wg_transform_flush returned status %#lx\n", status); + return HRESULT_FROM_NT(status); + } + + return S_OK; +} + +struct wg_source *wg_source_create(const WCHAR *url, uint64_t file_size, + const void *data, uint32_t size, WCHAR mime_type[256]) +{ + struct wg_source_create_params params = + { + .file_size = file_size, + .data = data, .size = size, + }; + UINT len = url ? WideCharToMultiByte(CP_ACP, 0, url, -1, NULL, 0, NULL, NULL) : 0; + char *tmp = url ? malloc(len) : NULL; + + TRACE("url %s, file_size %#I64x, data %p, size %#x, mime_type %p\n", debugstr_w(url), + file_size, data, size, mime_type); + + if ((params.url = tmp)) + WideCharToMultiByte(CP_ACP, 0, url, -1, tmp, len, NULL, NULL); + + if (!WINE_UNIX_CALL(unix_wg_source_create, ¶ms)) + { + MultiByteToWideChar(CP_ACP, 0, params.mime_type, -1, mime_type, 256); + TRACE("Returning source %p.\n", params.source); + } + + free(tmp); + return params.source; +} + +void wg_source_destroy(struct wg_source *source) +{ + TRACE("source %p.\n", source); + + WINE_UNIX_CALL(unix_wg_source_destroy, source); +} + +bool wg_source_get_status(struct wg_source *source, uint32_t *stream_count, + uint64_t *duration, uint64_t *read_offset) +{ + struct wg_source_get_status_params params = + { + .source = source, + }; + NTSTATUS status; + + TRACE("source %p, stream_count %p, duration %p, read_offset %p\n", + source, stream_count, duration, read_offset); + + if ((status = WINE_UNIX_CALL(unix_wg_source_get_status, ¶ms)) + && status != STATUS_PENDING) + return false; + + *stream_count = params.stream_count; + *duration = params.duration; + *read_offset = params.read_offset; + TRACE("source %p, stream_count %u, duration %s, read_offset %#I64x\n", + source, *stream_count, debugstr_time(*duration), *read_offset); + return true; +} + +HRESULT wg_source_push_data(struct wg_source *source, const void *data, uint32_t size) +{ + struct wg_source_push_data_params params = + { + .source = source, + .data = data, + .size = size, + }; + TRACE("source %p, data %p, size %#x\n", source, data, size); + return HRESULT_FROM_NT(WINE_UNIX_CALL(unix_wg_source_push_data, ¶ms)); +} + +bool wg_source_get_stream_format(struct wg_source *source, UINT32 index, + struct wg_format *format) +{ + struct wg_source_get_stream_format_params params = + { + .source = source, + .index = index, + }; + + TRACE("source %p, index %u, format %p\n", source, + index, format); + + if (WINE_UNIX_CALL(unix_wg_source_get_stream_format, ¶ms)) + return false; + + *format = params.format; + return true; +} + +char *wg_source_get_stream_tag(struct wg_source *source, UINT32 index, enum wg_parser_tag tag) +{ + struct wg_source_get_stream_tag_params params = + { + .source = source, + .index = index, + .tag = tag, + }; + char *buffer; + + if (WINE_UNIX_CALL(unix_wg_source_get_stream_tag, ¶ms) != STATUS_BUFFER_TOO_SMALL) + return NULL; + if (!(buffer = malloc(params.size))) + { + ERR("No memory.\n"); + return NULL; + } + params.buffer = buffer; + if (WINE_UNIX_CALL(unix_wg_source_get_stream_tag, ¶ms)) + { + ERR("wg_source_get_stream_tag failed unexpectedly.\n"); + free(buffer); + return NULL; + } + return buffer; +} + +#define ALIGN(n, alignment) (((n) + (alignment) - 1) & ~((alignment) - 1)) + +unsigned int wg_format_get_stride(const struct wg_format *format) +{ + const unsigned int width = format->u.video.width; + + switch (format->u.video.format) + { + case WG_VIDEO_FORMAT_AYUV: + return width * 4; + + case WG_VIDEO_FORMAT_BGRA: + case WG_VIDEO_FORMAT_BGRx: + case WG_VIDEO_FORMAT_RGBA: + return width * 4; + + case WG_VIDEO_FORMAT_BGR: + return ALIGN(width * 3, 4); + + case WG_VIDEO_FORMAT_UYVY: + case WG_VIDEO_FORMAT_YUY2: + case WG_VIDEO_FORMAT_YVYU: + return ALIGN(width * 2, 4); + + case WG_VIDEO_FORMAT_RGB15: + case WG_VIDEO_FORMAT_RGB16: + return ALIGN(width * 2, 4); + + case WG_VIDEO_FORMAT_I420: + case WG_VIDEO_FORMAT_NV12: + case WG_VIDEO_FORMAT_YV12: + return ALIGN(width, 4); /* Y plane */ + + case WG_VIDEO_FORMAT_UNKNOWN: + FIXME("Cannot calculate stride for unknown video format.\n"); + } + + return 0; +} + +bool wg_video_format_is_rgb(enum wg_video_format format) +{ + switch (format) + { + case WG_VIDEO_FORMAT_BGRA: + case WG_VIDEO_FORMAT_BGRx: + case WG_VIDEO_FORMAT_RGBA: + case WG_VIDEO_FORMAT_BGR: + case WG_VIDEO_FORMAT_RGB15: + case WG_VIDEO_FORMAT_RGB16: + return true; + + case WG_VIDEO_FORMAT_AYUV: + case WG_VIDEO_FORMAT_I420: + case WG_VIDEO_FORMAT_NV12: + case WG_VIDEO_FORMAT_UYVY: + case WG_VIDEO_FORMAT_YUY2: + case WG_VIDEO_FORMAT_YV12: + case WG_VIDEO_FORMAT_YVYU: + case WG_VIDEO_FORMAT_UNKNOWN: + break; + } + + return false; } BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, void *reserved) diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index c9ee6f227e6..3cb57e94384 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -27,8 +27,6 @@ WINE_DEFAULT_DEBUG_CHANNEL(mfplat); -extern const GUID MFVideoFormat_ABGR32; - struct media_stream { IMFMediaStream IMFMediaStream_iface; @@ -38,8 +36,6 @@ struct media_stream IMFMediaEventQueue *event_queue; IMFStreamDescriptor *descriptor; - struct wg_parser_stream *wg_stream; - IUnknown **token_queue; LONG token_queue_count; LONG token_queue_cap; @@ -47,9 +43,6 @@ struct media_stream DWORD stream_id; BOOL active; BOOL eos; - - DWORD busy; - CONDITION_VARIABLE cond; }; enum source_async_op @@ -95,11 +88,17 @@ struct media_source CRITICAL_SECTION cs; + struct wg_source *wg_source; struct wg_parser *wg_parser; + WCHAR mime_type[256]; + UINT64 file_size; + UINT64 duration; + IMFStreamDescriptor **descriptors; struct media_stream **streams; ULONG stream_count; - IMFPresentationDescriptor *pres_desc; + UINT *stream_map; + enum { SOURCE_OPENING, @@ -196,7 +195,7 @@ static const IUnknownVtbl source_async_command_vtbl = source_async_command_Release, }; -static HRESULT source_create_async_op(enum source_async_op op, struct source_async_command **ret) +static HRESULT source_create_async_op(enum source_async_op op, IUnknown **out) { struct source_async_command *command; @@ -204,10 +203,10 @@ static HRESULT source_create_async_op(enum source_async_op op, struct source_asy return E_OUTOFMEMORY; command->IUnknown_iface.lpVtbl = &source_async_command_vtbl; + command->refcount = 1; command->op = op; - *ret = command; - + *out = &command->IUnknown_iface; return S_OK; } @@ -246,28 +245,130 @@ static ULONG WINAPI source_async_commands_callback_Release(IMFAsyncCallback *ifa return IMFMediaSource_Release(&source->IMFMediaSource_iface); } -static IMFStreamDescriptor *stream_descriptor_from_id(IMFPresentationDescriptor *pres_desc, DWORD id, BOOL *selected) +static HRESULT stream_descriptor_get_media_type(IMFStreamDescriptor *descriptor, IMFMediaType **media_type) { - ULONG sd_count; - IMFStreamDescriptor *ret; - unsigned int i; + IMFMediaTypeHandler *handler; + HRESULT hr; + + if (FAILED(hr = IMFStreamDescriptor_GetMediaTypeHandler(descriptor, &handler))) + return hr; + hr = IMFMediaTypeHandler_GetCurrentMediaType(handler, media_type); + IMFMediaTypeHandler_Release(handler); + + return hr; +} + +static HRESULT wg_format_from_stream_descriptor(IMFStreamDescriptor *descriptor, struct wg_format *format) +{ + IMFMediaType *media_type; + HRESULT hr; + + if (FAILED(hr = stream_descriptor_get_media_type(descriptor, &media_type))) + return hr; + mf_media_type_to_wg_format(media_type, format); + IMFMediaType_Release(media_type); + + return hr; +} + +static HRESULT stream_descriptor_create(UINT32 id, struct wg_format *format, IMFStreamDescriptor **out) +{ + IMFStreamDescriptor *descriptor; + IMFMediaTypeHandler *handler; + IMFMediaType *type; + HRESULT hr; + + /* native exposes NV12 video format before I420 */ + if (format->major_type == WG_MAJOR_TYPE_VIDEO + && format->u.video.format == WG_VIDEO_FORMAT_I420) + format->u.video.format = WG_VIDEO_FORMAT_NV12; + + if (!(type = mf_media_type_from_wg_format(format))) + return MF_E_INVALIDMEDIATYPE; + if (FAILED(hr = MFCreateStreamDescriptor(id, 1, &type, &descriptor))) + goto done; + + if (FAILED(hr = IMFStreamDescriptor_GetMediaTypeHandler(descriptor, &handler))) + IMFStreamDescriptor_Release(descriptor); + else + { + hr = IMFMediaTypeHandler_SetCurrentMediaType(handler, type); + IMFMediaTypeHandler_Release(handler); + } + +done: + IMFMediaType_Release(type); + *out = SUCCEEDED(hr) ? descriptor : NULL; + return hr; +} - if (FAILED(IMFPresentationDescriptor_GetStreamDescriptorCount(pres_desc, &sd_count))) - return NULL; +static HRESULT stream_descriptor_set_tag(IMFStreamDescriptor *descriptor, + struct wg_source *source, UINT index, const GUID *attr, enum wg_parser_tag tag) +{ + WCHAR *strW; + HRESULT hr; + DWORD len; + char *str; - for (i = 0; i < sd_count; i++) + if (!(str = wg_source_get_stream_tag(source, index, tag)) + || !(len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0))) + hr = S_OK; + else if (!(strW = malloc(len * sizeof(*strW)))) + hr = E_OUTOFMEMORY; + else { - DWORD stream_id; + if (MultiByteToWideChar(CP_UTF8, 0, str, -1, strW, len)) + hr = IMFStreamDescriptor_SetString(descriptor, attr, strW); + else + hr = E_FAIL; + free(strW); + } + + free(str); + return hr; +} + +static HRESULT map_stream_to_wg_parser_stream(struct media_source *source, UINT stream) +{ + struct wg_parser_stream *wg_stream; + struct wg_format stream_format; + HRESULT hr; + UINT i; + + if (!(wg_stream = wg_parser_get_stream(source->wg_parser, stream))) + return E_FAIL; + wg_parser_stream_get_preferred_format(wg_stream, &stream_format); - if (FAILED(IMFPresentationDescriptor_GetStreamDescriptorByIndex(pres_desc, i, selected, &ret))) - return NULL; + for (i = 0; i < source->stream_count; i++) + { + struct wg_format format; - if (SUCCEEDED(IMFStreamDescriptor_GetStreamIdentifier(ret, &stream_id)) && stream_id == id) - return ret; + if (FAILED(hr = wg_format_from_stream_descriptor(source->descriptors[i], &format))) + return hr; + if (stream_format.major_type != format.major_type) + continue; + if (source->stream_map[i]) + continue; - IMFStreamDescriptor_Release(ret); + TRACE("Mapped stream %u with descriptor %u\n", stream, i); + source->stream_map[i] = stream + 1; + return S_OK; } - return NULL; + + return E_FAIL; +} + +static HRESULT media_stream_get_wg_parser_stream(struct media_stream *stream, struct wg_parser_stream **wg_stream) +{ + struct media_source *source = impl_from_IMFMediaSource(stream->media_source); + HRESULT hr; + DWORD id; + + if (FAILED(hr = IMFStreamDescriptor_GetStreamIdentifier(stream->descriptor, &id))) + return hr; + if (!(id = source->stream_map[id - 1]) || !(*wg_stream = wg_parser_get_stream(source->wg_parser, id - 1))) + return MF_E_INVALIDSTREAMNUMBER; + return S_OK; } static BOOL enqueue_token(struct media_stream *stream, IUnknown *token) @@ -298,15 +399,17 @@ static void flush_token_queue(struct media_stream *stream, BOOL send) { if (send) { + IUnknown *op; HRESULT hr; - struct source_async_command *command; - if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_REQUEST_SAMPLE, &command))) + + if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_REQUEST_SAMPLE, &op))) { + struct source_async_command *command = impl_from_async_command_IUnknown(op); command->u.request_sample.stream = stream; command->u.request_sample.token = stream->token_queue[i]; - hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, - &command->IUnknown_iface); + hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, op); + IUnknown_Release(op); } if (FAILED(hr)) WARN("Could not enqueue sample request, hr %#lx\n", hr); @@ -320,11 +423,67 @@ static void flush_token_queue(struct media_stream *stream, BOOL send) stream->token_queue_cap = 0; } -static void start_pipeline(struct media_source *source, struct source_async_command *command) +static HRESULT media_stream_start(struct media_stream *stream, BOOL active, BOOL seeking, const PROPVARIANT *position) { - PROPVARIANT *position = &command->u.start.position; - BOOL seek_message = source->state != SOURCE_STOPPED && position->vt != VT_EMPTY; - unsigned int i; + struct media_source *source = impl_from_IMFMediaSource(stream->media_source); + struct wg_parser_stream *wg_stream; + struct wg_format format; + HRESULT hr; + + TRACE("source %p, stream %p\n", source, stream); + + if (FAILED(hr = media_stream_get_wg_parser_stream(stream, &wg_stream))) + return hr; + if (FAILED(hr = wg_format_from_stream_descriptor(stream->descriptor, &format))) + WARN("Failed to get wg_format from stream descriptor, hr %#lx\n", hr); + wg_parser_stream_enable(wg_stream, &format, 0); + + if (FAILED(hr = IMFMediaEventQueue_QueueEventParamUnk(source->event_queue, active ? MEUpdatedStream : MENewStream, + &GUID_NULL, S_OK, (IUnknown *)&stream->IMFMediaStream_iface))) + WARN("Failed to send source stream event, hr %#lx\n", hr); + return IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, seeking ? MEStreamSeeked : MEStreamStarted, + &GUID_NULL, S_OK, position); +} + +static DWORD CALLBACK read_thread(void *arg); + +static HRESULT media_source_start(struct media_source *source, IMFPresentationDescriptor *descriptor, + GUID *format, PROPVARIANT *position) +{ + BOOL starting = source->state == SOURCE_STOPPED, seek_message = !starting && position->vt != VT_EMPTY; + IMFStreamDescriptor **descriptors; + DWORD i, count; + HRESULT hr; + + TRACE("source %p, descriptor %p, format %s, position %s\n", source, descriptor, + debugstr_guid(format), wine_dbgstr_variant((VARIANT *)position)); + + if (source->state == SOURCE_SHUTDOWN) + return MF_E_SHUTDOWN; + + if (!source->wg_parser) + { + /* In Media Foundation, sources may read from any media source stream + * without fear of blocking due to buffering limits on another. Trailmakers, + * a Unity3D Engine game, only reads one sample from the audio stream (and + * never deselects it). Remove buffering limits from decodebin in order to + * account for this. Note that this does leak memory, but the same memory + * leak occurs with native. */ + if (!(source->wg_parser = wg_parser_create(WG_PARSER_DECODEBIN, false))) + return E_OUTOFMEMORY; + if (!(source->read_thread = CreateThread(NULL, 0, read_thread, source, 0, NULL))) + return E_OUTOFMEMORY; + if (FAILED(hr = wg_parser_connect(source->wg_parser, source->file_size, NULL))) + return hr; + + /* reset the stream map to map wg_stream numbers instead */ + memset(source->stream_map, 0, source->stream_count * sizeof(*source->stream_map)); + for (i = 0; i < source->stream_count; i++) + { + if (FAILED(hr = map_stream_to_wg_parser_stream(source, i))) + WARN("Failed to map stream %lu, hr %#lx\n", i, hr); + } + } /* seek to beginning on stop->play */ if (source->state == SOURCE_STOPPED && position->vt == VT_EMPTY) @@ -333,78 +492,78 @@ static void start_pipeline(struct media_source *source, struct source_async_comm position->hVal.QuadPart = 0; } - for (i = 0; i < source->stream_count; i++) + if (!(descriptors = calloc(source->stream_count, sizeof(*descriptors)))) + return E_OUTOFMEMORY; + + if (FAILED(hr = IMFPresentationDescriptor_GetStreamDescriptorCount(descriptor, &count))) + WARN("Failed to get presentation descriptor stream count, hr %#lx\n", hr); + + for (i = 0; i < count; i++) { - struct media_stream *stream; - IMFStreamDescriptor *sd; - IMFMediaTypeHandler *mth; - IMFMediaType *current_mt; - DWORD stream_id; - BOOL was_active; + IMFStreamDescriptor *stream_descriptor; BOOL selected; + DWORD id; - stream = source->streams[i]; - - IMFStreamDescriptor_GetStreamIdentifier(stream->descriptor, &stream_id); + if (FAILED(hr = IMFPresentationDescriptor_GetStreamDescriptorByIndex(descriptor, i, &selected, &stream_descriptor))) + WARN("Failed to get presentation stream descriptor, hr %#lx\n", hr); + else if (!selected || FAILED(hr = IMFStreamDescriptor_GetStreamIdentifier(stream_descriptor, &id))) + IMFStreamDescriptor_Release(stream_descriptor); + else + descriptors[id - 1] = stream_descriptor; + } - sd = stream_descriptor_from_id(command->u.start.descriptor, stream_id, &selected); - IMFStreamDescriptor_Release(sd); + source->state = SOURCE_RUNNING; + for (i = 0; i < source->stream_count; i++) + { + struct media_stream *stream = source->streams[i]; + BOOL was_active = !starting && stream->active; + struct wg_parser_stream *wg_stream; - was_active = stream->active; - stream->active = selected; + if (position->vt != VT_EMPTY) + stream->eos = FALSE; - if (selected) + if (!(stream->active = !!descriptors[i])) { - struct wg_format format; - - IMFStreamDescriptor_GetMediaTypeHandler(stream->descriptor, &mth); - IMFMediaTypeHandler_GetCurrentMediaType(mth, ¤t_mt); - - mf_media_type_to_wg_format(current_mt, &format); - wg_parser_stream_enable(stream->wg_stream, &format, 0); - - IMFMediaType_Release(current_mt); - IMFMediaTypeHandler_Release(mth); + if (FAILED(hr = media_stream_get_wg_parser_stream(stream, &wg_stream))) + return hr; + wg_parser_stream_disable(wg_stream); } else { - wg_parser_stream_disable(stream->wg_stream); - } - - if (position->vt != VT_EMPTY) - stream->eos = FALSE; - - if (selected) - { - TRACE("Stream %u (%p) selected\n", i, stream); - IMFMediaEventQueue_QueueEventParamUnk(source->event_queue, - was_active ? MEUpdatedStream : MENewStream, &GUID_NULL, - S_OK, (IUnknown*) &stream->IMFMediaStream_iface); - - IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, - seek_message ? MEStreamSeeked : MEStreamStarted, &GUID_NULL, S_OK, position); + if (FAILED(hr = media_stream_start(stream, was_active, seek_message, position))) + WARN("Failed to start media stream, hr %#lx\n", hr); + IMFStreamDescriptor_Release(descriptors[i]); } } - IMFMediaEventQueue_QueueEventParamVar(source->event_queue, - seek_message ? MESourceSeeked : MESourceStarted, - &GUID_NULL, S_OK, position); + free(descriptors); source->state = SOURCE_RUNNING; if (position->vt == VT_I8) - wg_parser_stream_seek(source->streams[0]->wg_stream, 1.0, position->hVal.QuadPart, 0, + { + struct wg_parser_stream *wg_stream = wg_parser_get_stream(source->wg_parser, 0); + wg_parser_stream_seek(wg_stream, 1.0, position->hVal.QuadPart, 0, AM_SEEKING_AbsolutePositioning, AM_SEEKING_NoPositioning); + } for (i = 0; i < source->stream_count; i++) flush_token_queue(source->streams[i], position->vt == VT_EMPTY); + + return IMFMediaEventQueue_QueueEventParamVar(source->event_queue, + seek_message ? MESourceSeeked : MESourceStarted, &GUID_NULL, S_OK, position); } -static void pause_pipeline(struct media_source *source) +static HRESULT media_source_pause(struct media_source *source) { unsigned int i; HRESULT hr; + TRACE("source %p\n", source); + + if (source->state == SOURCE_SHUTDOWN) + return MF_E_SHUTDOWN; + for (i = 0; i < source->stream_count; i++) { struct media_stream *stream = source->streams[i]; @@ -413,16 +572,20 @@ static void pause_pipeline(struct media_source *source) WARN("Failed to queue MEStreamPaused event, hr %#lx\n", hr); } - IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MESourcePaused, &GUID_NULL, S_OK, NULL); - source->state = SOURCE_PAUSED; + return IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MESourcePaused, &GUID_NULL, S_OK, NULL); } -static void stop_pipeline(struct media_source *source) +static HRESULT media_source_stop(struct media_source *source) { unsigned int i; HRESULT hr; + TRACE("source %p\n", source); + + if (source->state == SOURCE_SHUTDOWN) + return MF_E_SHUTDOWN; + for (i = 0; i < source->stream_count; i++) { struct media_stream *stream = source->streams[i]; @@ -431,131 +594,100 @@ static void stop_pipeline(struct media_source *source) WARN("Failed to queue MEStreamStopped event, hr %#lx\n", hr); } - IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MESourceStopped, &GUID_NULL, S_OK, NULL); - source->state = SOURCE_STOPPED; for (i = 0; i < source->stream_count; i++) flush_token_queue(source->streams[i], FALSE); -} - -static void dispatch_end_of_presentation(struct media_source *source) -{ - PROPVARIANT empty = {.vt = VT_EMPTY}; - unsigned int i; - - /* A stream has ended, check whether all have */ - for (i = 0; i < source->stream_count; i++) - { - struct media_stream *stream = source->streams[i]; - if (stream->active && !stream->eos) - return; - } - IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MEEndOfPresentation, &GUID_NULL, S_OK, &empty); + return IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MESourceStopped, &GUID_NULL, S_OK, NULL); } -static void send_buffer(struct media_stream *stream, const struct wg_parser_buffer *wg_buffer, IUnknown *token) +static HRESULT media_stream_send_sample(struct media_stream *stream, struct wg_parser_stream *wg_stream, + const struct wg_parser_buffer *wg_buffer, IUnknown *token) { + IMFSample *sample = NULL; IMFMediaBuffer *buffer; - IMFSample *sample; HRESULT hr; BYTE *data; - if (FAILED(hr = MFCreateSample(&sample))) - { - ERR("Failed to create sample, hr %#lx.\n", hr); - return; - } - if (FAILED(hr = MFCreateMemoryBuffer(wg_buffer->size, &buffer))) - { - ERR("Failed to create buffer, hr %#lx.\n", hr); - IMFSample_Release(sample); - return; - } - - if (FAILED(hr = IMFSample_AddBuffer(sample, buffer))) - { - ERR("Failed to add buffer, hr %#lx.\n", hr); - goto out; - } - + return hr; if (FAILED(hr = IMFMediaBuffer_SetCurrentLength(buffer, wg_buffer->size))) - { - ERR("Failed to set size, hr %#lx.\n", hr); goto out; - } - if (FAILED(hr = IMFMediaBuffer_Lock(buffer, &data, NULL, NULL))) - { - ERR("Failed to lock buffer, hr %#lx.\n", hr); goto out; - } - if (!wg_parser_stream_copy_buffer(stream->wg_stream, data, 0, wg_buffer->size)) + if (!wg_parser_stream_copy_buffer(wg_stream, data, 0, wg_buffer->size)) { - wg_parser_stream_release_buffer(stream->wg_stream); + wg_parser_stream_release_buffer(wg_stream); IMFMediaBuffer_Unlock(buffer); goto out; } - wg_parser_stream_release_buffer(stream->wg_stream); + wg_parser_stream_release_buffer(wg_stream); if (FAILED(hr = IMFMediaBuffer_Unlock(buffer))) - { - ERR("Failed to unlock buffer, hr %#lx.\n", hr); goto out; - } + if (FAILED(hr = MFCreateSample(&sample))) + goto out; + if (FAILED(hr = IMFSample_AddBuffer(sample, buffer))) + goto out; if (FAILED(hr = IMFSample_SetSampleTime(sample, wg_buffer->pts))) - { - ERR("Failed to set sample time, hr %#lx.\n", hr); goto out; - } - if (FAILED(hr = IMFSample_SetSampleDuration(sample, wg_buffer->duration))) - { - ERR("Failed to set sample duration, hr %#lx.\n", hr); goto out; - } - - if (token) - IMFSample_SetUnknown(sample, &MFSampleExtension_Token, token); + if (token && FAILED(hr = IMFSample_SetUnknown(sample, &MFSampleExtension_Token, token))) + goto out; - IMFMediaEventQueue_QueueEventParamUnk(stream->event_queue, MEMediaSample, + hr = IMFMediaEventQueue_QueueEventParamUnk(stream->event_queue, MEMediaSample, &GUID_NULL, S_OK, (IUnknown *)sample); out: + if (sample) + IMFSample_Release(sample); IMFMediaBuffer_Release(buffer); - IMFSample_Release(sample); + return hr; +} + +static HRESULT media_stream_send_eos(struct media_source *source, struct media_stream *stream) +{ + PROPVARIANT empty = {.vt = VT_EMPTY}; + HRESULT hr; + UINT i; + + TRACE("source %p, stream %p\n", source, stream); + + stream->eos = TRUE; + if (FAILED(hr = IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, MEEndOfStream, &GUID_NULL, S_OK, &empty))) + WARN("Failed to queue MEEndOfStream event, hr %#lx\n", hr); + + for (i = 0; i < source->stream_count; i++) + { + struct media_stream *stream = source->streams[i]; + if (stream->active && !stream->eos) + return S_OK; + } + + if (FAILED(hr = IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MEEndOfPresentation, &GUID_NULL, S_OK, &empty))) + WARN("Failed to queue MEEndOfPresentation event, hr %#lx\n", hr); + return S_OK; } -static void wait_on_sample(struct media_stream *stream, IUnknown *token) +static HRESULT wait_on_sample(struct media_stream *stream, IUnknown *token) { struct media_source *source = impl_from_IMFMediaSource(stream->media_source); - PROPVARIANT empty_var = {.vt = VT_EMPTY}; + struct wg_parser_stream *wg_stream; struct wg_parser_buffer buffer; - BOOL ret; + HRESULT hr; TRACE("%p, %p\n", stream, token); - stream->busy = TRUE; - LeaveCriticalSection(&source->cs); - ret = wg_parser_stream_get_buffer(source->wg_parser, stream->wg_stream, &buffer); - EnterCriticalSection(&source->cs); - stream->busy = FALSE; - WakeConditionVariable(&stream->cond); + if (FAILED(hr = media_stream_get_wg_parser_stream(stream, &wg_stream))) + return hr; + if (wg_parser_stream_get_buffer(source->wg_parser, wg_stream, &buffer)) + return media_stream_send_sample(stream, wg_stream, &buffer, token); - if (source->state == SOURCE_SHUTDOWN) - WARN("media source has been shutdown, returning\n"); - else if (ret) - send_buffer(stream, &buffer, token); - else - { - stream->eos = TRUE; - IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, MEEndOfStream, &GUID_NULL, S_OK, &empty_var); - dispatch_end_of_presentation(source); - } + return media_stream_send_eos(source, stream); } static HRESULT WINAPI source_async_commands_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) @@ -574,22 +706,31 @@ static HRESULT WINAPI source_async_commands_Invoke(IMFAsyncCallback *iface, IMFA switch (command->op) { case SOURCE_ASYNC_START: - if (source->state != SOURCE_SHUTDOWN) - start_pipeline(source, command); + { + IMFPresentationDescriptor *descriptor = command->u.start.descriptor; + GUID format = command->u.start.format; + PROPVARIANT position = command->u.start.position; + + if (FAILED(hr = media_source_start(source, descriptor, &format, &position))) + WARN("Failed to start source %p, hr %#lx\n", source, hr); break; + } case SOURCE_ASYNC_PAUSE: - if (source->state != SOURCE_SHUTDOWN) - pause_pipeline(source); + if (FAILED(hr = media_source_pause(source))) + WARN("Failed to pause source %p, hr %#lx\n", source, hr); break; case SOURCE_ASYNC_STOP: - if (source->state != SOURCE_SHUTDOWN) - stop_pipeline(source); + if (FAILED(hr = media_source_stop(source))) + WARN("Failed to stop source %p, hr %#lx\n", source, hr); break; case SOURCE_ASYNC_REQUEST_SAMPLE: if (source->state == SOURCE_PAUSED) enqueue_token(command->u.request_sample.stream, command->u.request_sample.token); else if (source->state == SOURCE_RUNNING) - wait_on_sample(command->u.request_sample.stream, command->u.request_sample.token); + { + if (FAILED(hr = wait_on_sample(command->u.request_sample.stream, command->u.request_sample.token))) + WARN("Failed to request sample, hr %#lx\n", hr); + } break; } @@ -808,7 +949,7 @@ static HRESULT WINAPI media_stream_RequestSample(IMFMediaStream *iface, IUnknown { struct media_stream *stream = impl_from_IMFMediaStream(iface); struct media_source *source = impl_from_IMFMediaSource(stream->media_source); - struct source_async_command *command; + IUnknown *op; HRESULT hr; TRACE("%p, %p.\n", iface, token); @@ -821,14 +962,16 @@ static HRESULT WINAPI media_stream_RequestSample(IMFMediaStream *iface, IUnknown hr = MF_E_MEDIA_SOURCE_WRONGSTATE; else if (stream->eos) hr = MF_E_END_OF_STREAM; - else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_REQUEST_SAMPLE, &command))) + else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_REQUEST_SAMPLE, &op))) { + struct source_async_command *command = impl_from_async_command_IUnknown(op); command->u.request_sample.stream = stream; if (token) IUnknown_AddRef(token); command->u.request_sample.token = token; - hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, &command->IUnknown_iface); + hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, op); + IUnknown_Release(op); } LeaveCriticalSection(&source->cs); @@ -850,14 +993,13 @@ static const IMFMediaStreamVtbl media_stream_vtbl = media_stream_RequestSample }; -static HRESULT media_stream_create(IMFMediaSource *source, DWORD id, +static HRESULT media_stream_create(IMFMediaSource *source, IMFStreamDescriptor *descriptor, struct media_stream **out) { - struct wg_parser *wg_parser = impl_from_IMFMediaSource(source)->wg_parser; struct media_stream *object; HRESULT hr; - TRACE("source %p, id %lu.\n", source, id); + TRACE("source %p, descriptor %p.\n", source, descriptor); if (!(object = calloc(1, sizeof(*object)))) return E_OUTOFMEMORY; @@ -873,11 +1015,8 @@ static HRESULT media_stream_create(IMFMediaSource *source, DWORD id, IMFMediaSource_AddRef(source); object->media_source = source; - object->stream_id = id; - - object->active = FALSE; - object->eos = FALSE; - object->wg_stream = wg_parser_get_stream(wg_parser, id); + IMFStreamDescriptor_AddRef(descriptor); + object->descriptor = descriptor; TRACE("Created stream object %p.\n", object); @@ -885,168 +1024,41 @@ static HRESULT media_stream_create(IMFMediaSource *source, DWORD id, return S_OK; } -static HRESULT media_stream_init_desc(struct media_stream *stream) +static HRESULT WINAPI media_source_get_service_QueryInterface(IMFGetService *iface, REFIID riid, void **obj) { - IMFMediaTypeHandler *type_handler = NULL; - IMFMediaType *stream_types[9]; - struct wg_format format; - DWORD type_count = 0; - HRESULT hr = S_OK; - unsigned int i; - - wg_parser_stream_get_preferred_format(stream->wg_stream, &format); - - if (format.major_type == WG_MAJOR_TYPE_VIDEO) - { - /* These are the most common native output types of decoders: - https://docs.microsoft.com/en-us/windows/win32/medfound/mft-decoder-expose-output-types-in-native-order */ - static const GUID *const video_types[] = - { - &MFVideoFormat_NV12, - &MFVideoFormat_YV12, - &MFVideoFormat_YUY2, - &MFVideoFormat_IYUV, - &MFVideoFormat_I420, - &MFVideoFormat_ARGB32, - &MFVideoFormat_RGB32, - &MFVideoFormat_ABGR32, - }; - - IMFMediaType *base_type = mf_media_type_from_wg_format(&format); - GUID base_subtype; - - if (!base_type) - { - hr = MF_E_INVALIDMEDIATYPE; - goto done; - } - - IMFMediaType_SetUINT32(base_type, &MF_MT_VIDEO_NOMINAL_RANGE, MFNominalRange_Normal); + struct media_source *source = impl_from_IMFGetService(iface); + return IMFMediaSource_QueryInterface(&source->IMFMediaSource_iface, riid, obj); +} - IMFMediaType_GetGUID(base_type, &MF_MT_SUBTYPE, &base_subtype); +static ULONG WINAPI media_source_get_service_AddRef(IMFGetService *iface) +{ + struct media_source *source = impl_from_IMFGetService(iface); + return IMFMediaSource_AddRef(&source->IMFMediaSource_iface); +} - stream_types[0] = base_type; - type_count = 1; +static ULONG WINAPI media_source_get_service_Release(IMFGetService *iface) +{ + struct media_source *source = impl_from_IMFGetService(iface); + return IMFMediaSource_Release(&source->IMFMediaSource_iface); +} - for (i = 0; i < ARRAY_SIZE(video_types); i++) - { - IMFMediaType *new_type; +static HRESULT WINAPI media_source_get_service_GetService(IMFGetService *iface, REFGUID service, REFIID riid, void **obj) +{ + struct media_source *source = impl_from_IMFGetService(iface); - if (IsEqualGUID(&base_subtype, video_types[i])) - continue; + TRACE("%p, %s, %s, %p.\n", iface, debugstr_guid(service), debugstr_guid(riid), obj); - if (FAILED(hr = MFCreateMediaType(&new_type))) - goto done; - stream_types[type_count++] = new_type; + *obj = NULL; - if (FAILED(hr = IMFMediaType_CopyAllItems(base_type, (IMFAttributes *) new_type))) - goto done; - if (FAILED(hr = IMFMediaType_SetGUID(new_type, &MF_MT_SUBTYPE, video_types[i]))) - goto done; - } - } - else if (format.major_type == WG_MAJOR_TYPE_AUDIO) + if (IsEqualGUID(service, &MF_RATE_CONTROL_SERVICE)) { - /* Expose at least one PCM and one floating point type for the - consumer to pick from. Moreover, ensure that we expose S16LE first, - as games such as MGSV expect the native media type to be 16 bps. */ - static const enum wg_audio_format audio_types[] = + if (IsEqualIID(riid, &IID_IMFRateSupport)) { - WG_AUDIO_FORMAT_S16LE, - WG_AUDIO_FORMAT_F32LE, - }; - - BOOL has_native_format = FALSE; - - for (i = 0; i < ARRAY_SIZE(audio_types); i++) + *obj = &source->IMFRateSupport_iface; + } + else if (IsEqualIID(riid, &IID_IMFRateControl)) { - struct wg_format new_format; - - new_format = format; - new_format.u.audio.format = audio_types[i]; - if ((stream_types[type_count] = mf_media_type_from_wg_format(&new_format))) - { - if (format.u.audio.format == audio_types[i]) - has_native_format = TRUE; - type_count++; - } - } - - if (!has_native_format && (stream_types[type_count] = mf_media_type_from_wg_format(&format))) - type_count++; - } - else - { - if ((stream_types[0] = mf_media_type_from_wg_format(&format))) - type_count = 1; - } - - assert(type_count <= ARRAY_SIZE(stream_types)); - - if (!type_count) - { - ERR("Failed to establish an IMFMediaType from any of the possible stream caps!\n"); - return E_FAIL; - } - - if (FAILED(hr = MFCreateStreamDescriptor(stream->stream_id, type_count, stream_types, &stream->descriptor))) - goto done; - - if (FAILED(hr = IMFStreamDescriptor_GetMediaTypeHandler(stream->descriptor, &type_handler))) - { - IMFStreamDescriptor_Release(stream->descriptor); - goto done; - } - - if (FAILED(hr = IMFMediaTypeHandler_SetCurrentMediaType(type_handler, stream_types[0]))) - { - IMFStreamDescriptor_Release(stream->descriptor); - goto done; - } - -done: - if (type_handler) - IMFMediaTypeHandler_Release(type_handler); - for (i = 0; i < type_count; i++) - IMFMediaType_Release(stream_types[i]); - return hr; -} - -static HRESULT WINAPI media_source_get_service_QueryInterface(IMFGetService *iface, REFIID riid, void **obj) -{ - struct media_source *source = impl_from_IMFGetService(iface); - return IMFMediaSource_QueryInterface(&source->IMFMediaSource_iface, riid, obj); -} - -static ULONG WINAPI media_source_get_service_AddRef(IMFGetService *iface) -{ - struct media_source *source = impl_from_IMFGetService(iface); - return IMFMediaSource_AddRef(&source->IMFMediaSource_iface); -} - -static ULONG WINAPI media_source_get_service_Release(IMFGetService *iface) -{ - struct media_source *source = impl_from_IMFGetService(iface); - return IMFMediaSource_Release(&source->IMFMediaSource_iface); -} - -static HRESULT WINAPI media_source_get_service_GetService(IMFGetService *iface, REFGUID service, REFIID riid, void **obj) -{ - struct media_source *source = impl_from_IMFGetService(iface); - - TRACE("%p, %s, %s, %p.\n", iface, debugstr_guid(service), debugstr_guid(riid), obj); - - *obj = NULL; - - if (IsEqualGUID(service, &MF_RATE_CONTROL_SERVICE)) - { - if (IsEqualIID(riid, &IID_IMFRateSupport)) - { - *obj = &source->IMFRateSupport_iface; - } - else if (IsEqualIID(riid, &IID_IMFRateControl)) - { - *obj = &source->IMFRateControl_iface; + *obj = &source->IMFRateControl_iface; } } else @@ -1236,11 +1248,11 @@ static ULONG WINAPI media_source_Release(IMFMediaSource *iface) if (!ref) { IMFMediaSource_Shutdown(iface); - MFUnlockWorkQueue(source->async_commands_queue); - IMFPresentationDescriptor_Release(source->pres_desc); IMFMediaEventQueue_Release(source->event_queue); IMFByteStream_Release(source->byte_stream); - wg_parser_destroy(source->wg_parser); + wg_source_destroy(source->wg_source); + if (source->wg_parser) + wg_parser_destroy(source->wg_parser); source->cs.DebugInfo->Spare[0] = 0; DeleteCriticalSection(&source->cs); free(source); @@ -1309,6 +1321,7 @@ static HRESULT WINAPI media_source_CreatePresentationDescriptor(IMFMediaSource * { struct media_source *source = impl_from_IMFMediaSource(iface); HRESULT hr; + UINT i; TRACE("%p, %p.\n", iface, descriptor); @@ -1316,8 +1329,25 @@ static HRESULT WINAPI media_source_CreatePresentationDescriptor(IMFMediaSource * if (source->state == SOURCE_SHUTDOWN) hr = MF_E_SHUTDOWN; - else - hr = IMFPresentationDescriptor_Clone(source->pres_desc, descriptor); + else if (SUCCEEDED(hr = MFCreatePresentationDescriptor(source->stream_count, source->descriptors, descriptor))) + { + if (FAILED(hr = IMFPresentationDescriptor_SetString(*descriptor, &MF_PD_MIME_TYPE, source->mime_type))) + WARN("Failed to set presentation descriptor MF_PD_MIME_TYPE, hr %#lx\n", hr); + if (FAILED(hr = IMFPresentationDescriptor_SetUINT64(*descriptor, &MF_PD_TOTAL_FILE_SIZE, source->file_size))) + WARN("Failed to set presentation descriptor MF_PD_TOTAL_FILE_SIZE, hr %#lx\n", hr); + if (FAILED(hr = IMFPresentationDescriptor_SetUINT64(*descriptor, &MF_PD_DURATION, source->duration))) + WARN("Failed to set presentation descriptor MF_PD_DURATION, hr %#lx\n", hr); + + for (i = 0; i < source->stream_count; ++i) + { + if (!source->streams[i]->active) + continue; + if (FAILED(hr = IMFPresentationDescriptor_SelectStream(*descriptor, i))) + WARN("Failed to select stream %u, hr %#lx\n", i, hr); + } + + hr = S_OK; + } LeaveCriticalSection(&source->cs); @@ -1328,7 +1358,7 @@ static HRESULT WINAPI media_source_Start(IMFMediaSource *iface, IMFPresentationD const GUID *time_format, const PROPVARIANT *position) { struct media_source *source = impl_from_IMFMediaSource(iface); - struct source_async_command *command; + IUnknown *op; HRESULT hr; TRACE("%p, %p, %p, %p.\n", iface, descriptor, time_format, position); @@ -1339,13 +1369,15 @@ static HRESULT WINAPI media_source_Start(IMFMediaSource *iface, IMFPresentationD hr = MF_E_SHUTDOWN; else if (!(IsEqualIID(time_format, &GUID_NULL))) hr = MF_E_UNSUPPORTED_TIME_FORMAT; - else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_START, &command))) + else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_START, &op))) { + struct source_async_command *command = impl_from_async_command_IUnknown(op); command->u.start.descriptor = descriptor; command->u.start.format = *time_format; PropVariantCopy(&command->u.start.position, position); - hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, &command->IUnknown_iface); + hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, op); + IUnknown_Release(op); } LeaveCriticalSection(&source->cs); @@ -1356,7 +1388,7 @@ static HRESULT WINAPI media_source_Start(IMFMediaSource *iface, IMFPresentationD static HRESULT WINAPI media_source_Stop(IMFMediaSource *iface) { struct media_source *source = impl_from_IMFMediaSource(iface); - struct source_async_command *command; + IUnknown *op; HRESULT hr; TRACE("%p.\n", iface); @@ -1365,8 +1397,11 @@ static HRESULT WINAPI media_source_Stop(IMFMediaSource *iface) if (source->state == SOURCE_SHUTDOWN) hr = MF_E_SHUTDOWN; - else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_STOP, &command))) - hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, &command->IUnknown_iface); + else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_STOP, &op))) + { + hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, op); + IUnknown_Release(op); + } LeaveCriticalSection(&source->cs); @@ -1376,7 +1411,7 @@ static HRESULT WINAPI media_source_Stop(IMFMediaSource *iface) static HRESULT WINAPI media_source_Pause(IMFMediaSource *iface) { struct media_source *source = impl_from_IMFMediaSource(iface); - struct source_async_command *command; + IUnknown *op; HRESULT hr; TRACE("%p.\n", iface); @@ -1387,9 +1422,11 @@ static HRESULT WINAPI media_source_Pause(IMFMediaSource *iface) hr = MF_E_SHUTDOWN; else if (source->state != SOURCE_RUNNING) hr = MF_E_INVALID_STATE_TRANSITION; - else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_PAUSE, &command))) - hr = MFPutWorkItem(source->async_commands_queue, - &source->async_commands_callback, &command->IUnknown_iface); + else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_PAUSE, &op))) + { + hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, op); + IUnknown_Release(op); + } LeaveCriticalSection(&source->cs); @@ -1399,7 +1436,6 @@ static HRESULT WINAPI media_source_Pause(IMFMediaSource *iface) static HRESULT WINAPI media_source_Shutdown(IMFMediaSource *iface) { struct media_source *source = impl_from_IMFMediaSource(iface); - UINT i; TRACE("%p.\n", iface); @@ -1413,15 +1449,8 @@ static HRESULT WINAPI media_source_Shutdown(IMFMediaSource *iface) source->state = SOURCE_SHUTDOWN; - for (i = 0; i < source->stream_count; i++) - { - struct media_stream *stream = source->streams[i]; - wg_parser_stream_disable(stream->wg_stream); - while (stream->busy) - SleepConditionVariableCS(&stream->cond, &source->cs, INFINITE); - } - - wg_parser_disconnect(source->wg_parser); + if (source->wg_parser) + wg_parser_disconnect(source->wg_parser); source->read_thread_shutdown = true; WaitForSingleObject(source->read_thread, INFINITE); @@ -1433,11 +1462,16 @@ static HRESULT WINAPI media_source_Shutdown(IMFMediaSource *iface) while (source->stream_count--) { struct media_stream *stream = source->streams[source->stream_count]; + IMFStreamDescriptor_Release(source->descriptors[source->stream_count]); IMFMediaEventQueue_Shutdown(stream->event_queue); IMFMediaStream_Release(&stream->IMFMediaStream_iface); } + free(source->stream_map); + free(source->descriptors); free(source->streams); + MFUnlockWorkQueue(source->async_commands_queue); + LeaveCriticalSection(&source->cs); return S_OK; @@ -1460,16 +1494,115 @@ static const IMFMediaSourceVtbl IMFMediaSource_vtbl = media_source_Shutdown, }; -static HRESULT media_source_constructor(IMFByteStream *bytestream, const WCHAR *uri, struct media_source **out_media_source) +static void media_source_init_stream_map(struct media_source *source, UINT stream_count) +{ + struct wg_format format; + int i, n = 0; + + if (wcscmp(source->mime_type, L"video/mp4")) + { + for (i = stream_count - 1; i >= 0; i--) + { + TRACE("mapping stream %u to wg_source stream %u\n", i, i); + source->stream_map[i] = i; + } + return; + } + + for (i = stream_count - 1; i >= 0; i--) + { + wg_source_get_stream_format(source->wg_source, i, &format); + if (format.major_type == WG_MAJOR_TYPE_UNKNOWN) continue; + if (format.major_type >= WG_MAJOR_TYPE_VIDEO) continue; + TRACE("mapping stream %u to wg_source stream %u\n", n, i); + source->stream_map[n++] = i; + } + for (i = stream_count - 1; i >= 0; i--) + { + wg_source_get_stream_format(source->wg_source, i, &format); + if (format.major_type == WG_MAJOR_TYPE_UNKNOWN) continue; + if (format.major_type < WG_MAJOR_TYPE_VIDEO) continue; + TRACE("mapping stream %u to wg_source stream %u\n", n, i); + source->stream_map[n++] = i; + } + for (i = stream_count - 1; i >= 0; i--) + { + wg_source_get_stream_format(source->wg_source, i, &format); + if (format.major_type != WG_MAJOR_TYPE_UNKNOWN) continue; + TRACE("mapping stream %u to wg_source stream %u\n", n, i); + source->stream_map[n++] = i; + } +} + +static void media_source_init_descriptors(struct media_source *source) +{ + UINT i, last_audio = -1, last_video = -1, first_audio = -1, first_video = -1; + HRESULT hr; + + for (i = 0; i < source->stream_count; i++) + { + IMFStreamDescriptor *descriptor = source->descriptors[i]; + struct wg_format format = {0}; + UINT exclude = -1; + + if (FAILED(hr = wg_format_from_stream_descriptor(descriptor, &format))) + WARN("Failed to get format from stream descriptor, hr %#lx\n", hr); + + if (format.major_type == WG_MAJOR_TYPE_AUDIO) + { + if (first_audio == -1) + first_audio = i; + exclude = last_audio; + last_audio = i; + } + else if (format.major_type == WG_MAJOR_TYPE_VIDEO) + { + if (first_video == -1) + first_video = i; + exclude = last_video; + last_video = i; + } + + if (exclude != -1) + { + if (FAILED(IMFStreamDescriptor_SetUINT32(source->descriptors[exclude], &MF_SD_MUTUALLY_EXCLUSIVE, 1))) + WARN("Failed to set stream %u MF_SD_MUTUALLY_EXCLUSIVE\n", exclude); + else if (FAILED(IMFStreamDescriptor_SetUINT32(descriptor, &MF_SD_MUTUALLY_EXCLUSIVE, 1))) + WARN("Failed to set stream %u MF_SD_MUTUALLY_EXCLUSIVE\n", i); + } + + if (FAILED(hr = stream_descriptor_set_tag(descriptor, source->wg_source, source->stream_map[i], + &MF_SD_LANGUAGE, WG_PARSER_TAG_LANGUAGE))) + WARN("Failed to set stream descriptor language, hr %#lx\n", hr); + if (FAILED(hr = stream_descriptor_set_tag(descriptor, source->wg_source, source->stream_map[i], + &MF_SD_STREAM_NAME, WG_PARSER_TAG_NAME))) + WARN("Failed to set stream descriptor name, hr %#lx\n", hr); + } + + if (!wcscmp(source->mime_type, L"video/mp4")) + { + if (last_audio != -1) + source->streams[last_audio]->active = TRUE; + if (last_video != -1) + source->streams[last_video]->active = TRUE; + } + else + { + if (first_audio != -1) + source->streams[first_audio]->active = TRUE; + if (first_video != -1) + source->streams[first_video]->active = TRUE; + } +} + +HRESULT media_source_create(IMFByteStream *bytestream, const WCHAR *url, BYTE *data, UINT64 size, IMFMediaSource **out) { - BOOL video_selected = FALSE, audio_selected = FALSE; - IMFStreamDescriptor **descriptors = NULL; - unsigned int stream_count = UINT_MAX; + UINT64 duration, next_offset, file_size; + UINT32 stream_count; struct media_source *object; - UINT64 total_pres_time = 0; - struct wg_parser *parser; - DWORD bytestream_caps; - uint64_t file_size; + struct wg_source *wg_source; + DWORD bytestream_caps, read_size = size; + WCHAR mime_type[256]; unsigned int i; HRESULT hr; @@ -1488,8 +1621,30 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, const WCHAR * return hr; } + if (!(wg_source = wg_source_create(url, file_size, data, size, mime_type))) + return MF_E_UNSUPPORTED_FORMAT; + + while (SUCCEEDED(hr) && SUCCEEDED(hr = wg_source_push_data(wg_source, data, read_size)) + && wg_source_get_status(wg_source, &stream_count, &duration, &next_offset) + && !stream_count && (read_size = min(file_size - min(file_size, next_offset), size))) + { + if (FAILED(hr = IMFByteStream_SetCurrentPosition(bytestream, next_offset))) + WARN("Failed to seek stream to %#I64x, hr %#lx\n", next_offset, hr); + else if (FAILED(hr = IMFByteStream_Read(bytestream, data, read_size, &read_size))) + WARN("Failed to read %#lx bytes from stream, hr %#lx\n", read_size, hr); + } + + if (!stream_count) + { + wg_source_destroy(wg_source); + return MF_E_UNSUPPORTED_FORMAT; + } + if (!(object = calloc(1, sizeof(*object)))) + { + wg_source_destroy(wg_source); return E_OUTOFMEMORY; + } object->IMFMediaSource_iface.lpVtbl = &IMFMediaSource_vtbl; object->IMFGetService_iface.lpVtbl = &media_source_get_service_vtbl; @@ -1497,8 +1652,12 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, const WCHAR * object->IMFRateControl_iface.lpVtbl = &media_source_rate_control_vtbl; object->async_commands_callback.lpVtbl = &source_async_commands_callback_vtbl; object->ref = 1; - object->byte_stream = bytestream; IMFByteStream_AddRef(bytestream); + object->byte_stream = bytestream; + object->wg_source = wg_source; + wcscpy(object->mime_type, mime_type); + object->file_size = file_size; + object->duration = duration; object->rate = 1.0f; InitializeCriticalSection(&object->cs); object->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": cs"); @@ -1509,618 +1668,64 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, const WCHAR * if (FAILED(hr = MFAllocateWorkQueue(&object->async_commands_queue))) goto fail; - if (!(parser = wg_parser_create(uri ? WG_PARSER_URIDECODEBIN : WG_PARSER_DECODEBIN, false))) + if (!(object->descriptors = calloc(stream_count, sizeof(*object->descriptors))) + || !(object->stream_map = calloc(stream_count, sizeof(*object->stream_map))) + || !(object->streams = calloc(stream_count, sizeof(*object->streams)))) { + free(object->stream_map); + free(object->descriptors); hr = E_OUTOFMEMORY; goto fail; } - object->wg_parser = parser; - - object->read_thread = CreateThread(NULL, 0, read_thread, object, 0, NULL); - - object->state = SOURCE_OPENING; - - if (FAILED(hr = wg_parser_connect(parser, file_size, uri))) - goto fail; - stream_count = wg_parser_get_stream_count(parser); - - if (!(object->streams = calloc(stream_count, sizeof(*object->streams)))) - { - hr = E_OUTOFMEMORY; - goto fail; - } + media_source_init_stream_map(object, stream_count); for (i = 0; i < stream_count; ++i) { - if (FAILED(hr = media_stream_create(&object->IMFMediaSource_iface, i, &object->streams[i]))) - goto fail; + IMFStreamDescriptor *descriptor; + struct media_stream *stream; + struct wg_format format; - if (FAILED(hr = media_stream_init_desc(object->streams[i]))) + wg_source_get_stream_format(wg_source, object->stream_map[i], &format); + if (FAILED(hr = stream_descriptor_create(i + 1, &format, &descriptor))) + goto fail; + if (FAILED(hr = media_stream_create(&object->IMFMediaSource_iface, descriptor, &stream))) { - ERR("Failed to finish initialization of media stream %p, hr %#lx.\n", object->streams[i], hr); - IMFMediaSource_Release(object->streams[i]->media_source); - IMFMediaEventQueue_Release(object->streams[i]->event_queue); - free(object->streams[i]); + IMFStreamDescriptor_Release(descriptor); goto fail; } + IMFStreamDescriptor_AddRef(descriptor); + object->descriptors[i] = descriptor; + object->streams[i] = stream; object->stream_count++; } - /* init presentation descriptor */ - - descriptors = malloc(object->stream_count * sizeof(IMFStreamDescriptor *)); - for (i = 0; i < object->stream_count; i++) - { - static const struct - { - enum wg_parser_tag tag; - const GUID *mf_attr; - } - tags[] = - { - {WG_PARSER_TAG_LANGUAGE, &MF_SD_LANGUAGE}, - {WG_PARSER_TAG_NAME, &MF_SD_STREAM_NAME}, - }; - IMFStreamDescriptor **descriptor = descriptors + object->stream_count - 1 - i; - unsigned int j; - WCHAR *strW; - DWORD len; - char *str; - - IMFMediaStream_GetStreamDescriptor(&object->streams[i]->IMFMediaStream_iface, descriptor); - - for (j = 0; j < ARRAY_SIZE(tags); ++j) - { - if (!(str = wg_parser_stream_get_tag(object->streams[i]->wg_stream, tags[j].tag))) - continue; - if (!(len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0))) - { - free(str); - continue; - } - strW = malloc(len * sizeof(*strW)); - if (MultiByteToWideChar(CP_UTF8, 0, str, -1, strW, len)) - IMFStreamDescriptor_SetString(*descriptor, tags[j].mf_attr, strW); - free(strW); - free(str); - } - } - - if (FAILED(hr = MFCreatePresentationDescriptor(object->stream_count, descriptors, &object->pres_desc))) - goto fail; - - /* Select one of each major type. */ - for (i = 0; i < object->stream_count; i++) - { - IMFMediaTypeHandler *handler; - GUID major_type; - BOOL select_stream = FALSE; - - IMFStreamDescriptor_GetMediaTypeHandler(descriptors[i], &handler); - IMFMediaTypeHandler_GetMajorType(handler, &major_type); - if (IsEqualGUID(&major_type, &MFMediaType_Video) && !video_selected) - { - select_stream = TRUE; - video_selected = TRUE; - } - if (IsEqualGUID(&major_type, &MFMediaType_Audio) && !audio_selected) - { - select_stream = TRUE; - audio_selected = TRUE; - } - if (select_stream) - IMFPresentationDescriptor_SelectStream(object->pres_desc, i); - IMFMediaTypeHandler_Release(handler); - IMFStreamDescriptor_Release(descriptors[i]); - } - free(descriptors); - descriptors = NULL; - - for (i = 0; i < object->stream_count; i++) - total_pres_time = max(total_pres_time, - wg_parser_stream_get_duration(object->streams[i]->wg_stream)); - - if (object->stream_count) - IMFPresentationDescriptor_SetUINT64(object->pres_desc, &MF_PD_DURATION, total_pres_time); - + media_source_init_descriptors(object); object->state = SOURCE_STOPPED; - *out_media_source = object; + *out = &object->IMFMediaSource_iface; return S_OK; - fail: +fail: WARN("Failed to construct MFMediaSource, hr %#lx.\n", hr); - if (descriptors) - { - for (i = 0; i < object->stream_count; i++) - IMFStreamDescriptor_Release(descriptors[i]); - free(descriptors); - } - while (object->streams && object->stream_count--) { struct media_stream *stream = object->streams[object->stream_count]; + IMFStreamDescriptor_Release(object->descriptors[object->stream_count]); IMFMediaStream_Release(&stream->IMFMediaStream_iface); } + free(object->stream_map); + free(object->descriptors); free(object->streams); - if (stream_count != UINT_MAX) - wg_parser_disconnect(object->wg_parser); - if (object->read_thread) - { - object->read_thread_shutdown = true; - WaitForSingleObject(object->read_thread, INFINITE); - CloseHandle(object->read_thread); - } - if (object->wg_parser) - wg_parser_destroy(object->wg_parser); if (object->async_commands_queue) MFUnlockWorkQueue(object->async_commands_queue); if (object->event_queue) IMFMediaEventQueue_Release(object->event_queue); IMFByteStream_Release(object->byte_stream); + wg_source_destroy(wg_source); free(object); return hr; } - -HRESULT winegstreamer_create_media_source_from_uri(const WCHAR *uri, IUnknown **out_object) -{ - struct media_source *object; - IMFByteStream *bytestream; - IStream *stream; - HRESULT hr; - - if (FAILED(hr = CreateStreamOnHGlobal(0, TRUE, &stream))) - return hr; - - hr = MFCreateMFByteStreamOnStream(stream, &bytestream); - IStream_Release(stream); - if (FAILED(hr)) - return hr; - - if (SUCCEEDED(hr = media_source_constructor(bytestream, uri, &object))) - *out_object = (IUnknown*)&object->IMFMediaSource_iface; - - IMFByteStream_Release(bytestream); - return hr; -} - -struct winegstreamer_stream_handler_result -{ - struct list entry; - IMFAsyncResult *result; - MF_OBJECT_TYPE obj_type; - IUnknown *object; -}; - -struct winegstreamer_stream_handler -{ - IMFByteStreamHandler IMFByteStreamHandler_iface; - IMFAsyncCallback IMFAsyncCallback_iface; - LONG refcount; - struct list results; - CRITICAL_SECTION cs; -}; - -static struct winegstreamer_stream_handler *impl_from_IMFByteStreamHandler(IMFByteStreamHandler *iface) -{ - return CONTAINING_RECORD(iface, struct winegstreamer_stream_handler, IMFByteStreamHandler_iface); -} - -static struct winegstreamer_stream_handler *impl_from_IMFAsyncCallback(IMFAsyncCallback *iface) -{ - return CONTAINING_RECORD(iface, struct winegstreamer_stream_handler, IMFAsyncCallback_iface); -} - -static HRESULT WINAPI winegstreamer_stream_handler_QueryInterface(IMFByteStreamHandler *iface, REFIID riid, void **obj) -{ - TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); - - if (IsEqualIID(riid, &IID_IMFByteStreamHandler) || - IsEqualIID(riid, &IID_IUnknown)) - { - *obj = iface; - IMFByteStreamHandler_AddRef(iface); - return S_OK; - } - - WARN("Unsupported %s.\n", debugstr_guid(riid)); - *obj = NULL; - return E_NOINTERFACE; -} - -static ULONG WINAPI winegstreamer_stream_handler_AddRef(IMFByteStreamHandler *iface) -{ - struct winegstreamer_stream_handler *handler = impl_from_IMFByteStreamHandler(iface); - ULONG refcount = InterlockedIncrement(&handler->refcount); - - TRACE("%p, refcount %lu.\n", handler, refcount); - - return refcount; -} - -static ULONG WINAPI winegstreamer_stream_handler_Release(IMFByteStreamHandler *iface) -{ - struct winegstreamer_stream_handler *handler = impl_from_IMFByteStreamHandler(iface); - ULONG refcount = InterlockedDecrement(&handler->refcount); - struct winegstreamer_stream_handler_result *result, *next; - - TRACE("%p, refcount %lu.\n", iface, refcount); - - if (!refcount) - { - LIST_FOR_EACH_ENTRY_SAFE(result, next, &handler->results, struct winegstreamer_stream_handler_result, entry) - { - list_remove(&result->entry); - IMFAsyncResult_Release(result->result); - if (result->object) - IUnknown_Release(result->object); - free(result); - } - DeleteCriticalSection(&handler->cs); - free(handler); - } - - return refcount; -} - -struct create_object_context -{ - IUnknown IUnknown_iface; - LONG refcount; - - IPropertyStore *props; - IMFByteStream *stream; - WCHAR *url; - DWORD flags; -}; - -static struct create_object_context *impl_from_IUnknown(IUnknown *iface) -{ - return CONTAINING_RECORD(iface, struct create_object_context, IUnknown_iface); -} - -static HRESULT WINAPI create_object_context_QueryInterface(IUnknown *iface, REFIID riid, void **obj) -{ - TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); - - if (IsEqualIID(riid, &IID_IUnknown)) - { - *obj = iface; - IUnknown_AddRef(iface); - return S_OK; - } - - WARN("Unsupported %s.\n", debugstr_guid(riid)); - *obj = NULL; - return E_NOINTERFACE; -} - -static ULONG WINAPI create_object_context_AddRef(IUnknown *iface) -{ - struct create_object_context *context = impl_from_IUnknown(iface); - ULONG refcount = InterlockedIncrement(&context->refcount); - - TRACE("%p, refcount %lu.\n", iface, refcount); - - return refcount; -} - -static ULONG WINAPI create_object_context_Release(IUnknown *iface) -{ - struct create_object_context *context = impl_from_IUnknown(iface); - ULONG refcount = InterlockedDecrement(&context->refcount); - - TRACE("%p, refcount %lu.\n", iface, refcount); - - if (!refcount) - { - if (context->props) - IPropertyStore_Release(context->props); - if (context->stream) - IMFByteStream_Release(context->stream); - free(context->url); - free(context); - } - - return refcount; -} - -static const IUnknownVtbl create_object_context_vtbl = -{ - create_object_context_QueryInterface, - create_object_context_AddRef, - create_object_context_Release, -}; - -static HRESULT WINAPI winegstreamer_stream_handler_BeginCreateObject(IMFByteStreamHandler *iface, IMFByteStream *stream, const WCHAR *url, DWORD flags, - IPropertyStore *props, IUnknown **cancel_cookie, IMFAsyncCallback *callback, IUnknown *state) -{ - struct winegstreamer_stream_handler *this = impl_from_IMFByteStreamHandler(iface); - struct create_object_context *context; - IMFAsyncResult *caller, *item; - HRESULT hr; - - TRACE("%p, %s, %#lx, %p, %p, %p, %p.\n", iface, debugstr_w(url), flags, props, cancel_cookie, callback, state); - - if (cancel_cookie) - *cancel_cookie = NULL; - - if (FAILED(hr = MFCreateAsyncResult(NULL, callback, state, &caller))) - return hr; - - if (!(context = calloc(1, sizeof(*context)))) - { - IMFAsyncResult_Release(caller); - return E_OUTOFMEMORY; - } - - context->IUnknown_iface.lpVtbl = &create_object_context_vtbl; - context->refcount = 1; - context->props = props; - if (context->props) - IPropertyStore_AddRef(context->props); - context->flags = flags; - context->stream = stream; - if (context->stream) - IMFByteStream_AddRef(context->stream); - if (url) - context->url = wcsdup(url); - if (!context->stream) - { - IMFAsyncResult_Release(caller); - IUnknown_Release(&context->IUnknown_iface); - return E_OUTOFMEMORY; - } - - hr = MFCreateAsyncResult(&context->IUnknown_iface, &this->IMFAsyncCallback_iface, (IUnknown *)caller, &item); - IUnknown_Release(&context->IUnknown_iface); - if (SUCCEEDED(hr)) - { - if (SUCCEEDED(hr = MFPutWorkItemEx(MFASYNC_CALLBACK_QUEUE_IO, item))) - { - if (cancel_cookie) - { - *cancel_cookie = (IUnknown *)caller; - IUnknown_AddRef(*cancel_cookie); - } - } - - IMFAsyncResult_Release(item); - } - IMFAsyncResult_Release(caller); - - return hr; -} - -static HRESULT WINAPI winegstreamer_stream_handler_EndCreateObject(IMFByteStreamHandler *iface, IMFAsyncResult *result, - MF_OBJECT_TYPE *obj_type, IUnknown **object) -{ - struct winegstreamer_stream_handler *this = impl_from_IMFByteStreamHandler(iface); - struct winegstreamer_stream_handler_result *found = NULL, *cur; - HRESULT hr; - - TRACE("%p, %p, %p, %p.\n", iface, result, obj_type, object); - - EnterCriticalSection(&this->cs); - - LIST_FOR_EACH_ENTRY(cur, &this->results, struct winegstreamer_stream_handler_result, entry) - { - if (result == cur->result) - { - list_remove(&cur->entry); - found = cur; - break; - } - } - - LeaveCriticalSection(&this->cs); - - if (found) - { - *obj_type = found->obj_type; - *object = found->object; - hr = IMFAsyncResult_GetStatus(found->result); - IMFAsyncResult_Release(found->result); - free(found); - } - else - { - *obj_type = MF_OBJECT_INVALID; - *object = NULL; - hr = MF_E_UNEXPECTED; - } - - return hr; -} - -static HRESULT WINAPI winegstreamer_stream_handler_CancelObjectCreation(IMFByteStreamHandler *iface, IUnknown *cancel_cookie) -{ - struct winegstreamer_stream_handler *this = impl_from_IMFByteStreamHandler(iface); - struct winegstreamer_stream_handler_result *found = NULL, *cur; - - TRACE("%p, %p.\n", iface, cancel_cookie); - - EnterCriticalSection(&this->cs); - - LIST_FOR_EACH_ENTRY(cur, &this->results, struct winegstreamer_stream_handler_result, entry) - { - if (cancel_cookie == (IUnknown *)cur->result) - { - list_remove(&cur->entry); - found = cur; - break; - } - } - - LeaveCriticalSection(&this->cs); - - if (found) - { - IMFAsyncResult_Release(found->result); - if (found->object) - IUnknown_Release(found->object); - free(found); - } - - return found ? S_OK : MF_E_UNEXPECTED; -} - -static HRESULT WINAPI winegstreamer_stream_handler_GetMaxNumberOfBytesRequiredForResolution(IMFByteStreamHandler *iface, QWORD *bytes) -{ - FIXME("stub (%p %p)\n", iface, bytes); - return E_NOTIMPL; -} - -static const IMFByteStreamHandlerVtbl winegstreamer_stream_handler_vtbl = -{ - winegstreamer_stream_handler_QueryInterface, - winegstreamer_stream_handler_AddRef, - winegstreamer_stream_handler_Release, - winegstreamer_stream_handler_BeginCreateObject, - winegstreamer_stream_handler_EndCreateObject, - winegstreamer_stream_handler_CancelObjectCreation, - winegstreamer_stream_handler_GetMaxNumberOfBytesRequiredForResolution, -}; - -static HRESULT WINAPI winegstreamer_stream_handler_callback_QueryInterface(IMFAsyncCallback *iface, REFIID riid, void **obj) -{ - if (IsEqualIID(riid, &IID_IMFAsyncCallback) || - IsEqualIID(riid, &IID_IUnknown)) - { - *obj = iface; - IMFAsyncCallback_AddRef(iface); - return S_OK; - } - - WARN("Unsupported %s.\n", debugstr_guid(riid)); - *obj = NULL; - return E_NOINTERFACE; -} - -static ULONG WINAPI winegstreamer_stream_handler_callback_AddRef(IMFAsyncCallback *iface) -{ - struct winegstreamer_stream_handler *handler = impl_from_IMFAsyncCallback(iface); - return IMFByteStreamHandler_AddRef(&handler->IMFByteStreamHandler_iface); -} - -static ULONG WINAPI winegstreamer_stream_handler_callback_Release(IMFAsyncCallback *iface) -{ - struct winegstreamer_stream_handler *handler = impl_from_IMFAsyncCallback(iface); - return IMFByteStreamHandler_Release(&handler->IMFByteStreamHandler_iface); -} - -static HRESULT WINAPI winegstreamer_stream_handler_callback_GetParameters(IMFAsyncCallback *iface, DWORD *flags, DWORD *queue) -{ - return E_NOTIMPL; -} - -static HRESULT winegstreamer_stream_handler_create_object(struct winegstreamer_stream_handler *This, WCHAR *url, IMFByteStream *stream, DWORD flags, - IPropertyStore *props, IUnknown **out_object, MF_OBJECT_TYPE *out_obj_type) -{ - TRACE("%p, %s, %p, %#lx, %p, %p, %p.\n", This, debugstr_w(url), stream, flags, props, out_object, out_obj_type); - - if (flags & MF_RESOLUTION_MEDIASOURCE) - { - HRESULT hr; - struct media_source *new_source; - - if (FAILED(hr = media_source_constructor(stream, NULL, &new_source))) - return hr; - - TRACE("->(%p)\n", new_source); - - *out_object = (IUnknown*)&new_source->IMFMediaSource_iface; - *out_obj_type = MF_OBJECT_MEDIASOURCE; - - return S_OK; - } - else - { - FIXME("Unhandled flags %#lx.\n", flags); - return E_NOTIMPL; - } -} - -static HRESULT WINAPI winegstreamer_stream_handler_callback_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) -{ - struct winegstreamer_stream_handler *handler = impl_from_IMFAsyncCallback(iface); - struct winegstreamer_stream_handler_result *handler_result; - MF_OBJECT_TYPE obj_type = MF_OBJECT_INVALID; - IUnknown *object = NULL, *context_object; - struct create_object_context *context; - IMFAsyncResult *caller; - HRESULT hr; - - caller = (IMFAsyncResult *)IMFAsyncResult_GetStateNoAddRef(result); - - if (FAILED(hr = IMFAsyncResult_GetObject(result, &context_object))) - { - WARN("Expected context set for callee result.\n"); - return hr; - } - - context = impl_from_IUnknown(context_object); - - hr = winegstreamer_stream_handler_create_object(handler, context->url, context->stream, context->flags, context->props, &object, &obj_type); - - if ((handler_result = malloc(sizeof(*handler_result)))) - { - handler_result->result = caller; - IMFAsyncResult_AddRef(handler_result->result); - handler_result->obj_type = obj_type; - handler_result->object = object; - - EnterCriticalSection(&handler->cs); - list_add_tail(&handler->results, &handler_result->entry); - LeaveCriticalSection(&handler->cs); - } - else - { - if (object) - IUnknown_Release(object); - hr = E_OUTOFMEMORY; - } - - IUnknown_Release(&context->IUnknown_iface); - - IMFAsyncResult_SetStatus(caller, hr); - MFInvokeCallback(caller); - - return S_OK; -} - -static const IMFAsyncCallbackVtbl winegstreamer_stream_handler_callback_vtbl = -{ - winegstreamer_stream_handler_callback_QueryInterface, - winegstreamer_stream_handler_callback_AddRef, - winegstreamer_stream_handler_callback_Release, - winegstreamer_stream_handler_callback_GetParameters, - winegstreamer_stream_handler_callback_Invoke, -}; - -HRESULT winegstreamer_stream_handler_create(REFIID riid, void **obj) -{ - struct winegstreamer_stream_handler *this; - HRESULT hr; - - TRACE("%s, %p.\n", debugstr_guid(riid), obj); - - if (!(this = calloc(1, sizeof(*this)))) - return E_OUTOFMEMORY; - - list_init(&this->results); - InitializeCriticalSection(&this->cs); - - this->IMFByteStreamHandler_iface.lpVtbl = &winegstreamer_stream_handler_vtbl; - this->IMFAsyncCallback_iface.lpVtbl = &winegstreamer_stream_handler_callback_vtbl; - this->refcount = 1; - - hr = IMFByteStreamHandler_QueryInterface(&this->IMFByteStreamHandler_iface, riid, obj); - IMFByteStreamHandler_Release(&this->IMFByteStreamHandler_iface); - - return hr; -} diff --git a/dlls/winegstreamer/media_source_old.c b/dlls/winegstreamer/media_source_old.c new file mode 100644 index 00000000000..7dd7a989d4a --- /dev/null +++ b/dlls/winegstreamer/media_source_old.c @@ -0,0 +1,1702 @@ +/* GStreamer Media Source + * + * Copyright 2020 Derek Lesho + * Copyright 2020 Zebediah Figura for CodeWeavers + * + * 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 + */ + +#include "gst_private.h" + +#include "mfapi.h" +#include "mferror.h" + +#include "wine/list.h" + +WINE_DEFAULT_DEBUG_CHANNEL(mfplat); + +extern const GUID MFVideoFormat_ABGR32; + +struct media_stream +{ + IMFMediaStream IMFMediaStream_iface; + LONG ref; + + IMFMediaSource *media_source; + IMFMediaEventQueue *event_queue; + IMFStreamDescriptor *descriptor; + + struct wg_parser_stream *wg_stream; + + IUnknown **token_queue; + LONG token_queue_count; + LONG token_queue_cap; + + DWORD stream_id; + BOOL active; + BOOL eos; + + DWORD busy; + CONDITION_VARIABLE cond; +}; + +enum source_async_op +{ + SOURCE_ASYNC_START, + SOURCE_ASYNC_PAUSE, + SOURCE_ASYNC_STOP, + SOURCE_ASYNC_REQUEST_SAMPLE, +}; + +struct source_async_command +{ + IUnknown IUnknown_iface; + LONG refcount; + enum source_async_op op; + union + { + struct + { + IMFPresentationDescriptor *descriptor; + GUID format; + PROPVARIANT position; + } start; + struct + { + struct media_stream *stream; + IUnknown *token; + } request_sample; + } u; +}; + +struct media_source +{ + IMFMediaSource IMFMediaSource_iface; + IMFGetService IMFGetService_iface; + IMFRateSupport IMFRateSupport_iface; + IMFRateControl IMFRateControl_iface; + IMFAsyncCallback async_commands_callback; + LONG ref; + DWORD async_commands_queue; + IMFMediaEventQueue *event_queue; + IMFByteStream *byte_stream; + + CRITICAL_SECTION cs; + + struct wg_parser *wg_parser; + + struct media_stream **streams; + ULONG stream_count; + IMFPresentationDescriptor *pres_desc; + enum + { + SOURCE_OPENING, + SOURCE_STOPPED, + SOURCE_PAUSED, + SOURCE_RUNNING, + SOURCE_SHUTDOWN, + } state; + float rate; + + HANDLE read_thread; + bool read_thread_shutdown; +}; + +static inline struct media_stream *impl_from_IMFMediaStream(IMFMediaStream *iface) +{ + return CONTAINING_RECORD(iface, struct media_stream, IMFMediaStream_iface); +} + +static inline struct media_source *impl_from_IMFMediaSource(IMFMediaSource *iface) +{ + return CONTAINING_RECORD(iface, struct media_source, IMFMediaSource_iface); +} + +static inline struct media_source *impl_from_IMFGetService(IMFGetService *iface) +{ + return CONTAINING_RECORD(iface, struct media_source, IMFGetService_iface); +} + +static inline struct media_source *impl_from_IMFRateSupport(IMFRateSupport *iface) +{ + return CONTAINING_RECORD(iface, struct media_source, IMFRateSupport_iface); +} + +static inline struct media_source *impl_from_IMFRateControl(IMFRateControl *iface) +{ + return CONTAINING_RECORD(iface, struct media_source, IMFRateControl_iface); +} + +static inline struct media_source *impl_from_async_commands_callback_IMFAsyncCallback(IMFAsyncCallback *iface) +{ + return CONTAINING_RECORD(iface, struct media_source, async_commands_callback); +} + +static inline struct source_async_command *impl_from_async_command_IUnknown(IUnknown *iface) +{ + return CONTAINING_RECORD(iface, struct source_async_command, IUnknown_iface); +} + +static HRESULT WINAPI source_async_command_QueryInterface(IUnknown *iface, REFIID riid, void **obj) +{ + if (IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IUnknown_AddRef(iface); + return S_OK; + } + + WARN("Unsupported interface %s.\n", debugstr_guid(riid)); + *obj = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI source_async_command_AddRef(IUnknown *iface) +{ + struct source_async_command *command = impl_from_async_command_IUnknown(iface); + return InterlockedIncrement(&command->refcount); +} + +static ULONG WINAPI source_async_command_Release(IUnknown *iface) +{ + struct source_async_command *command = impl_from_async_command_IUnknown(iface); + ULONG refcount = InterlockedDecrement(&command->refcount); + + if (!refcount) + { + if (command->op == SOURCE_ASYNC_START) + PropVariantClear(&command->u.start.position); + else if (command->op == SOURCE_ASYNC_REQUEST_SAMPLE) + { + if (command->u.request_sample.token) + IUnknown_Release(command->u.request_sample.token); + } + free(command); + } + + return refcount; +} + +static const IUnknownVtbl source_async_command_vtbl = +{ + source_async_command_QueryInterface, + source_async_command_AddRef, + source_async_command_Release, +}; + +static HRESULT source_create_async_op(enum source_async_op op, struct source_async_command **ret) +{ + struct source_async_command *command; + + if (!(command = calloc(1, sizeof(*command)))) + return E_OUTOFMEMORY; + + command->IUnknown_iface.lpVtbl = &source_async_command_vtbl; + command->op = op; + + *ret = command; + + return S_OK; +} + +static HRESULT WINAPI callback_QueryInterface(IMFAsyncCallback *iface, REFIID riid, void **obj) +{ + TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); + + if (IsEqualIID(riid, &IID_IMFAsyncCallback) || + IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IMFAsyncCallback_AddRef(iface); + return S_OK; + } + + WARN("Unsupported %s.\n", debugstr_guid(riid)); + *obj = NULL; + return E_NOINTERFACE; +} + +static HRESULT WINAPI callback_GetParameters(IMFAsyncCallback *iface, + DWORD *flags, DWORD *queue) +{ + return E_NOTIMPL; +} + +static ULONG WINAPI source_async_commands_callback_AddRef(IMFAsyncCallback *iface) +{ + struct media_source *source = impl_from_async_commands_callback_IMFAsyncCallback(iface); + return IMFMediaSource_AddRef(&source->IMFMediaSource_iface); +} + +static ULONG WINAPI source_async_commands_callback_Release(IMFAsyncCallback *iface) +{ + struct media_source *source = impl_from_async_commands_callback_IMFAsyncCallback(iface); + return IMFMediaSource_Release(&source->IMFMediaSource_iface); +} + +static IMFStreamDescriptor *stream_descriptor_from_id(IMFPresentationDescriptor *pres_desc, DWORD id, BOOL *selected) +{ + ULONG sd_count; + IMFStreamDescriptor *ret; + unsigned int i; + + if (FAILED(IMFPresentationDescriptor_GetStreamDescriptorCount(pres_desc, &sd_count))) + return NULL; + + for (i = 0; i < sd_count; i++) + { + DWORD stream_id; + + if (FAILED(IMFPresentationDescriptor_GetStreamDescriptorByIndex(pres_desc, i, selected, &ret))) + return NULL; + + if (SUCCEEDED(IMFStreamDescriptor_GetStreamIdentifier(ret, &stream_id)) && stream_id == id) + return ret; + + IMFStreamDescriptor_Release(ret); + } + return NULL; +} + +static BOOL enqueue_token(struct media_stream *stream, IUnknown *token) +{ + if (stream->token_queue_count == stream->token_queue_cap) + { + IUnknown **buf; + stream->token_queue_cap = stream->token_queue_cap * 2 + 1; + buf = realloc(stream->token_queue, stream->token_queue_cap * sizeof(*buf)); + if (buf) + stream->token_queue = buf; + else + { + stream->token_queue_cap = stream->token_queue_count; + return FALSE; + } + } + stream->token_queue[stream->token_queue_count++] = token; + return TRUE; +} + +static void flush_token_queue(struct media_stream *stream, BOOL send) +{ + struct media_source *source = impl_from_IMFMediaSource(stream->media_source); + LONG i; + + for (i = 0; i < stream->token_queue_count; i++) + { + if (send) + { + HRESULT hr; + struct source_async_command *command; + if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_REQUEST_SAMPLE, &command))) + { + command->u.request_sample.stream = stream; + command->u.request_sample.token = stream->token_queue[i]; + + hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, + &command->IUnknown_iface); + } + if (FAILED(hr)) + WARN("Could not enqueue sample request, hr %#lx\n", hr); + } + else if (stream->token_queue[i]) + IUnknown_Release(stream->token_queue[i]); + } + free(stream->token_queue); + stream->token_queue = NULL; + stream->token_queue_count = 0; + stream->token_queue_cap = 0; +} + +static void start_pipeline(struct media_source *source, struct source_async_command *command) +{ + PROPVARIANT *position = &command->u.start.position; + BOOL seek_message = source->state != SOURCE_STOPPED && position->vt != VT_EMPTY; + unsigned int i; + + /* seek to beginning on stop->play */ + if (source->state == SOURCE_STOPPED && position->vt == VT_EMPTY) + { + position->vt = VT_I8; + position->hVal.QuadPart = 0; + } + + for (i = 0; i < source->stream_count; i++) + { + struct media_stream *stream; + IMFStreamDescriptor *sd; + IMFMediaTypeHandler *mth; + IMFMediaType *current_mt; + DWORD stream_id; + BOOL was_active; + BOOL selected; + + stream = source->streams[i]; + + IMFStreamDescriptor_GetStreamIdentifier(stream->descriptor, &stream_id); + + sd = stream_descriptor_from_id(command->u.start.descriptor, stream_id, &selected); + IMFStreamDescriptor_Release(sd); + + was_active = stream->active; + stream->active = selected; + + if (selected) + { + struct wg_format format; + + IMFStreamDescriptor_GetMediaTypeHandler(stream->descriptor, &mth); + IMFMediaTypeHandler_GetCurrentMediaType(mth, ¤t_mt); + + mf_media_type_to_wg_format(current_mt, &format); + wg_parser_stream_enable(stream->wg_stream, &format, 0); + + IMFMediaType_Release(current_mt); + IMFMediaTypeHandler_Release(mth); + } + else + { + wg_parser_stream_disable(stream->wg_stream); + } + + if (position->vt != VT_EMPTY) + stream->eos = FALSE; + + if (selected) + { + TRACE("Stream %u (%p) selected\n", i, stream); + IMFMediaEventQueue_QueueEventParamUnk(source->event_queue, + was_active ? MEUpdatedStream : MENewStream, &GUID_NULL, + S_OK, (IUnknown*) &stream->IMFMediaStream_iface); + + IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, + seek_message ? MEStreamSeeked : MEStreamStarted, &GUID_NULL, S_OK, position); + } + } + + IMFMediaEventQueue_QueueEventParamVar(source->event_queue, + seek_message ? MESourceSeeked : MESourceStarted, + &GUID_NULL, S_OK, position); + + source->state = SOURCE_RUNNING; + + if (position->vt == VT_I8) + wg_parser_stream_seek(source->streams[0]->wg_stream, 1.0, position->hVal.QuadPart, 0, + AM_SEEKING_AbsolutePositioning, AM_SEEKING_NoPositioning); + + for (i = 0; i < source->stream_count; i++) + flush_token_queue(source->streams[i], position->vt == VT_EMPTY); +} + +static void pause_pipeline(struct media_source *source) +{ + unsigned int i; + HRESULT hr; + + for (i = 0; i < source->stream_count; i++) + { + struct media_stream *stream = source->streams[i]; + if (stream->active && FAILED(hr = IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, MEStreamPaused, + &GUID_NULL, S_OK, NULL))) + WARN("Failed to queue MEStreamPaused event, hr %#lx\n", hr); + } + + IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MESourcePaused, &GUID_NULL, S_OK, NULL); + + source->state = SOURCE_PAUSED; +} + +static void stop_pipeline(struct media_source *source) +{ + unsigned int i; + HRESULT hr; + + for (i = 0; i < source->stream_count; i++) + { + struct media_stream *stream = source->streams[i]; + if (stream->active && FAILED(hr = IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, MEStreamStopped, + &GUID_NULL, S_OK, NULL))) + WARN("Failed to queue MEStreamStopped event, hr %#lx\n", hr); + } + + IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MESourceStopped, &GUID_NULL, S_OK, NULL); + + source->state = SOURCE_STOPPED; + + for (i = 0; i < source->stream_count; i++) + flush_token_queue(source->streams[i], FALSE); +} + +static void dispatch_end_of_presentation(struct media_source *source) +{ + PROPVARIANT empty = {.vt = VT_EMPTY}; + unsigned int i; + + /* A stream has ended, check whether all have */ + for (i = 0; i < source->stream_count; i++) + { + struct media_stream *stream = source->streams[i]; + if (stream->active && !stream->eos) + return; + } + + IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MEEndOfPresentation, &GUID_NULL, S_OK, &empty); +} + +static void send_buffer(struct media_stream *stream, const struct wg_parser_buffer *wg_buffer, IUnknown *token) +{ + IMFMediaBuffer *buffer; + IMFSample *sample; + HRESULT hr; + BYTE *data; + + if (FAILED(hr = MFCreateSample(&sample))) + { + ERR("Failed to create sample, hr %#lx.\n", hr); + return; + } + + if (FAILED(hr = MFCreateMemoryBuffer(wg_buffer->size, &buffer))) + { + ERR("Failed to create buffer, hr %#lx.\n", hr); + IMFSample_Release(sample); + return; + } + + if (FAILED(hr = IMFSample_AddBuffer(sample, buffer))) + { + ERR("Failed to add buffer, hr %#lx.\n", hr); + goto out; + } + + if (FAILED(hr = IMFMediaBuffer_SetCurrentLength(buffer, wg_buffer->size))) + { + ERR("Failed to set size, hr %#lx.\n", hr); + goto out; + } + + if (FAILED(hr = IMFMediaBuffer_Lock(buffer, &data, NULL, NULL))) + { + ERR("Failed to lock buffer, hr %#lx.\n", hr); + goto out; + } + + if (!wg_parser_stream_copy_buffer(stream->wg_stream, data, 0, wg_buffer->size)) + { + wg_parser_stream_release_buffer(stream->wg_stream); + IMFMediaBuffer_Unlock(buffer); + goto out; + } + wg_parser_stream_release_buffer(stream->wg_stream); + + if (FAILED(hr = IMFMediaBuffer_Unlock(buffer))) + { + ERR("Failed to unlock buffer, hr %#lx.\n", hr); + goto out; + } + + if (FAILED(hr = IMFSample_SetSampleTime(sample, wg_buffer->pts))) + { + ERR("Failed to set sample time, hr %#lx.\n", hr); + goto out; + } + + if (FAILED(hr = IMFSample_SetSampleDuration(sample, wg_buffer->duration))) + { + ERR("Failed to set sample duration, hr %#lx.\n", hr); + goto out; + } + + if (token) + IMFSample_SetUnknown(sample, &MFSampleExtension_Token, token); + + IMFMediaEventQueue_QueueEventParamUnk(stream->event_queue, MEMediaSample, + &GUID_NULL, S_OK, (IUnknown *)sample); + +out: + IMFMediaBuffer_Release(buffer); + IMFSample_Release(sample); +} + +static void wait_on_sample(struct media_stream *stream, IUnknown *token) +{ + struct media_source *source = impl_from_IMFMediaSource(stream->media_source); + PROPVARIANT empty_var = {.vt = VT_EMPTY}; + struct wg_parser_buffer buffer; + BOOL ret; + + TRACE("%p, %p\n", stream, token); + + stream->busy = TRUE; + LeaveCriticalSection(&source->cs); + ret = wg_parser_stream_get_buffer(source->wg_parser, stream->wg_stream, &buffer); + EnterCriticalSection(&source->cs); + stream->busy = FALSE; + WakeConditionVariable(&stream->cond); + + if (source->state == SOURCE_SHUTDOWN) + WARN("media source has been shutdown, returning\n"); + else if (ret) + send_buffer(stream, &buffer, token); + else + { + stream->eos = TRUE; + IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, MEEndOfStream, &GUID_NULL, S_OK, &empty_var); + dispatch_end_of_presentation(source); + } +} + +static HRESULT WINAPI source_async_commands_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) +{ + struct media_source *source = impl_from_async_commands_callback_IMFAsyncCallback(iface); + struct source_async_command *command; + IUnknown *state; + HRESULT hr; + + if (FAILED(hr = IMFAsyncResult_GetState(result, &state))) + return hr; + + EnterCriticalSection(&source->cs); + + command = impl_from_async_command_IUnknown(state); + switch (command->op) + { + case SOURCE_ASYNC_START: + if (source->state != SOURCE_SHUTDOWN) + start_pipeline(source, command); + break; + case SOURCE_ASYNC_PAUSE: + if (source->state != SOURCE_SHUTDOWN) + pause_pipeline(source); + break; + case SOURCE_ASYNC_STOP: + if (source->state != SOURCE_SHUTDOWN) + stop_pipeline(source); + break; + case SOURCE_ASYNC_REQUEST_SAMPLE: + if (source->state == SOURCE_PAUSED) + enqueue_token(command->u.request_sample.stream, command->u.request_sample.token); + else if (source->state == SOURCE_RUNNING) + wait_on_sample(command->u.request_sample.stream, command->u.request_sample.token); + break; + } + + LeaveCriticalSection(&source->cs); + + IUnknown_Release(state); + + return S_OK; +} + +static const IMFAsyncCallbackVtbl source_async_commands_callback_vtbl = +{ + callback_QueryInterface, + source_async_commands_callback_AddRef, + source_async_commands_callback_Release, + callback_GetParameters, + source_async_commands_Invoke, +}; + +static DWORD CALLBACK read_thread(void *arg) +{ + struct media_source *source = arg; + IMFByteStream *byte_stream = source->byte_stream; + size_t buffer_size = 4096; + uint64_t file_size; + void *data; + + if (!(data = malloc(buffer_size))) + return 0; + + IMFByteStream_GetLength(byte_stream, &file_size); + + TRACE("Starting read thread for media source %p.\n", source); + + while (!source->read_thread_shutdown) + { + uint64_t offset; + ULONG ret_size; + uint32_t size; + HRESULT hr; + + if (!wg_parser_get_next_read_offset(source->wg_parser, &offset, &size)) + continue; + + if (offset >= file_size) + size = 0; + else if (offset + size >= file_size) + size = file_size - offset; + + /* Some IMFByteStreams (including the standard file-based stream) return + * an error when reading past the file size. */ + if (!size) + { + wg_parser_push_data(source->wg_parser, data, 0); + continue; + } + + if (!array_reserve(&data, &buffer_size, size, 1)) + { + free(data); + return 0; + } + + ret_size = 0; + + if (SUCCEEDED(hr = IMFByteStream_SetCurrentPosition(byte_stream, offset))) + hr = IMFByteStream_Read(byte_stream, data, size, &ret_size); + if (FAILED(hr)) + ERR("Failed to read %u bytes at offset %I64u, hr %#lx.\n", size, offset, hr); + else if (ret_size != size) + ERR("Unexpected short read: requested %u bytes, got %lu.\n", size, ret_size); + wg_parser_push_data(source->wg_parser, SUCCEEDED(hr) ? data : NULL, ret_size); + } + + free(data); + TRACE("Media source is shutting down; exiting.\n"); + return 0; +} + +static HRESULT WINAPI media_stream_QueryInterface(IMFMediaStream *iface, REFIID riid, void **out) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), out); + + if (IsEqualIID(riid, &IID_IMFMediaStream) || + IsEqualIID(riid, &IID_IMFMediaEventGenerator) || + IsEqualIID(riid, &IID_IUnknown)) + { + *out = &stream->IMFMediaStream_iface; + } + else + { + FIXME("(%s, %p)\n", debugstr_guid(riid), out); + *out = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown*)*out); + return S_OK; +} + +static ULONG WINAPI media_stream_AddRef(IMFMediaStream *iface) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + ULONG ref = InterlockedIncrement(&stream->ref); + + TRACE("%p, refcount %lu.\n", iface, ref); + + return ref; +} + +static ULONG WINAPI media_stream_Release(IMFMediaStream *iface) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + ULONG ref = InterlockedDecrement(&stream->ref); + + TRACE("%p, refcount %lu.\n", iface, ref); + + if (!ref) + { + IMFMediaSource_Release(stream->media_source); + IMFStreamDescriptor_Release(stream->descriptor); + IMFMediaEventQueue_Release(stream->event_queue); + flush_token_queue(stream, FALSE); + free(stream); + } + + return ref; +} + +static HRESULT WINAPI media_stream_GetEvent(IMFMediaStream *iface, DWORD flags, IMFMediaEvent **event) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + TRACE("%p, %#lx, %p.\n", iface, flags, event); + + return IMFMediaEventQueue_GetEvent(stream->event_queue, flags, event); +} + +static HRESULT WINAPI media_stream_BeginGetEvent(IMFMediaStream *iface, IMFAsyncCallback *callback, IUnknown *state) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + TRACE("%p, %p, %p.\n", iface, callback, state); + + return IMFMediaEventQueue_BeginGetEvent(stream->event_queue, callback, state); +} + +static HRESULT WINAPI media_stream_EndGetEvent(IMFMediaStream *iface, IMFAsyncResult *result, IMFMediaEvent **event) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + TRACE("%p, %p, %p.\n", stream, result, event); + + return IMFMediaEventQueue_EndGetEvent(stream->event_queue, result, event); +} + +static HRESULT WINAPI media_stream_QueueEvent(IMFMediaStream *iface, MediaEventType event_type, REFGUID ext_type, + HRESULT hr, const PROPVARIANT *value) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + TRACE("%p, %lu, %s, %#lx, %p.\n", iface, event_type, debugstr_guid(ext_type), hr, value); + + return IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, event_type, ext_type, hr, value); +} + +static HRESULT WINAPI media_stream_GetMediaSource(IMFMediaStream *iface, IMFMediaSource **out) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + struct media_source *source = impl_from_IMFMediaSource(stream->media_source); + HRESULT hr = S_OK; + + TRACE("%p, %p.\n", iface, out); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + hr = MF_E_SHUTDOWN; + else + { + IMFMediaSource_AddRef(&source->IMFMediaSource_iface); + *out = &source->IMFMediaSource_iface; + } + + LeaveCriticalSection(&source->cs); + + return hr; +} + +static HRESULT WINAPI media_stream_GetStreamDescriptor(IMFMediaStream* iface, IMFStreamDescriptor **descriptor) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + struct media_source *source = impl_from_IMFMediaSource(stream->media_source); + HRESULT hr = S_OK; + + TRACE("%p, %p.\n", iface, descriptor); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + hr = MF_E_SHUTDOWN; + else + { + IMFStreamDescriptor_AddRef(stream->descriptor); + *descriptor = stream->descriptor; + } + + LeaveCriticalSection(&source->cs); + + return hr; +} + +static HRESULT WINAPI media_stream_RequestSample(IMFMediaStream *iface, IUnknown *token) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + struct media_source *source = impl_from_IMFMediaSource(stream->media_source); + struct source_async_command *command; + HRESULT hr; + + TRACE("%p, %p.\n", iface, token); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + hr = MF_E_SHUTDOWN; + else if (!stream->active) + hr = MF_E_MEDIA_SOURCE_WRONGSTATE; + else if (stream->eos) + hr = MF_E_END_OF_STREAM; + else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_REQUEST_SAMPLE, &command))) + { + command->u.request_sample.stream = stream; + if (token) + IUnknown_AddRef(token); + command->u.request_sample.token = token; + + hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, &command->IUnknown_iface); + } + + LeaveCriticalSection(&source->cs); + + return hr; +} + +static const IMFMediaStreamVtbl media_stream_vtbl = +{ + media_stream_QueryInterface, + media_stream_AddRef, + media_stream_Release, + media_stream_GetEvent, + media_stream_BeginGetEvent, + media_stream_EndGetEvent, + media_stream_QueueEvent, + media_stream_GetMediaSource, + media_stream_GetStreamDescriptor, + media_stream_RequestSample +}; + +static HRESULT media_stream_create(IMFMediaSource *source, DWORD id, + struct media_stream **out) +{ + struct wg_parser *wg_parser = impl_from_IMFMediaSource(source)->wg_parser; + struct media_stream *object; + HRESULT hr; + + TRACE("source %p, id %lu.\n", source, id); + + if (!(object = calloc(1, sizeof(*object)))) + return E_OUTOFMEMORY; + + object->IMFMediaStream_iface.lpVtbl = &media_stream_vtbl; + object->ref = 1; + + if (FAILED(hr = MFCreateEventQueue(&object->event_queue))) + { + free(object); + return hr; + } + + IMFMediaSource_AddRef(source); + object->media_source = source; + object->stream_id = id; + + object->active = FALSE; + object->eos = FALSE; + object->wg_stream = wg_parser_get_stream(wg_parser, id); + + TRACE("Created stream object %p.\n", object); + + *out = object; + return S_OK; +} + +static HRESULT media_stream_init_desc(struct media_stream *stream) +{ + IMFMediaTypeHandler *type_handler = NULL; + IMFMediaType *stream_types[9]; + struct wg_format format; + DWORD type_count = 0; + HRESULT hr = S_OK; + unsigned int i; + + wg_parser_stream_get_preferred_format(stream->wg_stream, &format); + + if (format.major_type == WG_MAJOR_TYPE_VIDEO) + { + /* Try to prefer YUV formats over RGB ones. Most decoders output in the + * YUV color space, and it's generally much less expensive for + * videoconvert to do YUV -> YUV transformations. */ + static const enum wg_video_format video_formats[] = + { + WG_VIDEO_FORMAT_NV12, + WG_VIDEO_FORMAT_YV12, + WG_VIDEO_FORMAT_YUY2, + WG_VIDEO_FORMAT_I420, + WG_VIDEO_FORMAT_BGRA, + WG_VIDEO_FORMAT_BGRx, + WG_VIDEO_FORMAT_RGBA, + }; + + IMFMediaType *base_type = mf_media_type_from_wg_format(&format); + GUID base_subtype; + + if (!base_type) + { + hr = MF_E_INVALIDMEDIATYPE; + goto done; + } + + IMFMediaType_GetGUID(base_type, &MF_MT_SUBTYPE, &base_subtype); + + stream_types[0] = base_type; + type_count = 1; + + for (i = 0; i < ARRAY_SIZE(video_formats); ++i) + { + struct wg_format new_format = format; + IMFMediaType *new_type; + + new_format.u.video.format = video_formats[i]; + + if (!(new_type = mf_media_type_from_wg_format(&new_format))) + { + hr = E_OUTOFMEMORY; + goto done; + } + stream_types[type_count++] = new_type; + + if (video_formats[i] == WG_VIDEO_FORMAT_I420) + { + IMFMediaType *iyuv_type; + + if (FAILED(hr = MFCreateMediaType(&iyuv_type))) + goto done; + if (FAILED(hr = IMFMediaType_CopyAllItems(new_type, (IMFAttributes *)iyuv_type))) + goto done; + if (FAILED(hr = IMFMediaType_SetGUID(iyuv_type, &MF_MT_SUBTYPE, &MFVideoFormat_IYUV))) + goto done; + stream_types[type_count++] = iyuv_type; + } + } + + for (i = 0; i < type_count; i++) + { + IMFMediaType_SetUINT32(stream_types[i], &MF_MT_VIDEO_NOMINAL_RANGE, + MFNominalRange_Normal); + } + } + else if (format.major_type == WG_MAJOR_TYPE_AUDIO) + { + /* Expose at least one PCM and one floating point type for the + consumer to pick from. Moreover, ensure that we expose S16LE first, + as games such as MGSV expect the native media type to be 16 bps. */ + static const enum wg_audio_format audio_types[] = + { + WG_AUDIO_FORMAT_S16LE, + WG_AUDIO_FORMAT_F32LE, + }; + + BOOL has_native_format = FALSE; + + for (i = 0; i < ARRAY_SIZE(audio_types); i++) + { + struct wg_format new_format; + + new_format = format; + new_format.u.audio.format = audio_types[i]; + if ((stream_types[type_count] = mf_media_type_from_wg_format(&new_format))) + { + if (format.u.audio.format == audio_types[i]) + has_native_format = TRUE; + type_count++; + } + } + + if (!has_native_format && (stream_types[type_count] = mf_media_type_from_wg_format(&format))) + type_count++; + } + else + { + if ((stream_types[0] = mf_media_type_from_wg_format(&format))) + type_count = 1; + } + + assert(type_count <= ARRAY_SIZE(stream_types)); + + if (!type_count) + { + ERR("Failed to establish an IMFMediaType from any of the possible stream caps!\n"); + return E_FAIL; + } + + if (FAILED(hr = MFCreateStreamDescriptor(stream->stream_id, type_count, stream_types, &stream->descriptor))) + goto done; + + if (FAILED(hr = IMFStreamDescriptor_GetMediaTypeHandler(stream->descriptor, &type_handler))) + { + IMFStreamDescriptor_Release(stream->descriptor); + goto done; + } + + if (FAILED(hr = IMFMediaTypeHandler_SetCurrentMediaType(type_handler, stream_types[0]))) + { + IMFStreamDescriptor_Release(stream->descriptor); + goto done; + } + +done: + if (type_handler) + IMFMediaTypeHandler_Release(type_handler); + for (i = 0; i < type_count; i++) + IMFMediaType_Release(stream_types[i]); + return hr; +} + +static HRESULT WINAPI media_source_get_service_QueryInterface(IMFGetService *iface, REFIID riid, void **obj) +{ + struct media_source *source = impl_from_IMFGetService(iface); + return IMFMediaSource_QueryInterface(&source->IMFMediaSource_iface, riid, obj); +} + +static ULONG WINAPI media_source_get_service_AddRef(IMFGetService *iface) +{ + struct media_source *source = impl_from_IMFGetService(iface); + return IMFMediaSource_AddRef(&source->IMFMediaSource_iface); +} + +static ULONG WINAPI media_source_get_service_Release(IMFGetService *iface) +{ + struct media_source *source = impl_from_IMFGetService(iface); + return IMFMediaSource_Release(&source->IMFMediaSource_iface); +} + +static HRESULT WINAPI media_source_get_service_GetService(IMFGetService *iface, REFGUID service, REFIID riid, void **obj) +{ + struct media_source *source = impl_from_IMFGetService(iface); + + TRACE("%p, %s, %s, %p.\n", iface, debugstr_guid(service), debugstr_guid(riid), obj); + + *obj = NULL; + + if (IsEqualGUID(service, &MF_RATE_CONTROL_SERVICE)) + { + if (IsEqualIID(riid, &IID_IMFRateSupport)) + { + *obj = &source->IMFRateSupport_iface; + } + else if (IsEqualIID(riid, &IID_IMFRateControl)) + { + *obj = &source->IMFRateControl_iface; + } + } + else + FIXME("Unsupported service %s.\n", debugstr_guid(service)); + + if (*obj) + IUnknown_AddRef((IUnknown *)*obj); + + return *obj ? S_OK : E_NOINTERFACE; +} + +static const IMFGetServiceVtbl media_source_get_service_vtbl = +{ + media_source_get_service_QueryInterface, + media_source_get_service_AddRef, + media_source_get_service_Release, + media_source_get_service_GetService, +}; + +static HRESULT WINAPI media_source_rate_support_QueryInterface(IMFRateSupport *iface, REFIID riid, void **obj) +{ + struct media_source *source = impl_from_IMFRateSupport(iface); + return IMFMediaSource_QueryInterface(&source->IMFMediaSource_iface, riid, obj); +} + +static ULONG WINAPI media_source_rate_support_AddRef(IMFRateSupport *iface) +{ + struct media_source *source = impl_from_IMFRateSupport(iface); + return IMFMediaSource_AddRef(&source->IMFMediaSource_iface); +} + +static ULONG WINAPI media_source_rate_support_Release(IMFRateSupport *iface) +{ + struct media_source *source = impl_from_IMFRateSupport(iface); + return IMFMediaSource_Release(&source->IMFMediaSource_iface); +} + +static HRESULT WINAPI media_source_rate_support_GetSlowestRate(IMFRateSupport *iface, MFRATE_DIRECTION direction, BOOL thin, float *rate) +{ + TRACE("%p, %d, %d, %p.\n", iface, direction, thin, rate); + + *rate = 0.0f; + + return S_OK; +} + +static HRESULT WINAPI media_source_rate_support_GetFastestRate(IMFRateSupport *iface, MFRATE_DIRECTION direction, BOOL thin, float *rate) +{ + TRACE("%p, %d, %d, %p.\n", iface, direction, thin, rate); + + *rate = direction == MFRATE_FORWARD ? 1e6f : -1e6f; + + return S_OK; +} + +static HRESULT WINAPI media_source_rate_support_IsRateSupported(IMFRateSupport *iface, BOOL thin, float rate, + float *nearest_rate) +{ + TRACE("%p, %d, %f, %p.\n", iface, thin, rate, nearest_rate); + + if (nearest_rate) + *nearest_rate = rate; + + return rate >= -1e6f && rate <= 1e6f ? S_OK : MF_E_UNSUPPORTED_RATE; +} + +static const IMFRateSupportVtbl media_source_rate_support_vtbl = +{ + media_source_rate_support_QueryInterface, + media_source_rate_support_AddRef, + media_source_rate_support_Release, + media_source_rate_support_GetSlowestRate, + media_source_rate_support_GetFastestRate, + media_source_rate_support_IsRateSupported, +}; + +static HRESULT WINAPI media_source_rate_control_QueryInterface(IMFRateControl *iface, REFIID riid, void **obj) +{ + struct media_source *source = impl_from_IMFRateControl(iface); + return IMFMediaSource_QueryInterface(&source->IMFMediaSource_iface, riid, obj); +} + +static ULONG WINAPI media_source_rate_control_AddRef(IMFRateControl *iface) +{ + struct media_source *source = impl_from_IMFRateControl(iface); + return IMFMediaSource_AddRef(&source->IMFMediaSource_iface); +} + +static ULONG WINAPI media_source_rate_control_Release(IMFRateControl *iface) +{ + struct media_source *source = impl_from_IMFRateControl(iface); + return IMFMediaSource_Release(&source->IMFMediaSource_iface); +} + +static HRESULT WINAPI media_source_rate_control_SetRate(IMFRateControl *iface, BOOL thin, float rate) +{ + struct media_source *source = impl_from_IMFRateControl(iface); + HRESULT hr; + + FIXME("%p, %d, %f.\n", iface, thin, rate); + + if (rate < 0.0f) + return MF_E_REVERSE_UNSUPPORTED; + + if (thin) + return MF_E_THINNING_UNSUPPORTED; + + if (FAILED(hr = IMFRateSupport_IsRateSupported(&source->IMFRateSupport_iface, thin, rate, NULL))) + return hr; + + EnterCriticalSection(&source->cs); + source->rate = rate; + LeaveCriticalSection(&source->cs); + + return IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MESourceRateChanged, &GUID_NULL, S_OK, NULL); +} + +static HRESULT WINAPI media_source_rate_control_GetRate(IMFRateControl *iface, BOOL *thin, float *rate) +{ + struct media_source *source = impl_from_IMFRateControl(iface); + + TRACE("%p, %p, %p.\n", iface, thin, rate); + + if (thin) + *thin = FALSE; + + EnterCriticalSection(&source->cs); + *rate = source->rate; + LeaveCriticalSection(&source->cs); + + return S_OK; +} + +static const IMFRateControlVtbl media_source_rate_control_vtbl = +{ + media_source_rate_control_QueryInterface, + media_source_rate_control_AddRef, + media_source_rate_control_Release, + media_source_rate_control_SetRate, + media_source_rate_control_GetRate, +}; + +static HRESULT WINAPI media_source_QueryInterface(IMFMediaSource *iface, REFIID riid, void **out) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + + TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), out); + + if (IsEqualIID(riid, &IID_IMFMediaSource) || + IsEqualIID(riid, &IID_IMFMediaEventGenerator) || + IsEqualIID(riid, &IID_IUnknown)) + { + *out = &source->IMFMediaSource_iface; + } + else if (IsEqualIID(riid, &IID_IMFGetService)) + { + *out = &source->IMFGetService_iface; + } + else + { + FIXME("%s, %p.\n", debugstr_guid(riid), out); + *out = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown*)*out); + return S_OK; +} + +static ULONG WINAPI media_source_AddRef(IMFMediaSource *iface) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + ULONG ref = InterlockedIncrement(&source->ref); + + TRACE("%p, refcount %lu.\n", iface, ref); + + return ref; +} + +static ULONG WINAPI media_source_Release(IMFMediaSource *iface) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + ULONG ref = InterlockedDecrement(&source->ref); + + TRACE("%p, refcount %lu.\n", iface, ref); + + if (!ref) + { + IMFMediaSource_Shutdown(iface); + MFUnlockWorkQueue(source->async_commands_queue); + IMFPresentationDescriptor_Release(source->pres_desc); + IMFMediaEventQueue_Release(source->event_queue); + IMFByteStream_Release(source->byte_stream); + wg_parser_destroy(source->wg_parser); + source->cs.DebugInfo->Spare[0] = 0; + DeleteCriticalSection(&source->cs); + free(source); + } + + return ref; +} + +static HRESULT WINAPI media_source_GetEvent(IMFMediaSource *iface, DWORD flags, IMFMediaEvent **event) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + + TRACE("%p, %#lx, %p.\n", iface, flags, event); + + return IMFMediaEventQueue_GetEvent(source->event_queue, flags, event); +} + +static HRESULT WINAPI media_source_BeginGetEvent(IMFMediaSource *iface, IMFAsyncCallback *callback, IUnknown *state) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + + TRACE("%p, %p, %p.\n", iface, callback, state); + + return IMFMediaEventQueue_BeginGetEvent(source->event_queue, callback, state); +} + +static HRESULT WINAPI media_source_EndGetEvent(IMFMediaSource *iface, IMFAsyncResult *result, IMFMediaEvent **event) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + + TRACE("%p, %p, %p.\n", iface, result, event); + + return IMFMediaEventQueue_EndGetEvent(source->event_queue, result, event); +} + +static HRESULT WINAPI media_source_QueueEvent(IMFMediaSource *iface, MediaEventType event_type, REFGUID ext_type, + HRESULT hr, const PROPVARIANT *value) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + + TRACE("%p, %lu, %s, %#lx, %p.\n", iface, event_type, debugstr_guid(ext_type), hr, value); + + return IMFMediaEventQueue_QueueEventParamVar(source->event_queue, event_type, ext_type, hr, value); +} + +static HRESULT WINAPI media_source_GetCharacteristics(IMFMediaSource *iface, DWORD *characteristics) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + HRESULT hr = S_OK; + + TRACE("%p, %p.\n", iface, characteristics); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + hr = MF_E_SHUTDOWN; + else + *characteristics = MFMEDIASOURCE_CAN_SEEK | MFMEDIASOURCE_CAN_PAUSE; + + LeaveCriticalSection(&source->cs); + + return hr; +} + +static HRESULT WINAPI media_source_CreatePresentationDescriptor(IMFMediaSource *iface, IMFPresentationDescriptor **descriptor) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + HRESULT hr; + + TRACE("%p, %p.\n", iface, descriptor); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + hr = MF_E_SHUTDOWN; + else + hr = IMFPresentationDescriptor_Clone(source->pres_desc, descriptor); + + LeaveCriticalSection(&source->cs); + + return hr; +} + +static HRESULT WINAPI media_source_Start(IMFMediaSource *iface, IMFPresentationDescriptor *descriptor, + const GUID *time_format, const PROPVARIANT *position) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + struct source_async_command *command; + HRESULT hr; + + TRACE("%p, %p, %p, %p.\n", iface, descriptor, time_format, position); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + hr = MF_E_SHUTDOWN; + else if (!(IsEqualIID(time_format, &GUID_NULL))) + hr = MF_E_UNSUPPORTED_TIME_FORMAT; + else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_START, &command))) + { + command->u.start.descriptor = descriptor; + command->u.start.format = *time_format; + PropVariantCopy(&command->u.start.position, position); + + hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, &command->IUnknown_iface); + } + + LeaveCriticalSection(&source->cs); + + return hr; +} + +static HRESULT WINAPI media_source_Stop(IMFMediaSource *iface) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + struct source_async_command *command; + HRESULT hr; + + TRACE("%p.\n", iface); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + hr = MF_E_SHUTDOWN; + else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_STOP, &command))) + hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, &command->IUnknown_iface); + + LeaveCriticalSection(&source->cs); + + return hr; +} + +static HRESULT WINAPI media_source_Pause(IMFMediaSource *iface) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + struct source_async_command *command; + HRESULT hr; + + TRACE("%p.\n", iface); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + hr = MF_E_SHUTDOWN; + else if (source->state != SOURCE_RUNNING) + hr = MF_E_INVALID_STATE_TRANSITION; + else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_PAUSE, &command))) + hr = MFPutWorkItem(source->async_commands_queue, + &source->async_commands_callback, &command->IUnknown_iface); + + LeaveCriticalSection(&source->cs); + + return S_OK; +} + +static HRESULT WINAPI media_source_Shutdown(IMFMediaSource *iface) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + UINT i; + + TRACE("%p.\n", iface); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + { + LeaveCriticalSection(&source->cs); + return MF_E_SHUTDOWN; + } + + source->state = SOURCE_SHUTDOWN; + + for (i = 0; i < source->stream_count; i++) + { + struct media_stream *stream = source->streams[i]; + wg_parser_stream_disable(stream->wg_stream); + while (stream->busy) + SleepConditionVariableCS(&stream->cond, &source->cs, INFINITE); + } + + wg_parser_disconnect(source->wg_parser); + + source->read_thread_shutdown = true; + WaitForSingleObject(source->read_thread, INFINITE); + CloseHandle(source->read_thread); + + IMFMediaEventQueue_Shutdown(source->event_queue); + IMFByteStream_Close(source->byte_stream); + + while (source->stream_count--) + { + struct media_stream *stream = source->streams[source->stream_count]; + IMFMediaEventQueue_Shutdown(stream->event_queue); + IMFMediaStream_Release(&stream->IMFMediaStream_iface); + } + free(source->streams); + + LeaveCriticalSection(&source->cs); + + return S_OK; +} + +static const IMFMediaSourceVtbl IMFMediaSource_vtbl = +{ + media_source_QueryInterface, + media_source_AddRef, + media_source_Release, + media_source_GetEvent, + media_source_BeginGetEvent, + media_source_EndGetEvent, + media_source_QueueEvent, + media_source_GetCharacteristics, + media_source_CreatePresentationDescriptor, + media_source_Start, + media_source_Stop, + media_source_Pause, + media_source_Shutdown, +}; + +HRESULT media_source_create_old(IMFByteStream *bytestream, const WCHAR *uri, IMFMediaSource **out) +{ + BOOL video_selected = FALSE, audio_selected = FALSE; + IMFStreamDescriptor **descriptors = NULL; + unsigned int stream_count = UINT_MAX; + struct media_source *object; + UINT64 total_pres_time = 0; + struct wg_parser *parser; + DWORD bytestream_caps; + uint64_t file_size; + unsigned int i; + HRESULT hr; + + if (FAILED(hr = IMFByteStream_GetCapabilities(bytestream, &bytestream_caps))) + return hr; + + if (!(bytestream_caps & MFBYTESTREAM_IS_SEEKABLE)) + { + FIXME("Non-seekable bytestreams not supported.\n"); + return MF_E_BYTESTREAM_NOT_SEEKABLE; + } + + if (FAILED(hr = IMFByteStream_GetLength(bytestream, &file_size))) + { + FIXME("Failed to get byte stream length, hr %#lx.\n", hr); + return hr; + } + + if (!(object = calloc(1, sizeof(*object)))) + return E_OUTOFMEMORY; + + object->IMFMediaSource_iface.lpVtbl = &IMFMediaSource_vtbl; + object->IMFGetService_iface.lpVtbl = &media_source_get_service_vtbl; + object->IMFRateSupport_iface.lpVtbl = &media_source_rate_support_vtbl; + object->IMFRateControl_iface.lpVtbl = &media_source_rate_control_vtbl; + object->async_commands_callback.lpVtbl = &source_async_commands_callback_vtbl; + object->ref = 1; + object->byte_stream = bytestream; + IMFByteStream_AddRef(bytestream); + object->rate = 1.0f; + InitializeCriticalSection(&object->cs); + object->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": cs"); + + if (FAILED(hr = MFCreateEventQueue(&object->event_queue))) + goto fail; + + if (FAILED(hr = MFAllocateWorkQueue(&object->async_commands_queue))) + goto fail; + + if (!(parser = wg_parser_create(uri ? WG_PARSER_URIDECODEBIN : WG_PARSER_DECODEBIN, false))) + { + hr = E_OUTOFMEMORY; + goto fail; + } + object->wg_parser = parser; + + object->read_thread = CreateThread(NULL, 0, read_thread, object, 0, NULL); + + object->state = SOURCE_OPENING; + + if (FAILED(hr = wg_parser_connect(parser, file_size, uri))) + goto fail; + + stream_count = wg_parser_get_stream_count(parser); + + if (!(object->streams = calloc(stream_count, sizeof(*object->streams)))) + { + hr = E_OUTOFMEMORY; + goto fail; + } + + for (i = 0; i < stream_count; ++i) + { + if (FAILED(hr = media_stream_create(&object->IMFMediaSource_iface, i, &object->streams[i]))) + goto fail; + + if (FAILED(hr = media_stream_init_desc(object->streams[i]))) + { + ERR("Failed to finish initialization of media stream %p, hr %#lx.\n", object->streams[i], hr); + IMFMediaSource_Release(object->streams[i]->media_source); + IMFMediaEventQueue_Release(object->streams[i]->event_queue); + free(object->streams[i]); + goto fail; + } + + object->stream_count++; + } + + /* init presentation descriptor */ + + descriptors = malloc(object->stream_count * sizeof(IMFStreamDescriptor *)); + for (i = 0; i < object->stream_count; i++) + { + static const struct + { + enum wg_parser_tag tag; + const GUID *mf_attr; + } + tags[] = + { + {WG_PARSER_TAG_LANGUAGE, &MF_SD_LANGUAGE}, + {WG_PARSER_TAG_NAME, &MF_SD_STREAM_NAME}, + }; + IMFStreamDescriptor **descriptor = descriptors + object->stream_count - 1 - i; + unsigned int j; + WCHAR *strW; + DWORD len; + char *str; + + IMFMediaStream_GetStreamDescriptor(&object->streams[i]->IMFMediaStream_iface, descriptor); + + for (j = 0; j < ARRAY_SIZE(tags); ++j) + { + if (!(str = wg_parser_stream_get_tag(object->streams[i]->wg_stream, tags[j].tag))) + continue; + if (!(len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0))) + { + free(str); + continue; + } + strW = malloc(len * sizeof(*strW)); + if (MultiByteToWideChar(CP_UTF8, 0, str, -1, strW, len)) + IMFStreamDescriptor_SetString(*descriptor, tags[j].mf_attr, strW); + free(strW); + free(str); + } + } + + if (FAILED(hr = MFCreatePresentationDescriptor(object->stream_count, descriptors, &object->pres_desc))) + goto fail; + + /* Select one of each major type. */ + for (i = 0; i < object->stream_count; i++) + { + IMFMediaTypeHandler *handler; + GUID major_type; + BOOL select_stream = FALSE; + + IMFStreamDescriptor_GetMediaTypeHandler(descriptors[i], &handler); + IMFMediaTypeHandler_GetMajorType(handler, &major_type); + if (IsEqualGUID(&major_type, &MFMediaType_Video) && !video_selected) + { + select_stream = TRUE; + video_selected = TRUE; + } + if (IsEqualGUID(&major_type, &MFMediaType_Audio) && !audio_selected) + { + select_stream = TRUE; + audio_selected = TRUE; + } + if (select_stream) + IMFPresentationDescriptor_SelectStream(object->pres_desc, i); + IMFMediaTypeHandler_Release(handler); + IMFStreamDescriptor_Release(descriptors[i]); + } + free(descriptors); + descriptors = NULL; + + for (i = 0; i < object->stream_count; i++) + total_pres_time = max(total_pres_time, + wg_parser_stream_get_duration(object->streams[i]->wg_stream)); + + if (object->stream_count) + IMFPresentationDescriptor_SetUINT64(object->pres_desc, &MF_PD_DURATION, total_pres_time); + + object->state = SOURCE_STOPPED; + + *out = &object->IMFMediaSource_iface; + return S_OK; + + fail: + WARN("Failed to construct MFMediaSource, hr %#lx.\n", hr); + + if (descriptors) + { + for (i = 0; i < object->stream_count; i++) + IMFStreamDescriptor_Release(descriptors[i]); + free(descriptors); + } + + while (object->streams && object->stream_count--) + { + struct media_stream *stream = object->streams[object->stream_count]; + IMFMediaStream_Release(&stream->IMFMediaStream_iface); + } + free(object->streams); + + if (stream_count != UINT_MAX) + wg_parser_disconnect(object->wg_parser); + if (object->read_thread) + { + object->read_thread_shutdown = true; + WaitForSingleObject(object->read_thread, INFINITE); + CloseHandle(object->read_thread); + } + if (object->wg_parser) + wg_parser_destroy(object->wg_parser); + if (object->async_commands_queue) + MFUnlockWorkQueue(object->async_commands_queue); + if (object->event_queue) + IMFMediaEventQueue_Release(object->event_queue); + IMFByteStream_Release(object->byte_stream); + free(object); + return hr; +} + +HRESULT media_source_create_from_url(const WCHAR *url, IMFMediaSource **out) +{ + IMFByteStream *bytestream; + IStream *stream; + HRESULT hr; + + if (FAILED(hr = CreateStreamOnHGlobal(0, TRUE, &stream))) + return hr; + + hr = MFCreateMFByteStreamOnStream(stream, &bytestream); + IStream_Release(stream); + if (FAILED(hr)) + return hr; + + hr = media_source_create_old(bytestream, url, out); + IMFByteStream_Release(bytestream); + + return hr; +} diff --git a/dlls/winegstreamer/mf_handler.c b/dlls/winegstreamer/mf_handler.c new file mode 100644 index 00000000000..bd1bd810c96 --- /dev/null +++ b/dlls/winegstreamer/mf_handler.c @@ -0,0 +1,606 @@ +/* + * Copyright 2020 Derek Lesho + * Copyright 2020 Zebediah Figura for CodeWeavers + * + * 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 + */ + +#include + +#define COBJMACROS + +#include "windef.h" +#include "winbase.h" +#include "mfidl.h" +#include "mferror.h" +#include "mfapi.h" +#include "gst_private.h" + +#include "wine/debug.h" +#include "wine/list.h" + +WINE_DEFAULT_DEBUG_CHANNEL(mfplat); + +struct result_entry +{ + struct list entry; + IMFAsyncResult *result; + MF_OBJECT_TYPE type; + IUnknown *object; +}; + +static HRESULT result_entry_create(IMFAsyncResult *result, MF_OBJECT_TYPE type, + IUnknown *object, struct result_entry **out) +{ + struct result_entry *entry; + + if (!(entry = malloc(sizeof(*entry)))) + return E_OUTOFMEMORY; + + IMFAsyncResult_AddRef((entry->result = result)); + entry->type = type; + if ((entry->object = object)) + IUnknown_AddRef(entry->object); + + *out = entry; + return S_OK; +} + +static void result_entry_destroy(struct result_entry *entry) +{ + IMFAsyncResult_Release(entry->result); + if (entry->object) + IUnknown_Release(entry->object); + free(entry); +} + +struct async_create_object +{ + IUnknown IUnknown_iface; + LONG refcount; + + IMFByteStream *stream; + WCHAR *url; + DWORD flags; + IMFAsyncResult *result; + + UINT64 size; + BYTE buffer[]; +}; + +C_ASSERT(sizeof(struct async_create_object) == offsetof(struct async_create_object, buffer[0])); + +static struct async_create_object *impl_from_IUnknown(IUnknown *iface) +{ + if (!iface) return NULL; + return CONTAINING_RECORD(iface, struct async_create_object, IUnknown_iface); +} + +static HRESULT WINAPI async_create_object_QueryInterface(IUnknown *iface, REFIID riid, void **obj) +{ + TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); + + if (IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IUnknown_AddRef(iface); + return S_OK; + } + + WARN("Unsupported %s.\n", debugstr_guid(riid)); + *obj = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI async_create_object_AddRef(IUnknown *iface) +{ + struct async_create_object *async = impl_from_IUnknown(iface); + ULONG refcount = InterlockedIncrement(&async->refcount); + TRACE("%p, refcount %lu.\n", iface, refcount); + return refcount; +} + +static ULONG WINAPI async_create_object_Release(IUnknown *iface) +{ + struct async_create_object *async = impl_from_IUnknown(iface); + ULONG refcount = InterlockedDecrement(&async->refcount); + + TRACE("%p, refcount %lu.\n", iface, refcount); + + if (!refcount) + { + IMFAsyncResult_Release(async->result); + if (async->stream) + IMFByteStream_Release(async->stream); + free(async->url); + free(async); + } + + return refcount; +} + +static const IUnknownVtbl async_create_object_vtbl = +{ + async_create_object_QueryInterface, + async_create_object_AddRef, + async_create_object_Release, +}; + +static HRESULT async_create_object_create(DWORD flags, IMFByteStream *stream, const WCHAR *url, + IMFAsyncResult *result, UINT size, IUnknown **out, BYTE **buffer) +{ + WCHAR *tmp_url = url ? wcsdup(url) : NULL; + struct async_create_object *impl; + + if (!stream && !tmp_url) + return E_INVALIDARG; + if (!(impl = calloc(1, offsetof(struct async_create_object, buffer[size])))) + { + free(tmp_url); + return E_OUTOFMEMORY; + } + + impl->IUnknown_iface.lpVtbl = &async_create_object_vtbl; + impl->refcount = 1; + impl->flags = flags; + if ((impl->stream = stream)) + IMFByteStream_AddRef(impl->stream); + impl->url = tmp_url; + IMFAsyncResult_AddRef((impl->result = result)); + + *buffer = impl->buffer; + *out = &impl->IUnknown_iface; + return S_OK; +} + +static HRESULT async_create_object_complete(struct async_create_object *async, + struct list *results, CRITICAL_SECTION *results_cs) +{ + IUnknown *object; + HRESULT hr; + + if (async->flags & MF_RESOLUTION_MEDIASOURCE) + { + const char *sgi, *env = getenv("WINE_NEW_MEDIA_SOURCE"); + if (!env && (sgi = getenv("SteamGameId")) && (!strcmp(sgi, "692850"))) env = "1"; + + if (!async->stream) + hr = media_source_create_from_url(async->url, (IMFMediaSource **)&object); + else if (!env || !atoi(env)) + hr = media_source_create_old(async->stream, NULL, (IMFMediaSource **)&object); + else if (FAILED(hr = media_source_create(async->stream, async->url, async->buffer, async->size, (IMFMediaSource **)&object))) + { + FIXME("Failed to create new media source, falling back to old implementation, hr %#lx\n", hr); + hr = media_source_create_old(async->stream, NULL, (IMFMediaSource **)&object); + } + } + else + { + FIXME("Unhandled flags %#lx.\n", async->flags); + hr = E_NOTIMPL; + } + + if (FAILED(hr)) + WARN("Failed to create object, hr %#lx.\n", hr); + else + { + struct result_entry *entry; + + if (FAILED(hr = result_entry_create(async->result, MF_OBJECT_MEDIASOURCE, object, &entry))) + WARN("Failed to add handler result, hr %#lx\n", hr); + else + { + EnterCriticalSection(results_cs); + list_add_tail(results, &entry->entry); + LeaveCriticalSection(results_cs); + } + + IUnknown_Release(object); + } + + IMFAsyncResult_SetStatus(async->result, hr); + return MFInvokeCallback(async->result); +} + +struct handler +{ + IMFAsyncCallback IMFAsyncCallback_iface; + IMFByteStreamHandler IMFByteStreamHandler_iface; + IMFSchemeHandler IMFSchemeHandler_iface; + LONG refcount; + struct list results; + CRITICAL_SECTION cs; +}; + +static HRESULT handler_begin_create_object(struct handler *handler, DWORD flags, + IMFByteStream *stream, const WCHAR *url, IMFAsyncResult *result) +{ + UINT size = 0x2000; + IUnknown *async; + HRESULT hr; + BYTE *buffer; + + if (SUCCEEDED(hr = async_create_object_create(flags, stream, url, result, size, &async, &buffer))) + { + if (stream && FAILED(hr = IMFByteStream_BeginRead(stream, buffer, size, &handler->IMFAsyncCallback_iface, async))) + WARN("Failed to begin reading from stream, hr %#lx\n", hr); + if (!stream && FAILED(hr = MFPutWorkItem(MFASYNC_CALLBACK_QUEUE_IO, &handler->IMFAsyncCallback_iface, async))) + WARN("Failed to queue async work item, hr %#lx\n", hr); + IUnknown_Release(async); + } + + return hr; +} + +static struct result_entry *handler_find_result_entry(struct handler *handler, IMFAsyncResult *result) +{ + struct result_entry *entry; + + EnterCriticalSection(&handler->cs); + LIST_FOR_EACH_ENTRY(entry, &handler->results, struct result_entry, entry) + { + if (result == entry->result) + { + list_remove(&entry->entry); + LeaveCriticalSection(&handler->cs); + return entry; + } + } + LeaveCriticalSection(&handler->cs); + + return NULL; +} + +static HRESULT handler_end_create_object(struct handler *handler, + IMFAsyncResult *result, MF_OBJECT_TYPE *type, IUnknown **object) +{ + struct result_entry *entry; + HRESULT hr; + + if (!(entry = handler_find_result_entry(handler, result))) + { + *type = MF_OBJECT_INVALID; + *object = NULL; + return MF_E_UNEXPECTED; + } + + hr = IMFAsyncResult_GetStatus(entry->result); + *type = entry->type; + *object = entry->object; + entry->object = NULL; + + result_entry_destroy(entry); + return hr; +} + +static HRESULT handler_cancel_object_creation(struct handler *handler, IUnknown *cookie) +{ + IMFAsyncResult *result = (IMFAsyncResult *)cookie; + struct result_entry *entry; + + if (!(entry = handler_find_result_entry(handler, result))) + return MF_E_UNEXPECTED; + + result_entry_destroy(entry); + return S_OK; +} + +static struct handler *impl_from_IMFAsyncCallback(IMFAsyncCallback *iface) +{ + return CONTAINING_RECORD(iface, struct handler, IMFAsyncCallback_iface); +} + +static struct handler *impl_from_IMFByteStreamHandler(IMFByteStreamHandler *iface) +{ + return CONTAINING_RECORD(iface, struct handler, IMFByteStreamHandler_iface); +} + +static struct handler *impl_from_IMFSchemeHandler(IMFSchemeHandler *iface) +{ + return CONTAINING_RECORD(iface, struct handler, IMFSchemeHandler_iface); +} + +static HRESULT WINAPI async_callback_QueryInterface(IMFAsyncCallback *iface, REFIID riid, void **obj) +{ + if (IsEqualIID(riid, &IID_IMFAsyncCallback) + || IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IMFAsyncCallback_AddRef(iface); + return S_OK; + } + + WARN("Unsupported %s.\n", debugstr_guid(riid)); + *obj = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI async_callback_AddRef(IMFAsyncCallback *iface) +{ + struct handler *handler = impl_from_IMFAsyncCallback(iface); + ULONG refcount = InterlockedIncrement(&handler->refcount); + TRACE("%p, refcount %lu.\n", handler, refcount); + return refcount; +} + +static ULONG WINAPI async_callback_Release(IMFAsyncCallback *iface) +{ + struct handler *handler = impl_from_IMFAsyncCallback(iface); + ULONG refcount = InterlockedDecrement(&handler->refcount); + struct result_entry *entry, *next; + + TRACE("%p, refcount %lu.\n", iface, refcount); + + if (!refcount) + { + LIST_FOR_EACH_ENTRY_SAFE(entry, next, &handler->results, struct result_entry, entry) + result_entry_destroy(entry); + DeleteCriticalSection(&handler->cs); + free(handler); + } + + return refcount; +} + +static HRESULT WINAPI async_callback_GetParameters(IMFAsyncCallback *iface, DWORD *flags, DWORD *queue) +{ + *flags = 0; + *queue = MFASYNC_CALLBACK_QUEUE_IO; + return S_OK; +} + +static HRESULT WINAPI async_callback_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) +{ + struct handler *handler = impl_from_IMFAsyncCallback(iface); + struct async_create_object *async; + ULONG size = 0; + HRESULT hr; + + TRACE("iface %p, result %p\n", iface, result); + + if (!(async = impl_from_IUnknown(IMFAsyncResult_GetStateNoAddRef(result)))) + { + WARN("Expected context set for callee result.\n"); + return E_FAIL; + } + + if (async->stream && FAILED(hr = IMFByteStream_EndRead(async->stream, result, &size))) + WARN("Failed to complete stream read, hr %#lx\n", hr); + async->size = size; + + return async_create_object_complete(async, &handler->results, &handler->cs); +} + +static const IMFAsyncCallbackVtbl async_callback_vtbl = +{ + async_callback_QueryInterface, + async_callback_AddRef, + async_callback_Release, + async_callback_GetParameters, + async_callback_Invoke, +}; + +static HRESULT WINAPI stream_handler_QueryInterface(IMFByteStreamHandler *iface, REFIID riid, void **obj) +{ + TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); + + if (IsEqualIID(riid, &IID_IMFByteStreamHandler) + || IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IMFByteStreamHandler_AddRef(iface); + return S_OK; + } + + WARN("Unsupported %s.\n", debugstr_guid(riid)); + *obj = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI stream_handler_AddRef(IMFByteStreamHandler *iface) +{ + struct handler *handler = impl_from_IMFByteStreamHandler(iface); + return IMFAsyncCallback_AddRef(&handler->IMFAsyncCallback_iface); +} + +static ULONG WINAPI stream_handler_Release(IMFByteStreamHandler *iface) +{ + struct handler *handler = impl_from_IMFByteStreamHandler(iface); + return IMFAsyncCallback_Release(&handler->IMFAsyncCallback_iface); +} + +static HRESULT WINAPI stream_handler_BeginCreateObject(IMFByteStreamHandler *iface, + IMFByteStream *stream, const WCHAR *url, DWORD flags, IPropertyStore *props, + IUnknown **cookie, IMFAsyncCallback *callback, IUnknown *state) +{ + struct handler *handler = impl_from_IMFByteStreamHandler(iface); + IMFAsyncResult *result; + HRESULT hr; + + TRACE("%p, %s, %#lx, %p, %p, %p, %p.\n", iface, debugstr_w(url), flags, props, cookie, callback, state); + + if (cookie) + *cookie = NULL; + + if (FAILED(hr = MFCreateAsyncResult((IUnknown *)iface, callback, state, &result))) + return hr; + + if (SUCCEEDED(hr = handler_begin_create_object(handler, flags, stream, url, result)) && cookie) + { + *cookie = (IUnknown *)result; + IUnknown_AddRef(*cookie); + } + + IMFAsyncResult_Release(result); + + return hr; +} + +static HRESULT WINAPI stream_handler_EndCreateObject(IMFByteStreamHandler *iface, + IMFAsyncResult *result, MF_OBJECT_TYPE *type, IUnknown **object) +{ + struct handler *handler = impl_from_IMFByteStreamHandler(iface); + TRACE("%p, %p, %p, %p.\n", iface, result, type, object); + return handler_end_create_object(handler, result, type, object); +} + +static HRESULT WINAPI stream_handler_CancelObjectCreation(IMFByteStreamHandler *iface, IUnknown *cookie) +{ + struct handler *handler = impl_from_IMFByteStreamHandler(iface); + TRACE("%p, %p.\n", iface, cookie); + return handler_cancel_object_creation(handler, cookie); +} + +static HRESULT WINAPI stream_handler_GetMaxNumberOfBytesRequiredForResolution( + IMFByteStreamHandler *iface, QWORD *bytes) +{ + FIXME("stub (%p %p)\n", iface, bytes); + return E_NOTIMPL; +} + +static const IMFByteStreamHandlerVtbl stream_handler_vtbl = +{ + stream_handler_QueryInterface, + stream_handler_AddRef, + stream_handler_Release, + stream_handler_BeginCreateObject, + stream_handler_EndCreateObject, + stream_handler_CancelObjectCreation, + stream_handler_GetMaxNumberOfBytesRequiredForResolution, +}; + +static HRESULT WINAPI scheme_handler_QueryInterface(IMFSchemeHandler *iface, REFIID riid, void **obj) +{ + TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); + + if (IsEqualIID(riid, &IID_IMFSchemeHandler) + || IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IMFSchemeHandler_AddRef(iface); + return S_OK; + } + + WARN("Unsupported %s.\n", debugstr_guid(riid)); + *obj = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI scheme_handler_AddRef(IMFSchemeHandler *iface) +{ + struct handler *handler = impl_from_IMFSchemeHandler(iface); + return IMFAsyncCallback_AddRef(&handler->IMFAsyncCallback_iface); +} + +static ULONG WINAPI scheme_handler_Release(IMFSchemeHandler *iface) +{ + struct handler *handler = impl_from_IMFSchemeHandler(iface); + return IMFAsyncCallback_Release(&handler->IMFAsyncCallback_iface); +} + +static HRESULT WINAPI scheme_handler_BeginCreateObject(IMFSchemeHandler *iface, const WCHAR *url, + DWORD flags, IPropertyStore *props, IUnknown **cookie, IMFAsyncCallback *callback, IUnknown *state) +{ + struct handler *handler = impl_from_IMFSchemeHandler(iface); + IMFAsyncResult *result; + HRESULT hr; + + TRACE("%p, %s, %#lx, %p, %p, %p, %p.\n", iface, debugstr_w(url), flags, props, cookie, callback, state); + + if (cookie) + *cookie = NULL; + + if (FAILED(hr = MFCreateAsyncResult((IUnknown *)iface, callback, state, &result))) + return hr; + + if (SUCCEEDED(hr = handler_begin_create_object(handler, flags, NULL, url, result)) && cookie) + { + *cookie = (IUnknown *)result; + IUnknown_AddRef(*cookie); + } + + IMFAsyncResult_Release(result); + + return hr; +} + +static HRESULT WINAPI scheme_handler_EndCreateObject(IMFSchemeHandler *iface, + IMFAsyncResult *result, MF_OBJECT_TYPE *type, IUnknown **object) +{ + struct handler *handler = impl_from_IMFSchemeHandler(iface); + TRACE("%p, %p, %p, %p.\n", iface, result, type, object); + return handler_end_create_object(handler, result, type, object); +} + +static HRESULT WINAPI scheme_handler_CancelObjectCreation(IMFSchemeHandler *iface, IUnknown *cookie) +{ + struct handler *handler = impl_from_IMFSchemeHandler(iface); + TRACE("%p, %p.\n", iface, cookie); + return handler_cancel_object_creation(handler, cookie); +} + +static const IMFSchemeHandlerVtbl scheme_handler_vtbl = +{ + scheme_handler_QueryInterface, + scheme_handler_AddRef, + scheme_handler_Release, + scheme_handler_BeginCreateObject, + scheme_handler_EndCreateObject, + scheme_handler_CancelObjectCreation, +}; + +HRESULT winegstreamer_stream_handler_create(REFIID riid, void **obj) +{ + struct handler *handler; + HRESULT hr; + + TRACE("%s, %p.\n", debugstr_guid(riid), obj); + + if (!(handler = calloc(1, sizeof(*handler)))) + return E_OUTOFMEMORY; + + handler->IMFAsyncCallback_iface.lpVtbl = &async_callback_vtbl; + handler->IMFByteStreamHandler_iface.lpVtbl = &stream_handler_vtbl; + handler->refcount = 1; + list_init(&handler->results); + InitializeCriticalSection(&handler->cs); + + hr = IMFByteStreamHandler_QueryInterface(&handler->IMFByteStreamHandler_iface, riid, obj); + IMFAsyncCallback_Release(&handler->IMFAsyncCallback_iface); + + return hr; +} + +HRESULT winegstreamer_scheme_handler_create(REFIID riid, void **obj) +{ + struct handler *handler; + HRESULT hr; + + TRACE("%s, %p.\n", debugstr_guid(riid), obj); + + if (!(handler = calloc(1, sizeof(*handler)))) + return E_OUTOFMEMORY; + + handler->IMFAsyncCallback_iface.lpVtbl = &async_callback_vtbl; + handler->IMFSchemeHandler_iface.lpVtbl = &scheme_handler_vtbl; + handler->refcount = 1; + list_init(&handler->results); + InitializeCriticalSection(&handler->cs); + + hr = IMFSchemeHandler_QueryInterface(&handler->IMFSchemeHandler_iface, riid, obj); + IMFAsyncCallback_Release(&handler->IMFAsyncCallback_iface); + + return hr; +} diff --git a/dlls/winegstreamer/mfplat.c b/dlls/winegstreamer/mfplat.c index c1deffbb1cc..09b0cc9eb2a 100644 --- a/dlls/winegstreamer/mfplat.c +++ b/dlls/winegstreamer/mfplat.c @@ -121,8 +121,7 @@ static const IClassFactoryVtbl class_factory_vtbl = }; static const GUID CLSID_GStreamerByteStreamHandler = {0x317df618, 0x5e5a, 0x468a, {0x9f, 0x15, 0xd8, 0x27, 0xa9, 0xa0, 0x81, 0x62}}; - -static const GUID CLSID_GStreamerSchemePlugin = {0x587eeb6a,0x7336,0x4ebd,{0xa4,0xf2,0x91,0xc9,0x48,0xde,0x62,0x2c}}; +static const GUID CLSID_GStreamerSchemeHandler = {0x587eeb6a,0x7336,0x4ebd,{0xa4,0xf2,0x91,0xc9,0x48,0xde,0x62,0x2c}}; static const struct class_object { @@ -133,9 +132,9 @@ class_objects[] = { { &CLSID_VideoProcessorMFT, &video_processor_create }, { &CLSID_GStreamerByteStreamHandler, &winegstreamer_stream_handler_create }, + { &CLSID_GStreamerSchemeHandler, &winegstreamer_scheme_handler_create }, { &CLSID_MSAACDecMFT, &aac_decoder_create }, { &CLSID_MSH264DecoderMFT, &h264_decoder_create }, - { &CLSID_GStreamerSchemePlugin, &gstreamer_scheme_handler_construct }, }; HRESULT mfplat_get_class_object(REFCLSID rclsid, REFIID riid, void **obj) @@ -519,27 +518,35 @@ static IMFMediaType *mf_media_type_from_wg_format_video(const struct wg_format * { if (format->u.video.format == video_formats[i].format) { + unsigned int stride = wg_format_get_stride(format); + int32_t height = abs(format->u.video.height); + int32_t width = format->u.video.width; + if (FAILED(MFCreateMediaType(&type))) return NULL; IMFMediaType_SetGUID(type, &MF_MT_MAJOR_TYPE, &MFMediaType_Video); IMFMediaType_SetGUID(type, &MF_MT_SUBTYPE, video_formats[i].subtype); - IMFMediaType_SetUINT64(type, &MF_MT_FRAME_SIZE, - make_uint64(format->u.video.width, format->u.video.height)); + IMFMediaType_SetUINT64(type, &MF_MT_FRAME_SIZE, make_uint64(width, height)); IMFMediaType_SetUINT64(type, &MF_MT_FRAME_RATE, make_uint64(format->u.video.fps_n, format->u.video.fps_d)); IMFMediaType_SetUINT32(type, &MF_MT_COMPRESSED, FALSE); IMFMediaType_SetUINT32(type, &MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); IMFMediaType_SetUINT32(type, &MF_MT_VIDEO_ROTATION, MFVideoRotationFormat_0); - if (!IsRectEmpty(&format->u.video.padding)) + if (format->u.video.height < 0) + stride = -stride; + IMFMediaType_SetUINT32(type, &MF_MT_DEFAULT_STRIDE, stride); + + if (format->u.video.padding.left || format->u.video.padding.right + || format->u.video.padding.top || format->u.video.padding.bottom) { MFVideoArea aperture = { .OffsetX = {.value = format->u.video.padding.left}, .OffsetY = {.value = format->u.video.padding.top}, - .Area.cx = format->u.video.width - format->u.video.padding.right - format->u.video.padding.left, - .Area.cy = format->u.video.height - format->u.video.padding.bottom - format->u.video.padding.top, + .Area.cx = width - format->u.video.padding.right - format->u.video.padding.left, + .Area.cy = height - format->u.video.padding.bottom - format->u.video.padding.top, }; IMFMediaType_SetBlob(type, &MF_MT_MINIMUM_DISPLAY_APERTURE, @@ -681,12 +688,24 @@ static void mf_media_type_to_wg_format_audio_mpeg4(IMFMediaType *type, const GUI format->u.audio_mpeg4.codec_data_len = codec_data_size; } +static enum wg_video_format mf_video_format_to_wg(const GUID *subtype) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(video_formats); ++i) + { + if (IsEqualGUID(subtype, video_formats[i].subtype)) + return video_formats[i].format; + } + FIXME("Unrecognized video subtype %s.\n", debugstr_guid(subtype)); + return WG_VIDEO_FORMAT_UNKNOWN; +} + static void mf_media_type_to_wg_format_video(IMFMediaType *type, const GUID *subtype, struct wg_format *format) { UINT64 frame_rate, frame_size; MFVideoArea aperture; - unsigned int i; - UINT32 size; + UINT32 size, stride; if (FAILED(IMFMediaType_GetUINT64(type, &MF_MT_FRAME_SIZE, &frame_size))) { @@ -715,15 +734,17 @@ static void mf_media_type_to_wg_format_video(IMFMediaType *type, const GUID *sub format->u.video.fps_d = (UINT32)frame_rate; } - for (i = 0; i < ARRAY_SIZE(video_formats); ++i) + format->u.video.format = mf_video_format_to_wg(subtype); + + if (SUCCEEDED(IMFMediaType_GetUINT32(type, &MF_MT_DEFAULT_STRIDE, &stride))) { - if (IsEqualGUID(subtype, video_formats[i].subtype)) - { - format->u.video.format = video_formats[i].format; - return; - } + if ((int)stride < 0) + format->u.video.height = -format->u.video.height; + } + else if (wg_video_format_is_rgb(format->u.video.format)) + { + format->u.video.height = -format->u.video.height; } - FIXME("Unrecognized video subtype %s.\n", debugstr_guid(subtype)); } static void mf_media_type_to_wg_format_audio_wma(IMFMediaType *type, const GUID *subtype, struct wg_format *format) diff --git a/dlls/winegstreamer/quartz_parser.c b/dlls/winegstreamer/quartz_parser.c index 4f91b2584e2..889a9223d3e 100644 --- a/dlls/winegstreamer/quartz_parser.c +++ b/dlls/winegstreamer/quartz_parser.c @@ -343,7 +343,7 @@ unsigned int wg_format_get_max_size(const struct wg_format *format) { case WG_MAJOR_TYPE_VIDEO: { - unsigned int width = format->u.video.width, height = format->u.video.height; + unsigned int width = format->u.video.width, height = abs(format->u.video.height); switch (format->u.video.format) { @@ -546,7 +546,7 @@ static bool amt_from_wg_format_video(AM_MEDIA_TYPE *mt, const struct wg_format * if (wm) { - SetRect(&video_format->rcSource, 0, 0, format->u.video.width, format->u.video.height); + SetRect(&video_format->rcSource, 0, 0, format->u.video.width, abs(format->u.video.height)); video_format->rcTarget = video_format->rcSource; } if ((frame_time = MulDiv(10000000, format->u.video.fps_d, format->u.video.fps_n)) != -1) @@ -554,6 +554,8 @@ static bool amt_from_wg_format_video(AM_MEDIA_TYPE *mt, const struct wg_format * video_format->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); video_format->bmiHeader.biWidth = format->u.video.width; video_format->bmiHeader.biHeight = format->u.video.height; + if (wg_video_format_is_rgb(format->u.video.format)) + video_format->bmiHeader.biHeight = -format->u.video.height; video_format->bmiHeader.biPlanes = 1; video_format->bmiHeader.biBitCount = wg_video_format_get_depth(format->u.video.format); video_format->bmiHeader.biCompression = wg_video_format_get_compression(format->u.video.format); @@ -826,6 +828,8 @@ static bool amt_to_wg_format_video(const AM_MEDIA_TYPE *mt, struct wg_format *fo if (IsEqualGUID(&mt->subtype, format_map[i].subtype)) { format->u.video.format = format_map[i].format; + if (wg_video_format_is_rgb(format->u.video.format)) + format->u.video.height = -format->u.video.height; return true; } } @@ -1470,6 +1474,9 @@ static HRESULT decodebin_parser_source_get_media_type(struct parser_source *pin, if (format.major_type == WG_MAJOR_TYPE_VIDEO && index < ARRAY_SIZE(video_formats)) { format.u.video.format = video_formats[index]; + /* Downstream filters probably expect RGB video to be bottom-up. */ + if (format.u.video.height > 0 && wg_video_format_is_rgb(video_formats[index])) + format.u.video.height = -format.u.video.height; if (!amt_from_wg_format(mt, &format, false)) return E_OUTOFMEMORY; return S_OK; diff --git a/dlls/winegstreamer/quartz_transform.c b/dlls/winegstreamer/quartz_transform.c index 09ad4862410..84f6bbd6361 100644 --- a/dlls/winegstreamer/quartz_transform.c +++ b/dlls/winegstreamer/quartz_transform.c @@ -98,6 +98,7 @@ static HRESULT transform_init_stream(struct strmbase_filter *iface) { struct transform *filter = impl_from_strmbase_filter(iface); struct wg_format input_format, output_format; + struct wg_transform_attrs attrs = {0}; HRESULT hr; if (filter->source.pin.peer) @@ -111,7 +112,7 @@ static HRESULT transform_init_stream(struct strmbase_filter *iface) if (FAILED(hr = wg_sample_queue_create(&filter->sample_queue))) return hr; - filter->transform = wg_transform_create(&input_format, &output_format); + filter->transform = wg_transform_create(&input_format, &output_format, &attrs); if (!filter->transform) { wg_sample_queue_destroy(filter->sample_queue); @@ -710,11 +711,12 @@ HRESULT mpeg_audio_codec_create(IUnknown *outer, IUnknown **out) .rate = 44100, }, }; + struct wg_transform_attrs attrs = {0}; struct wg_transform *transform; struct transform *object; HRESULT hr; - transform = wg_transform_create(&input_format, &output_format); + transform = wg_transform_create(&input_format, &output_format, &attrs); if (!transform) { ERR_(winediag)("GStreamer doesn't support MPEG-1 audio decoding, please install appropriate plugins.\n"); @@ -844,11 +846,12 @@ HRESULT mpeg_layer3_decoder_create(IUnknown *outer, IUnknown **out) .rate = 44100, }, }; + struct wg_transform_attrs attrs = {0}; struct wg_transform *transform; struct transform *object; HRESULT hr; - transform = wg_transform_create(&input_format, &output_format); + transform = wg_transform_create(&input_format, &output_format, &attrs); if (!transform) { ERR_(winediag)("GStreamer doesn't support MPEG-1 audio decoding, please install appropriate plugins.\n"); diff --git a/dlls/winegstreamer/resampler.c b/dlls/winegstreamer/resampler.c index 4c8d27856f9..9df0d6fe563 100644 --- a/dlls/winegstreamer/resampler.c +++ b/dlls/winegstreamer/resampler.c @@ -56,6 +56,7 @@ struct resampler static HRESULT try_create_wg_transform(struct resampler *impl) { struct wg_format input_format, output_format; + struct wg_transform_attrs attrs = {.input_queue_length = 15}; if (impl->wg_transform) wg_transform_destroy(impl->wg_transform); @@ -69,7 +70,7 @@ static HRESULT try_create_wg_transform(struct resampler *impl) if (output_format.major_type == WG_MAJOR_TYPE_UNKNOWN) return MF_E_INVALIDMEDIATYPE; - if (!(impl->wg_transform = wg_transform_create(&input_format, &output_format))) + if (!(impl->wg_transform = wg_transform_create(&input_format, &output_format, &attrs))) return E_FAIL; return S_OK; @@ -513,7 +514,7 @@ static HRESULT WINAPI transform_ProcessMessage(IMFTransform *iface, MFT_MESSAGE_ return MF_E_TRANSFORM_TYPE_NOT_SET; if (message == MFT_MESSAGE_COMMAND_DRAIN) - return wg_transform_drain(impl->wg_transform, FALSE); + return wg_transform_drain(impl->wg_transform); FIXME("Ignoring message %#x.\n", message); @@ -906,13 +907,14 @@ HRESULT resampler_create(IUnknown *outer, IUnknown **out) .rate = 44100, }, }; + struct wg_transform_attrs attrs = {0}; struct wg_transform *transform; struct resampler *impl; HRESULT hr; TRACE("outer %p, out %p.\n", outer, out); - if (!(transform = wg_transform_create(&input_format, &output_format))) + if (!(transform = wg_transform_create(&input_format, &output_format, &attrs))) { ERR_(winediag)("GStreamer doesn't support audio resampling, please install appropriate plugins.\n"); return E_FAIL; diff --git a/dlls/winegstreamer/scheme_handler.c b/dlls/winegstreamer/scheme_handler.c deleted file mode 100644 index 1aa36b8b7a5..00000000000 --- a/dlls/winegstreamer/scheme_handler.c +++ /dev/null @@ -1,408 +0,0 @@ -#include - -#define COBJMACROS - -#include "windef.h" -#include "winbase.h" -#include "mfidl.h" -#include "mferror.h" -#include "mfapi.h" -#include "gst_private.h" - -#include "wine/debug.h" -#include "wine/list.h" - -WINE_DEFAULT_DEBUG_CHANNEL(mfplat); - -struct gstreamer_scheme_handler_result -{ - struct list entry; - IMFAsyncResult *result; - IUnknown *object; -}; - -struct gstreamer_scheme_handler -{ - IMFSchemeHandler IMFSchemeHandler_iface; - IMFAsyncCallback IMFAsyncCallback_iface; - LONG refcount; - struct list results; - CRITICAL_SECTION cs; -}; - -static struct gstreamer_scheme_handler *impl_from_IMFSchemeHandler(IMFSchemeHandler *iface) -{ - return CONTAINING_RECORD(iface, struct gstreamer_scheme_handler, IMFSchemeHandler_iface); -} - -static struct gstreamer_scheme_handler *impl_from_IMFAsyncCallback(IMFAsyncCallback *iface) -{ - return CONTAINING_RECORD(iface, struct gstreamer_scheme_handler, IMFAsyncCallback_iface); -} - -static HRESULT WINAPI gstreamer_scheme_handler_QueryIntace(IMFSchemeHandler *iface, REFIID riid, void **obj) -{ - TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); - - if (IsEqualIID(riid, &IID_IMFSchemeHandler) || - IsEqualIID(riid, &IID_IUnknown)) - { - *obj = iface; - IMFSchemeHandler_AddRef(iface); - return S_OK; - } - - WARN("Unsupported %s.\n", debugstr_guid(riid)); - *obj = NULL; - return E_NOINTERFACE; -} - -static ULONG WINAPI gstreamer_scheme_handler_AddRef(IMFSchemeHandler *iface) -{ - struct gstreamer_scheme_handler *handler = impl_from_IMFSchemeHandler(iface); - ULONG refcount = InterlockedIncrement(&handler->refcount); - - TRACE("%p, refcount %lu.\n", handler, refcount); - - return refcount; -} - -static ULONG WINAPI gstreamer_scheme_handler_Release(IMFSchemeHandler *iface) -{ - struct gstreamer_scheme_handler *handler = impl_from_IMFSchemeHandler(iface); - ULONG refcount = InterlockedDecrement(&handler->refcount); - struct gstreamer_scheme_handler_result *result, *next; - - TRACE("%p, refcount %lu.\n", iface, refcount); - - if (!refcount) - { - LIST_FOR_EACH_ENTRY_SAFE(result, next, &handler->results, struct gstreamer_scheme_handler_result, entry) - { - list_remove(&result->entry); - IMFAsyncResult_Release(result->result); - if (result->object) - IUnknown_Release(result->object); - free(result); - } - DeleteCriticalSection(&handler->cs); - free(handler); - } - - return refcount; -} - -struct create_object_context -{ - IUnknown IUnknown_iface; - LONG refcount; - - IPropertyStore *props; - WCHAR *url; - DWORD flags; -}; - -static struct create_object_context *impl_from_IUnknown(IUnknown *iface) -{ - return CONTAINING_RECORD(iface, struct create_object_context, IUnknown_iface); -} - -static HRESULT WINAPI create_object_context_QueryInterface(IUnknown *iface, REFIID riid, void **obj) -{ - TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); - - if (IsEqualIID(riid, &IID_IUnknown)) - { - *obj = iface; - IUnknown_AddRef(iface); - return S_OK; - } - - WARN("Unsupported %s.\n", debugstr_guid(riid)); - *obj = NULL; - return E_NOINTERFACE; -} - -static ULONG WINAPI create_object_context_AddRef(IUnknown *iface) -{ - struct create_object_context *context = impl_from_IUnknown(iface); - ULONG refcount = InterlockedIncrement(&context->refcount); - - TRACE("%p, refcount %lu.\n", iface, refcount); - - return refcount; -} - -static ULONG WINAPI create_object_context_Release(IUnknown *iface) -{ - struct create_object_context *context = impl_from_IUnknown(iface); - ULONG refcount = InterlockedDecrement(&context->refcount); - - TRACE("%p, refcount %lu.\n", iface, refcount); - - if (!refcount) - { - if (context->props) - IPropertyStore_Release(context->props); - free(context->url); - free(context); - } - - return refcount; -} - -static const IUnknownVtbl create_object_context_vtbl = -{ - create_object_context_QueryInterface, - create_object_context_AddRef, - create_object_context_Release, -}; - -static HRESULT WINAPI gstreamer_scheme_handler_BeginCreateObject(IMFSchemeHandler *iface, const WCHAR *url, DWORD flags, - IPropertyStore *props, IUnknown **cancel_cookie, IMFAsyncCallback *callback, IUnknown *state) -{ - struct gstreamer_scheme_handler *handler = impl_from_IMFSchemeHandler(iface); - struct create_object_context *context; - IMFAsyncResult *caller, *item; - HRESULT hr; - - TRACE("%p, %s, %#lx, %p, %p, %p, %p.\n", iface, debugstr_w(url), flags, props, cancel_cookie, callback, state); - - if (cancel_cookie) - *cancel_cookie = NULL; - - if (FAILED(hr = MFCreateAsyncResult(NULL, callback, state, &caller))) - return hr; - - if (!(context = malloc(sizeof(*context)))) - { - IMFAsyncResult_Release(caller); - return E_OUTOFMEMORY; - } - - context->IUnknown_iface.lpVtbl = &create_object_context_vtbl; - context->refcount = 1; - context->props = props; - if (context->props) - IPropertyStore_AddRef(context->props); - context->flags = flags; - context->url = wcsdup(url); - if (!context->url) - { - IMFAsyncResult_Release(caller); - IUnknown_Release(&context->IUnknown_iface); - return E_OUTOFMEMORY; - } - - hr = MFCreateAsyncResult(&context->IUnknown_iface, &handler->IMFAsyncCallback_iface, (IUnknown *)caller, &item); - IUnknown_Release(&context->IUnknown_iface); - if (SUCCEEDED(hr)) - { - if (SUCCEEDED(hr = MFPutWorkItemEx(MFASYNC_CALLBACK_QUEUE_IO, item))) - { - if (cancel_cookie) - { - *cancel_cookie = (IUnknown *)caller; - IUnknown_AddRef(*cancel_cookie); - } - } - - IMFAsyncResult_Release(item); - } - IMFAsyncResult_Release(caller); - - return hr; -} - -static HRESULT WINAPI gstreamer_scheme_handler_EndCreateObject(IMFSchemeHandler *iface, IMFAsyncResult *result, - MF_OBJECT_TYPE *obj_type, IUnknown **object) -{ - struct gstreamer_scheme_handler *handler = impl_from_IMFSchemeHandler(iface); - struct gstreamer_scheme_handler_result *found = NULL, *cur; - HRESULT hr; - - TRACE("%p, %p, %p, %p.\n", iface, result, obj_type, object); - - EnterCriticalSection(&handler->cs); - - LIST_FOR_EACH_ENTRY(cur, &handler->results, struct gstreamer_scheme_handler_result, entry) - { - if (result == cur->result) - { - list_remove(&cur->entry); - found = cur; - break; - } - } - - LeaveCriticalSection(&handler->cs); - - if (found) - { - *obj_type = MF_OBJECT_MEDIASOURCE; - *object = found->object; - hr = IMFAsyncResult_GetStatus(found->result); - IMFAsyncResult_Release(found->result); - free(found); - } - else - { - *obj_type = MF_OBJECT_INVALID; - *object = NULL; - hr = MF_E_UNEXPECTED; - } - - return hr; -} - -static HRESULT WINAPI gstreamer_scheme_handler_CancelObjectCreation(IMFSchemeHandler *iface, IUnknown *cancel_cookie) -{ - struct gstreamer_scheme_handler *handler = impl_from_IMFSchemeHandler(iface); - struct gstreamer_scheme_handler_result *found = NULL, *cur; - - TRACE("%p, %p.\n", iface, cancel_cookie); - - EnterCriticalSection(&handler->cs); - - LIST_FOR_EACH_ENTRY(cur, &handler->results, struct gstreamer_scheme_handler_result, entry) - { - if (cancel_cookie == (IUnknown *)cur->result) - { - list_remove(&cur->entry); - found = cur; - break; - } - } - - LeaveCriticalSection(&handler->cs); - - if (found) - { - IMFAsyncResult_Release(found->result); - if (found->object) - IUnknown_Release(found->object); - free(found); - } - - return found ? S_OK : MF_E_UNEXPECTED; -} - -static const IMFSchemeHandlerVtbl gstreamer_scheme_handler_vtbl = -{ - gstreamer_scheme_handler_QueryIntace, - gstreamer_scheme_handler_AddRef, - gstreamer_scheme_handler_Release, - gstreamer_scheme_handler_BeginCreateObject, - gstreamer_scheme_handler_EndCreateObject, - gstreamer_scheme_handler_CancelObjectCreation, -}; - -static HRESULT WINAPI gstreamer_scheme_handler_callback_QueryIntace(IMFAsyncCallback *iface, REFIID riid, void **obj) -{ - if (IsEqualIID(riid, &IID_IMFAsyncCallback) || - IsEqualIID(riid, &IID_IUnknown)) - { - *obj = iface; - IMFAsyncCallback_AddRef(iface); - return S_OK; - } - - WARN("Unsupported %s.\n", debugstr_guid(riid)); - *obj = NULL; - return E_NOINTERFACE; -} - -static ULONG WINAPI gstreamer_scheme_handler_callback_AddRef(IMFAsyncCallback *iface) -{ - struct gstreamer_scheme_handler *handler = impl_from_IMFAsyncCallback(iface); - return IMFSchemeHandler_AddRef(&handler->IMFSchemeHandler_iface); -} - -static ULONG WINAPI gstreamer_scheme_handler_callback_Release(IMFAsyncCallback *iface) -{ - struct gstreamer_scheme_handler *handler = impl_from_IMFAsyncCallback(iface); - return IMFSchemeHandler_Release(&handler->IMFSchemeHandler_iface); -} - -static HRESULT WINAPI gstreamer_scheme_handler_callback_GetParameters(IMFAsyncCallback *iface, DWORD *flags, DWORD *queue) -{ - return E_NOTIMPL; -} - -static HRESULT WINAPI gstreamer_scheme_handler_callback_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) -{ - IMFAsyncResult *caller; - struct gstreamer_scheme_handler *handler = impl_from_IMFAsyncCallback(iface); - struct gstreamer_scheme_handler_result *handler_result; - IUnknown *object = NULL, *context_object; - struct create_object_context *context; - HRESULT hr; - - caller = (IMFAsyncResult *)IMFAsyncResult_GetStateNoAddRef(result); - - if (FAILED(hr = IMFAsyncResult_GetObject(result, &context_object))) - { - WARN("Expected context set for callee result.\n"); - return hr; - } - - context = impl_from_IUnknown(context_object); - - hr = winegstreamer_create_media_source_from_uri(context->url, &object); - - handler_result = malloc(sizeof(*handler_result)); - if (handler_result) - { - handler_result->result = caller; - IMFAsyncResult_AddRef(handler_result->result); - - handler_result->object = object; - - EnterCriticalSection(&handler->cs); - list_add_tail(&handler->results, &handler_result->entry); - LeaveCriticalSection(&handler->cs); - } - else - { - if (object) - IUnknown_Release(object); - hr = E_OUTOFMEMORY; - } - - IMFAsyncResult_SetStatus(caller, hr); - MFInvokeCallback(caller); - - return S_OK; -} - -static const IMFAsyncCallbackVtbl gstreamer_scheme_handler_callback_vtbl = -{ - gstreamer_scheme_handler_callback_QueryIntace, - gstreamer_scheme_handler_callback_AddRef, - gstreamer_scheme_handler_callback_Release, - gstreamer_scheme_handler_callback_GetParameters, - gstreamer_scheme_handler_callback_Invoke, -}; - -HRESULT gstreamer_scheme_handler_construct(REFIID riid, void **obj) -{ - struct gstreamer_scheme_handler *handler; - HRESULT hr; - - TRACE("%s, %p.\n", debugstr_guid(riid), obj); - - if (!(handler = calloc(1, sizeof(*handler)))) - return E_OUTOFMEMORY; - - handler->IMFSchemeHandler_iface.lpVtbl = &gstreamer_scheme_handler_vtbl; - handler->IMFAsyncCallback_iface.lpVtbl = &gstreamer_scheme_handler_callback_vtbl; - handler->refcount = 1; - list_init(&handler->results); - InitializeCriticalSection(&handler->cs); - - hr = IMFSchemeHandler_QueryInterface(&handler->IMFSchemeHandler_iface, riid, obj); - IMFSchemeHandler_Release(&handler->IMFSchemeHandler_iface); - - return hr; -} - diff --git a/dlls/winegstreamer/unix_private.h b/dlls/winegstreamer/unix_private.h index 1b892cb0a98..434b81715e8 100644 --- a/dlls/winegstreamer/unix_private.h +++ b/dlls/winegstreamer/unix_private.h @@ -43,6 +43,11 @@ extern GstElement *find_element(GstElementFactoryListType type, GstCaps *src_cap extern bool append_element(GstElement *container, GstElement *element, GstElement **first, GstElement **last) DECLSPEC_HIDDEN; extern bool link_src_to_element(GstPad *src_pad, GstElement *element) DECLSPEC_HIDDEN; extern bool link_element_to_sink(GstElement *element, GstPad *sink_pad) DECLSPEC_HIDDEN; +extern GstCaps *detect_caps_from_data(const char *url, const void *data, guint size) DECLSPEC_HIDDEN; +extern GstPad *create_pad_with_caps(GstPadDirection direction, GstCaps *caps) DECLSPEC_HIDDEN; +extern GstBuffer *create_buffer_from_bytes(const void *data, guint size) DECLSPEC_HIDDEN; +extern gchar *stream_lang_from_tags(GstTagList *tags, GstCaps *caps) DECLSPEC_HIDDEN; +extern gchar *stream_name_from_tags(GstTagList *tags) DECLSPEC_HIDDEN; /* wg_format.c */ @@ -50,6 +55,9 @@ extern void wg_format_from_caps(struct wg_format *format, const GstCaps *caps) D extern bool wg_format_compare(const struct wg_format *a, const struct wg_format *b) DECLSPEC_HIDDEN; extern GstCaps *wg_format_to_caps(const struct wg_format *format) DECLSPEC_HIDDEN; +extern gchar *wg_stream_lang_from_tags(GstTagList *tags, GstCaps *caps) DECLSPEC_HIDDEN; +extern gchar *wg_stream_name_from_tags(GstTagList *tags) DECLSPEC_HIDDEN; + /* wg_transform.c */ extern NTSTATUS wg_transform_create(void *args) DECLSPEC_HIDDEN; @@ -59,6 +67,20 @@ extern NTSTATUS wg_transform_push_data(void *args) DECLSPEC_HIDDEN; extern NTSTATUS wg_transform_read_data(void *args) DECLSPEC_HIDDEN; extern NTSTATUS wg_transform_get_status(void *args) DECLSPEC_HIDDEN; extern NTSTATUS wg_transform_drain(void *args) DECLSPEC_HIDDEN; +extern NTSTATUS wg_transform_flush(void *args) DECLSPEC_HIDDEN; + +/* wg_source.c */ + +extern NTSTATUS wg_source_create(void *args) DECLSPEC_HIDDEN; +extern NTSTATUS wg_source_destroy(void *args) DECLSPEC_HIDDEN; +extern NTSTATUS wg_source_get_status(void *args) DECLSPEC_HIDDEN; +extern NTSTATUS wg_source_push_data(void *args) DECLSPEC_HIDDEN; +extern NTSTATUS wg_source_get_stream_format(void *args) DECLSPEC_HIDDEN; +extern NTSTATUS wg_source_get_stream_tag(void *args) DECLSPEC_HIDDEN; + +/* wg_task_pool.c */ + +extern GstTaskPool *wg_task_pool_new(void) DECLSPEC_HIDDEN; /* wg_allocator.c */ diff --git a/dlls/winegstreamer/unixlib.c b/dlls/winegstreamer/unixlib.c index a185000654d..346adec81f8 100644 --- a/dlls/winegstreamer/unixlib.c +++ b/dlls/winegstreamer/unixlib.c @@ -30,6 +30,10 @@ #define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_30 #include +#include +#include +#include +#include #include #include "ntstatus.h" @@ -102,6 +106,16 @@ GstElement *find_element(GstElementFactoryListType type, GstCaps *src_caps, GstC for (tmp = transforms; tmp != NULL && element == NULL; tmp = tmp->next) { name = gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(tmp->data)); + + if (!strcmp(name, "vaapidecodebin")) + { + /* vaapidecodebin adds asynchronicity which breaks wg_transform synchronous drain / flush + * requirements. Ignore it and use VA-API decoders directly instead. + */ + GST_WARNING("Ignoring vaapidecodebin decoder."); + continue; + } + if (!(element = gst_element_factory_create(GST_ELEMENT_FACTORY(tmp->data), NULL))) GST_WARNING("Failed to create %s element.", name); } @@ -193,6 +207,112 @@ bool link_element_to_sink(GstElement *element, GstPad *sink_pad) return !ret; } +GstCaps *detect_caps_from_data(const char *url, const void *data, guint size) +{ + const char *extension = url ? strrchr(url, '.') : NULL; + GstTypeFindProbability probability; + GstCaps *caps; + gchar *str; + + if (!(caps = gst_type_find_helper_for_data_with_extension(NULL, data, size, + extension ? extension + 1 : NULL, &probability))) + { + GST_ERROR("Failed to detect caps for url %s, data %p, size %u", url, data, size); + return NULL; + } + + str = gst_caps_to_string(caps); + if (probability > GST_TYPE_FIND_POSSIBLE) + GST_INFO("Detected caps %s with probability %u for url %s, data %p, size %u", + str, probability, url, data, size); + else + GST_FIXME("Detected caps %s with probability %u for url %s, data %p, size %u", + str, probability, url, data, size); + g_free(str); + + return caps; +} + +GstPad *create_pad_with_caps(GstPadDirection direction, GstCaps *caps) +{ + GstCaps *pad_caps = caps ? gst_caps_ref(caps) : gst_caps_new_any(); + const char *name = direction == GST_PAD_SRC ? "src" : "sink"; + GstPadTemplate *template; + GstPad *pad; + + if (!pad_caps || !(template = gst_pad_template_new(name, direction, GST_PAD_ALWAYS, pad_caps))) + return NULL; + pad = gst_pad_new_from_template(template, "src"); + g_object_unref(template); + gst_caps_unref(pad_caps); + return pad; +} + +GstBuffer *create_buffer_from_bytes(const void *data, guint size) +{ + GstBuffer *buffer; + + if (!(buffer = gst_buffer_new_and_alloc(size))) + GST_ERROR("Failed to allocate buffer for %#x bytes\n", size); + else + { + gst_buffer_fill(buffer, 0, data, size); + gst_buffer_set_size(buffer, size); + } + + return buffer; +} + +gchar *stream_lang_from_tags(GstTagList *tags, GstCaps *caps) +{ + gchar *value; + + if (!gst_tag_list_get_string(tags, GST_TAG_LANGUAGE_CODE, &value) || !value) + return NULL; + + return value; +} + +gchar *stream_name_from_tags(GstTagList *tags) +{ + /* Extract stream name from Quick Time demuxer private tag where it puts unrecognized chunks. */ + guint i, tag_count = gst_tag_list_get_tag_size(tags, "private-qt-tag"); + gchar *value = NULL; + + for (i = 0; !value && i < tag_count; ++i) + { + const gchar *name; + const GValue *val; + GstSample *sample; + GstBuffer *buf; + gsize size; + + if (!(val = gst_tag_list_get_value_index(tags, "private-qt-tag", i))) + continue; + if (!GST_VALUE_HOLDS_SAMPLE(val) || !(sample = gst_value_get_sample(val))) + continue; + name = gst_structure_get_name(gst_sample_get_info(sample)); + if (!name || strcmp(name, "application/x-gst-qt-name-tag")) + continue; + if (!(buf = gst_sample_get_buffer(sample))) + continue; + if ((size = gst_buffer_get_size(buf)) < 8) + continue; + size -= 8; + if (!(value = g_malloc(size + 1))) + return NULL; + if (gst_buffer_extract(buf, 8, value, size) != size) + { + g_free(value); + value = NULL; + continue; + } + value[size] = 0; + } + + return value; +} + NTSTATUS wg_init_gstreamer(void *arg) { static GstGLContext *gl_context; diff --git a/dlls/winegstreamer/unixlib.h b/dlls/winegstreamer/unixlib.h index 9134cc47413..e3d259a7fd2 100644 --- a/dlls/winegstreamer/unixlib.h +++ b/dlls/winegstreamer/unixlib.h @@ -112,6 +112,8 @@ struct wg_format WG_VIDEO_FORMAT_YV12, WG_VIDEO_FORMAT_YVYU, } format; + /* Positive height indicates top-down video; negative height + * indicates bottom-up video. */ int32_t width, height; uint32_t fps_n, fps_d; RECT padding; @@ -296,11 +298,19 @@ struct wg_parser_stream_seek_params DWORD start_flags, stop_flags; }; +struct wg_transform_attrs +{ + UINT32 output_plane_align; + UINT32 input_queue_length; + BOOL low_latency; +}; + struct wg_transform_create_params { struct wg_transform *transform; const struct wg_format *input_format; const struct wg_format *output_format; + const struct wg_transform_attrs *attrs; }; struct wg_transform_push_data_params @@ -330,10 +340,45 @@ struct wg_transform_get_status_params UINT32 accepts_input; }; -struct wg_transform_drain_params +struct wg_source_create_params { - struct wg_transform *transform; - BOOL flush; + const char *url; + UINT64 file_size; + const void *data; + UINT32 size; + char mime_type[256]; + struct wg_source *source; +}; + +struct wg_source_get_status_params +{ + struct wg_source *source; + UINT32 stream_count; + UINT64 duration; + UINT64 read_offset; +}; + +struct wg_source_push_data_params +{ + struct wg_source *source; + const void *data; + UINT32 size; +}; + +struct wg_source_get_stream_format_params +{ + struct wg_source *source; + UINT32 index; + struct wg_format format; +}; + +struct wg_source_get_stream_tag_params +{ + struct wg_source *source; + UINT32 index; + enum wg_parser_tag tag; + UINT32 size; + char *buffer; }; enum unix_funcs @@ -373,6 +418,14 @@ enum unix_funcs unix_wg_transform_read_data, unix_wg_transform_get_status, unix_wg_transform_drain, + unix_wg_transform_flush, + + unix_wg_source_create, + unix_wg_source_destroy, + unix_wg_source_get_status, + unix_wg_source_push_data, + unix_wg_source_get_stream_format, + unix_wg_source_get_stream_tag, }; #endif /* __WINE_WINEGSTREAMER_UNIXLIB_H */ diff --git a/dlls/winegstreamer/video_decoder.c b/dlls/winegstreamer/video_decoder.c index 1fdabd46b96..abcadf90a32 100644 --- a/dlls/winegstreamer/video_decoder.c +++ b/dlls/winegstreamer/video_decoder.c @@ -68,6 +68,7 @@ static struct video_decoder *impl_from_IMFTransform(IMFTransform *iface) static HRESULT try_create_wg_transform(struct video_decoder *decoder) { + struct wg_transform_attrs attrs = {0}; struct wg_format input_format; struct wg_format output_format; @@ -86,7 +87,7 @@ static HRESULT try_create_wg_transform(struct video_decoder *decoder) output_format.u.video.fps_d = 0; output_format.u.video.fps_n = 0; - if (!(decoder->wg_transform = wg_transform_create(&input_format, &output_format))) + if (!(decoder->wg_transform = wg_transform_create(&input_format, &output_format, &attrs))) { ERR("Failed to create transform with input major_type %u.\n", input_format.major_type); return E_FAIL; @@ -310,8 +311,6 @@ static HRESULT WINAPI transform_SetOutputType(IMFTransform *iface, DWORD id, IMF { mf_media_type_to_wg_format(decoder->output_type, &output_format); - output_format.u.video.width = frame_size >> 32; - output_format.u.video.height = (UINT32)frame_size; output_format.u.video.fps_d = 0; output_format.u.video.fps_n = 0; diff --git a/dlls/winegstreamer/video_processor.c b/dlls/winegstreamer/video_processor.c index 03186b36d9d..4c5e822d14a 100644 --- a/dlls/winegstreamer/video_processor.c +++ b/dlls/winegstreamer/video_processor.c @@ -88,6 +88,7 @@ struct video_processor static HRESULT try_create_wg_transform(struct video_processor *impl) { struct wg_format input_format, output_format; + struct wg_transform_attrs attrs = {.input_queue_length = 15}; if (impl->wg_transform) wg_transform_destroy(impl->wg_transform); @@ -101,7 +102,7 @@ static HRESULT try_create_wg_transform(struct video_processor *impl) if (output_format.major_type == WG_MAJOR_TYPE_UNKNOWN) return MF_E_INVALIDMEDIATYPE; - if (!(impl->wg_transform = wg_transform_create(&input_format, &output_format))) + if (!(impl->wg_transform = wg_transform_create(&input_format, &output_format, &attrs))) return E_FAIL; return S_OK; @@ -513,7 +514,7 @@ static HRESULT WINAPI video_processor_ProcessMessage(IMFTransform *iface, MFT_ME return MF_E_TRANSFORM_TYPE_NOT_SET; if (message == MFT_MESSAGE_COMMAND_DRAIN) - return wg_transform_drain(impl->wg_transform, FALSE); + return wg_transform_drain(impl->wg_transform); FIXME("Ignoring message %#x.\n", message); @@ -613,13 +614,14 @@ HRESULT video_processor_create(REFIID riid, void **ret) .height = 1080, }, }; + struct wg_transform_attrs attrs = {0}; struct wg_transform *transform; struct video_processor *impl; HRESULT hr; TRACE("riid %s, ret %p.\n", debugstr_guid(riid), ret); - if (!(transform = wg_transform_create(&input_format, &output_format))) + if (!(transform = wg_transform_create(&input_format, &output_format, &attrs))) { ERR_(winediag)("GStreamer doesn't support video conversion, please install appropriate plugins.\n"); return E_FAIL; diff --git a/dlls/winegstreamer/wg_format.c b/dlls/winegstreamer/wg_format.c index 9507d6475b0..5ab2665f38d 100644 --- a/dlls/winegstreamer/wg_format.c +++ b/dlls/winegstreamer/wg_format.c @@ -529,11 +529,10 @@ static GstCaps *wg_format_to_caps_video_h264(const struct wg_format *format) GST_FIXME("H264 profile attribute %u not implemented.", format->u.video_h264.profile); /* fallthrough */ case eAVEncH264VProfile_unknown: - profile = NULL; + profile = "baseline"; break; } - if (profile) - gst_caps_set_simple(caps, "profile", G_TYPE_STRING, profile, NULL); + gst_caps_set_simple(caps, "profile", G_TYPE_STRING, profile, NULL); switch (format->u.video_h264.level) { diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index d12c4c2dad1..3e5afe49bf0 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -64,6 +64,7 @@ struct wg_parser GstElement *container, *decodebin; GstBus *bus; + GstTaskPool *task_pool; GstPad *my_src; guint64 file_size, start_offset, next_offset, stop_offset; @@ -224,34 +225,9 @@ static NTSTATUS wg_parser_stream_enable(void *args) if (format->major_type == WG_MAJOR_TYPE_VIDEO) { - if (params->flags & STREAM_ENABLE_FLAG_FLIP_RGB) - { - bool flip = (format->u.video.height < 0); - - switch (format->u.video.format) - { - case WG_VIDEO_FORMAT_BGRA: - case WG_VIDEO_FORMAT_BGRx: - case WG_VIDEO_FORMAT_BGR: - case WG_VIDEO_FORMAT_RGBA: - case WG_VIDEO_FORMAT_RGB15: - case WG_VIDEO_FORMAT_RGB16: - flip = !flip; - break; - - case WG_VIDEO_FORMAT_AYUV: - case WG_VIDEO_FORMAT_I420: - case WG_VIDEO_FORMAT_NV12: - case WG_VIDEO_FORMAT_UYVY: - case WG_VIDEO_FORMAT_YUY2: - case WG_VIDEO_FORMAT_YV12: - case WG_VIDEO_FORMAT_YVYU: - case WG_VIDEO_FORMAT_UNKNOWN: - break; - } + bool flip = (params->flags & STREAM_ENABLE_FLAG_FLIP_RGB) && (format->u.video.height < 0); - gst_util_set_object_arg(G_OBJECT(stream->flip), "method", flip ? "vertical-flip" : "none"); - } + gst_util_set_object_arg(G_OBJECT(stream->flip), "method", flip ? "vertical-flip" : "none"); } gst_pad_push_event(stream->my_sink, gst_event_new_reconfigure()); @@ -517,6 +493,11 @@ static GstAutoplugSelectResult autoplug_select_cb(GstElement *bin, GstPad *pad, } if (!strcmp(name, "QuickTime demuxer")) parser->using_qtdemux = true; + if (!strcmp(name, "Proton video converter") && !parser->use_mediaconv) + { + GST_INFO("Skipping \"Proton video converter\"."); + return GST_AUTOPLUG_SELECT_SKIP; + } return GST_AUTOPLUG_SELECT_TRY; } @@ -588,10 +569,16 @@ static void no_more_pads_cb(GstElement *element, gpointer user) static void deep_element_added_cb(GstBin *self, GstBin *sub_bin, GstElement *element, gpointer user) { - GstElementFactory *factory = gst_element_get_factory(element); - const char *name = gst_element_factory_get_longname(factory); + GstElementFactory *factory = NULL; + const char *name = NULL; + + if (element) + factory = gst_element_get_factory(element); - if (strstr(name, "Dav1d")) + if (factory) + name = gst_element_factory_get_longname(factory); + + if (name && strstr(name, "Dav1d")) { #if defined(__x86_64__) GST_DEBUG("%s found, setting n-threads to 4.", name); @@ -1321,6 +1308,24 @@ static GstBusSyncReply bus_handler_cb(GstBus *bus, GstMessage *msg, gpointer use } break; + case GST_MESSAGE_STREAM_STATUS: + { + GstStreamStatusType type; + GstElement *element; + const GValue *val; + GstTask *task; + + gst_message_parse_stream_status(msg, &type, &element); + val = gst_message_get_stream_status_object(msg); + GST_DEBUG("parser %p, message %s, type %u, value %p (%s).", parser, GST_MESSAGE_TYPE_NAME(msg), type, val, G_VALUE_TYPE_NAME(val)); + + if (G_VALUE_TYPE(val) == GST_TYPE_TASK && (task = g_value_get_object(val)) + && type == GST_STREAM_STATUS_TYPE_CREATE) + gst_task_set_pool(task, parser->task_pool); + + break; + } + default: break; } @@ -1455,8 +1460,6 @@ static void query_tags(struct wg_parser_stream *stream) static NTSTATUS wg_parser_connect(void *args) { - GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE("quartz_src", - GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); const struct wg_parser_connect_params *params = args; struct wg_parser *parser = params->parser; const WCHAR *uri = params->uri; @@ -1487,7 +1490,7 @@ static NTSTATUS wg_parser_connect(void *args) if (parser->context) gst_element_set_context(parser->container, parser->context); - parser->my_src = gst_pad_new_from_static_template(&src_template, "quartz-src"); + parser->my_src = create_pad_with_caps(GST_PAD_SRC, NULL); gst_pad_set_getrange_function(parser->my_src, src_getrange_cb); gst_pad_set_query_function(parser->my_src, src_query_cb); gst_pad_set_activatemode_function(parser->my_src, src_activate_mode_cb); @@ -1683,6 +1686,7 @@ static NTSTATUS wg_parser_disconnect(void *args) gst_object_unref(parser->container); parser->container = NULL; + gst_task_pool_cleanup(parser->task_pool); return S_OK; } @@ -1825,6 +1829,7 @@ static NTSTATUS wg_parser_create(void *args) struct wg_parser_create_params *params = args; struct wg_parser *parser; + GError *error; if (!(parser = calloc(1, sizeof(*parser)))) return E_OUTOFMEMORY; @@ -1838,6 +1843,12 @@ static NTSTATUS wg_parser_create(void *args) parser->use_opengl = FALSE; } } + if (!(parser->task_pool = wg_task_pool_new())) + { + free(parser); + return E_OUTOFMEMORY; + } + gst_task_pool_prepare(parser->task_pool, &error); pthread_mutex_init(&parser->mutex, NULL); pthread_cond_init(&parser->init_cond, NULL); @@ -1860,6 +1871,7 @@ static NTSTATUS wg_parser_destroy(void *args) gst_bus_set_sync_handler(parser->bus, NULL, NULL, NULL); gst_object_unref(parser->bus); } + gst_object_unref(parser->task_pool); if (parser->context) gst_context_unref(parser->context); @@ -1912,4 +1924,12 @@ const unixlib_entry_t __wine_unix_call_funcs[] = X(wg_transform_read_data), X(wg_transform_get_status), X(wg_transform_drain), + X(wg_transform_flush), + + X(wg_source_create), + X(wg_source_destroy), + X(wg_source_get_status), + X(wg_source_push_data), + X(wg_source_get_stream_format), + X(wg_source_get_stream_tag), }; diff --git a/dlls/winegstreamer/wg_source.c b/dlls/winegstreamer/wg_source.c new file mode 100644 index 00000000000..114a0e60582 --- /dev/null +++ b/dlls/winegstreamer/wg_source.c @@ -0,0 +1,616 @@ +/* + * Copyright 2023 Rémi Bernon for CodeWeavers + * + * 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 + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "winternl.h" +#include "mferror.h" + +#include "unix_private.h" + +#define WG_SOURCE_MAX_STREAMS 32 + +struct wg_source +{ + GstPad *src_pad; + GstElement *container; + GstSegment segment; + bool valid_segment; + + guint64 max_duration; + GstPad *stream_pads[WG_SOURCE_MAX_STREAMS]; + guint stream_count; +}; + +static GstStream *source_get_stream(struct wg_source *source, guint index) +{ + return index >= source->stream_count ? NULL : gst_pad_get_stream(source->stream_pads[index]); +} + +static GstCaps *source_get_stream_caps(struct wg_source *source, guint index) +{ + GstStream *stream; + GstCaps *caps; + if (!(stream = source_get_stream(source, index))) + return NULL; + caps = gst_stream_get_caps(stream); + gst_object_unref(stream); + return caps; +} + +static GstTagList *source_get_stream_tags(struct wg_source *source, guint index) +{ + GstStream *stream; + GstTagList *tags; + if (!(stream = source_get_stream(source, index))) + return NULL; + tags = gst_stream_get_tags(stream); + gst_object_unref(stream); + return tags; +} + +static gboolean src_event_seek(struct wg_source *source, GstEvent *event) +{ + guint32 seqnum = gst_event_get_seqnum(event); + GstSeekType cur_type, stop_type; + GstSeekFlags flags; + GstFormat format; + gint64 cur, stop; + gdouble rate; + + gst_event_parse_seek(event, &rate, &format, &flags, &cur_type, &cur, &stop_type, &stop); + gst_event_unref(event); + if (format != GST_FORMAT_BYTES) + return false; + + GST_TRACE("source %p, rate %f, format %s, flags %#x, cur_type %u, cur %#" G_GINT64_MODIFIER "x, " + "stop_type %u, stop %#" G_GINT64_MODIFIER "x.", source, rate, gst_format_get_name(format), + flags, cur_type, cur, stop_type, stop); + + if (flags & GST_SEEK_FLAG_FLUSH) + { + if (!(event = gst_event_new_flush_start())) + GST_ERROR("Failed to allocate flush_start event"); + else + { + gst_event_set_seqnum(event, seqnum); + if (!gst_pad_push_event(source->src_pad, event)) + GST_ERROR("Failed to push flush_start event"); + } + } + + source->segment.start = cur; + + if (flags & GST_SEEK_FLAG_FLUSH) + { + if (!(event = gst_event_new_flush_stop(true))) + GST_ERROR("Failed to allocate flush_stop event"); + else + { + gst_event_set_seqnum(event, seqnum); + if (!gst_pad_push_event(source->src_pad, event)) + GST_ERROR("Failed to push flush_stop event"); + } + source->valid_segment = false; + } + + return true; +} + +static gboolean src_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) +{ + struct wg_source *source = gst_pad_get_element_private(pad); + + switch (GST_EVENT_TYPE(event)) + { + case GST_EVENT_SEEK: + return src_event_seek(source, event); + default: + return gst_pad_event_default(pad, parent, event); + } +} + +static gboolean src_query_duration(struct wg_source *source, GstQuery *query) +{ + GstFormat format; + + gst_query_parse_duration(query, &format, NULL); + GST_TRACE("source %p, format %s", source, gst_format_get_name(format)); + if (format != GST_FORMAT_BYTES) + return false; + + gst_query_set_duration(query, format, source->segment.stop); + return true; +} + +static gboolean src_query_scheduling(struct wg_source *source, GstQuery *query) +{ + GST_TRACE("source %p", source); + gst_query_set_scheduling(query, GST_SCHEDULING_FLAG_SEEKABLE, 1, -1, 0); + gst_query_add_scheduling_mode(query, GST_PAD_MODE_PUSH); + return true; +} + +static gboolean src_query_seeking(struct wg_source *source, GstQuery *query) +{ + GstFormat format; + + gst_query_parse_seeking(query, &format, NULL, NULL, NULL); + GST_TRACE("source %p, format %s", source, gst_format_get_name(format)); + if (format != GST_FORMAT_BYTES) + return false; + + gst_query_set_seeking(query, GST_FORMAT_BYTES, 1, 0, source->segment.stop); + return true; +} + +static gboolean src_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) +{ + struct wg_source *source = gst_pad_get_element_private(pad); + + switch (GST_QUERY_TYPE(query)) + { + case GST_QUERY_DURATION: + return src_query_duration(source, query); + case GST_QUERY_SCHEDULING: + return src_query_scheduling(source, query); + case GST_QUERY_SEEKING: + return src_query_seeking(source, query); + default: + return gst_pad_query_default(pad, parent, query); + } +} + +static GstFlowReturn sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *buffer) +{ + struct wg_soutce *source = gst_pad_get_element_private(pad); + GST_TRACE("source %p, pad %p, buffer %p.", source, pad, buffer); + gst_buffer_unref(buffer); + return GST_FLOW_EOS; +} + +static gboolean sink_event_caps(struct wg_source *source, GstPad *pad, GstEvent *event) +{ + GstStream *stream; + GstCaps *caps; + gchar *str; + + gst_event_parse_caps(event, &caps); + str = gst_caps_to_string(caps); + GST_TRACE("source %p, pad %p, caps %s", source, pad, str); + g_free(str); + + if ((stream = gst_pad_get_stream(pad))) + { + gst_stream_set_caps(stream, gst_caps_copy(caps)); + gst_stream_set_stream_type(stream, stream_type_from_caps(caps)); + gst_object_unref(stream); + } + + gst_event_unref(event); + return !!stream; +} + +static gboolean sink_event_tag(struct wg_source *source, GstPad *pad, GstEvent *event) +{ + GstTagList *new_tags; + GstStream *stream; + + gst_event_parse_tag(event, &new_tags); + GST_TRACE("source %p, pad %p, new_tags %p", source, pad, new_tags); + + if ((stream = gst_pad_get_stream(pad))) + { + GstTagList *old_tags = gst_stream_get_tags(stream); + if ((new_tags = gst_tag_list_merge(old_tags, new_tags, GST_TAG_MERGE_REPLACE))) + { + gst_stream_set_tags(stream, new_tags); + gst_tag_list_unref(new_tags); + } + if (old_tags) + gst_tag_list_unref(old_tags); + gst_object_unref(stream); + } + + gst_event_unref(event); + return stream && new_tags; +} + +static gboolean sink_event_stream_start(struct wg_source *source, GstPad *pad, GstEvent *event) +{ + guint group, flags; + GstStream *stream; + gint64 duration; + const gchar *id; + + gst_event_parse_stream_start(event, &id); + gst_event_parse_stream(event, &stream); + gst_event_parse_stream_flags(event, &flags); + if (!gst_event_parse_group_id(event, &group)) + group = -1; + if (gst_pad_peer_query_duration(pad, GST_FORMAT_TIME, &duration) && GST_CLOCK_TIME_IS_VALID(duration)) + source->max_duration = max(source->max_duration, duration); + + GST_TRACE("source %p, pad %p, stream %p, id %s, flags %#x, group %d, duration %" GST_TIME_FORMAT, + source, pad, stream, id, flags, group, GST_TIME_ARGS(duration)); + + gst_event_unref(event); + return true; +} + +static gboolean sink_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) +{ + struct wg_source *source = gst_pad_get_element_private(pad); + + switch (GST_EVENT_TYPE(event)) + { + case GST_EVENT_CAPS: + return sink_event_caps(source, pad, event); + case GST_EVENT_TAG: + return sink_event_tag(source, pad, event); + case GST_EVENT_STREAM_START: + return sink_event_stream_start(source, pad, event); + default: + return gst_pad_event_default(pad, parent, event); + } +} + +static GstEvent *create_stream_start_event(const char *stream_id) +{ + GstStream *stream; + GstEvent *event; + + if (!(stream = gst_stream_new(stream_id, NULL, GST_STREAM_TYPE_UNKNOWN, 0))) + return NULL; + if ((event = gst_event_new_stream_start(stream_id))) + { + gst_event_set_stream(event, stream); + gst_object_unref(stream); + } + + return event; +} + +static void pad_added_cb(GstElement *element, GstPad *pad, gpointer user) +{ + struct wg_source *source = user; + char stream_id[256]; + GstFlowReturn ret; + GstPad *sink_pad; + GstEvent *event; + guint index; + + GST_TRACE("source %p, element %p, pad %p.", source, element, pad); + if ((index = source->stream_count++) >= ARRAY_SIZE(source->stream_pads)) + { + GST_FIXME("Not enough sink pads, need %u", source->stream_count); + return; + } + + sink_pad = source->stream_pads[index]; + if (gst_pad_link(pad, sink_pad) < 0 || !gst_pad_set_active(sink_pad, true)) + GST_ERROR("Failed to link new pad to sink pad %p", sink_pad); + + snprintf(stream_id, ARRAY_SIZE(stream_id), "wg_source/%03u", index); + if (!(event = create_stream_start_event(stream_id))) + GST_ERROR("Failed to create stream event for sink pad %p", sink_pad); + else + { + if ((ret = gst_pad_store_sticky_event(pad, event)) < 0) + GST_ERROR("Failed to create pad %p stream, ret %d", sink_pad, ret); + if ((ret = gst_pad_store_sticky_event(sink_pad, event)) < 0) + GST_ERROR("Failed to create pad %p stream, ret %d", sink_pad, ret); + gst_event_unref(event); + } +} + +NTSTATUS wg_source_create(void *args) +{ + struct wg_source_create_params *params = args; + GstElement *first = NULL, *last = NULL, *element; + GstCaps *src_caps, *any_caps; + struct wg_source *source; + const gchar *media_type; + GstEvent *event; + GstPad *peer; + guint i; + + if (!(src_caps = detect_caps_from_data(params->url, params->data, params->size))) + return STATUS_UNSUCCESSFUL; + if (!(source = calloc(1, sizeof(*source)))) + { + gst_caps_unref(src_caps); + return STATUS_UNSUCCESSFUL; + } + gst_segment_init(&source->segment, GST_FORMAT_BYTES); + source->segment.stop = params->file_size; + + media_type = gst_structure_get_name(gst_caps_get_structure(src_caps, 0)); + if (!strcmp(media_type, "video/quicktime")) + strcpy(params->mime_type, "video/mp4"); + else if (!strcmp(media_type, "video/x-msvideo")) + strcpy(params->mime_type, "video/avi"); + else + lstrcpynA(params->mime_type, media_type, ARRAY_SIZE(params->mime_type)); + + if (!(source->container = gst_bin_new("wg_source"))) + goto error; + if (!(source->src_pad = create_pad_with_caps(GST_PAD_SRC, src_caps))) + goto error; + gst_pad_set_element_private(source->src_pad, source); + gst_pad_set_query_function(source->src_pad, src_query_cb); + gst_pad_set_event_function(source->src_pad, src_event_cb); + + for (i = 0; i < ARRAY_SIZE(source->stream_pads); i++) + { + if (!(source->stream_pads[i] = create_pad_with_caps(GST_PAD_SINK, NULL))) + goto error; + gst_pad_set_element_private(source->stream_pads[i], source); + gst_pad_set_chain_function(source->stream_pads[i], sink_chain_cb); + gst_pad_set_event_function(source->stream_pads[i], sink_event_cb); + } + + if (!(any_caps = gst_caps_new_any())) + goto error; + if (!(element = find_element(GST_ELEMENT_FACTORY_TYPE_DECODABLE, src_caps, any_caps)) + || !append_element(source->container, element, &first, &last)) + { + gst_caps_unref(any_caps); + goto error; + } + g_signal_connect(element, "pad-added", G_CALLBACK(pad_added_cb), source); + gst_caps_unref(any_caps); + + if (!link_src_to_element(source->src_pad, first)) + goto error; + if (!gst_pad_set_active(source->src_pad, true)) + goto error; + + /* try to link the first output pad, some demuxers only have static pads */ + if ((peer = gst_element_get_static_pad(last, "src"))) + { + GstPad *sink_pad = source->stream_pads[0]; + if (gst_pad_link(peer, sink_pad) < 0 || !gst_pad_set_active(sink_pad, true)) + GST_ERROR("Failed to link static source pad %p", peer); + else + source->stream_count++; + gst_object_unref(peer); + } + + gst_element_set_state(source->container, GST_STATE_PAUSED); + if (!gst_element_get_state(source->container, NULL, NULL, -1)) + goto error; + + if (!(event = create_stream_start_event("wg_source")) + || !gst_pad_push_event(source->src_pad, event)) + goto error; + gst_caps_unref(src_caps); + + params->source = source; + GST_INFO("Created winegstreamer source %p.", source); + return STATUS_SUCCESS; + +error: + if (source->container) + { + gst_element_set_state(source->container, GST_STATE_NULL); + gst_object_unref(source->container); + } + for (i = 0; i < ARRAY_SIZE(source->stream_pads) && source->stream_pads[i]; i++) + gst_object_unref(source->stream_pads[i]); + if (source->src_pad) + gst_object_unref(source->src_pad); + free(source); + + gst_caps_unref(src_caps); + + GST_ERROR("Failed to create winegstreamer source."); + return STATUS_UNSUCCESSFUL; +} + +NTSTATUS wg_source_destroy(void *args) +{ + struct wg_source *source = args; + guint i; + + GST_TRACE("source %p", source); + + gst_element_set_state(source->container, GST_STATE_NULL); + gst_object_unref(source->container); + for (i = 0; i < ARRAY_SIZE(source->stream_pads); i++) + gst_object_unref(source->stream_pads[i]); + gst_object_unref(source->src_pad); + free(source); + + return STATUS_SUCCESS; +} + +NTSTATUS wg_source_get_status(void *args) +{ + struct wg_source_get_status_params *params = args; + struct wg_source *source = params->source; + UINT i, stream_count; + GstCaps *caps; + + GST_TRACE("source %p", source); + + for (i = 0, stream_count = source->stream_count; i < stream_count; i++) + { + if (!(caps = source_get_stream_caps(source, i))) + return STATUS_PENDING; + gst_caps_unref(caps); + } + + params->stream_count = stream_count; + params->duration = source->max_duration / 100; + params->read_offset = source->segment.start; + return STATUS_SUCCESS; +} + +NTSTATUS wg_source_push_data(void *args) +{ + struct wg_source_push_data_params *params = args; + struct wg_source *source = params->source; + GstFlowReturn ret = GST_FLOW_OK; + GstBuffer *buffer; + GstEvent *event; + + GST_TRACE("source %p, data %p, size %#x", source, params->data, params->size); + + if (!source->valid_segment) + { + if (!(event = gst_event_new_segment(&source->segment)) + || !gst_pad_push_event(source->src_pad, event)) + GST_ERROR("Failed to push new segment event"); + source->valid_segment = true; + } + + if (!(buffer = create_buffer_from_bytes(params->data, params->size))) + { + GST_WARNING("Failed to allocate buffer for data"); + return STATUS_UNSUCCESSFUL; + } + + source->segment.start += params->size; + if ((ret = gst_pad_push(source->src_pad, buffer)) && ret != GST_FLOW_EOS) + { + GST_WARNING("Failed to push data buffer, ret %d", ret); + source->segment.start -= params->size; + return STATUS_UNSUCCESSFUL; + } + + if (source->segment.start != source->segment.stop) + return STATUS_SUCCESS; + + if (!(event = gst_event_new_eos()) + || !gst_pad_push_event(source->src_pad, event)) + GST_WARNING("Failed to push EOS event"); + + return STATUS_SUCCESS; +} + +NTSTATUS wg_source_get_stream_format(void *args) +{ + struct wg_source_get_stream_format_params *params = args; + struct wg_source *source = params->source; + guint index = params->index; + GstCaps *caps, *copy; + + GST_TRACE("source %p, index %u", source, index); + + if (!(caps = source_get_stream_caps(source, index))) + return STATUS_UNSUCCESSFUL; + + if (!(copy = gst_caps_copy(caps))) + goto done; + switch (stream_type_from_caps(caps)) + { + case GST_STREAM_TYPE_VIDEO: + gst_structure_set_name(gst_caps_get_structure(copy, 0), "video/x-raw"); + gst_caps_set_simple(copy, "format", G_TYPE_STRING, "NV12", NULL); + break; + case GST_STREAM_TYPE_AUDIO: + gst_structure_set_name(gst_caps_get_structure(copy, 0), "audio/x-raw"); + gst_caps_set_simple(copy, "format", G_TYPE_STRING, "S16LE", NULL); + gst_caps_set_simple(copy, "layout", G_TYPE_STRING, "interleaved", NULL); + break; + default: break; + } + wg_format_from_caps(¶ms->format, copy); + gst_caps_unref(copy); + +done: + gst_caps_unref(caps); + return STATUS_SUCCESS; +} + +NTSTATUS wg_source_get_stream_tag(void *args) +{ + struct wg_source_get_stream_tag_params *params = args; + struct wg_source *source = params->source; + enum wg_parser_tag tag = params->tag; + guint index = params->index; + GstTagList *tags; + NTSTATUS status; + uint32_t len; + gchar *value; + + GST_TRACE("source %p, index %u, tag %u", source, index, tag); + + if (params->tag >= WG_PARSER_TAG_COUNT) + return STATUS_INVALID_PARAMETER; + if (!(tags = source_get_stream_tags(source, index))) + return STATUS_UNSUCCESSFUL; + + switch (tag) + { + case WG_PARSER_TAG_LANGUAGE: + { + GstCaps *caps = gst_pad_get_current_caps(source->src_pad); + value = stream_lang_from_tags(tags, caps); + if (caps) + gst_caps_unref(caps); + break; + } + case WG_PARSER_TAG_NAME: + value = stream_name_from_tags(tags); + break; + default: + GST_FIXME("Unsupported stream tag %u", tag); + value = NULL; + break; + } + + if (!value) + goto error; + + if ((len = strlen(value) + 1) > params->size) + { + params->size = len; + status = STATUS_BUFFER_TOO_SMALL; + } + else + { + memcpy(params->buffer, value, len); + status = STATUS_SUCCESS; + } + + gst_tag_list_unref(tags); + g_free(value); + return status; + +error: + gst_tag_list_unref(tags); + return STATUS_NOT_FOUND; +} diff --git a/dlls/winegstreamer/wg_task_pool.c b/dlls/winegstreamer/wg_task_pool.c new file mode 100644 index 00000000000..ec7da286d5f --- /dev/null +++ b/dlls/winegstreamer/wg_task_pool.c @@ -0,0 +1,98 @@ +/* + * GStreamer task pool + * + * Copyright 2023 Rémi Bernon for CodeWeavers + * + * 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 + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include +#include + +#include + +#include "unix_private.h" + +typedef struct +{ + GstTaskPool parent; +} WgTaskPool; + +typedef struct +{ + GstTaskPoolClass parent_class; +} WgTaskPoolClass; + +G_DEFINE_TYPE(WgTaskPool, wg_task_pool, GST_TYPE_TASK_POOL); + +static void wg_task_pool_prepare(GstTaskPool *pool, GError **error) +{ + GST_LOG("pool %p, error %p", pool, error); +} + +static void wg_task_pool_cleanup(GstTaskPool *pool) +{ + GST_LOG("pool %p", pool); +} + +static gpointer wg_task_pool_push(GstTaskPool *pool, GstTaskPoolFunction func, gpointer data, GError **error) +{ + pthread_t *tid; + gint res; + + GST_LOG("pool %p, func %p, data %p, error %p", pool, func, data, error); + + if (!(tid = malloc(sizeof(*tid))) || !(res = pthread_create(tid, NULL, (void *)func, data))) + return tid; + + g_set_error(error, G_THREAD_ERROR, G_THREAD_ERROR_AGAIN, "Error creating thread: %s", g_strerror(res)); + free(tid); + + return NULL; +} + +static void wg_task_pool_join(GstTaskPool *pool, gpointer id) +{ + pthread_t *tid = id; + + GST_LOG("pool %p, id %p", pool, id); + + pthread_join(*tid, NULL); + free(tid); +} + +static void wg_task_pool_class_init(WgTaskPoolClass *klass) +{ + GstTaskPoolClass *parent_class = (GstTaskPoolClass *)klass; + parent_class->prepare = wg_task_pool_prepare; + parent_class->cleanup = wg_task_pool_cleanup; + parent_class->push = wg_task_pool_push; + parent_class->join = wg_task_pool_join; +} + +static void wg_task_pool_init(WgTaskPool *pool) +{ +} + +GstTaskPool *wg_task_pool_new(void) +{ + return g_object_new(wg_task_pool_get_type(), NULL); +} diff --git a/dlls/winegstreamer/wg_transform.c b/dlls/winegstreamer/wg_transform.c index f5fd96ba674..a6df4a4d0e0 100644 --- a/dlls/winegstreamer/wg_transform.c +++ b/dlls/winegstreamer/wg_transform.c @@ -43,23 +43,25 @@ struct wg_transform { + struct wg_transform_attrs attrs; + GstElement *container; GstAllocator *allocator; GstPad *my_src, *my_sink; GstSegment segment; GstQuery *drain_query; - guint input_max_length; GstAtomicQueue *input_queue; - guint output_plane_align; + bool input_is_flipped; + GstElement *video_flip; + + struct wg_format output_format; struct wg_sample *output_wg_sample; GstAtomicQueue *output_queue; GstSample *output_sample; bool output_caps_changed; GstCaps *output_caps; - bool broken_timestamps; - bool setting_output_format; }; static void align_video_info_planes(gsize plane_align, GstVideoInfo *info, GstVideoAlignment *align) @@ -83,11 +85,6 @@ static GstFlowReturn transform_sink_chain_cb(GstPad *pad, GstObject *parent, Gst GST_LOG("transform %p, buffer %p.", transform, buffer); - if (GST_BUFFER_PTS_IS_VALID(buffer)) - transform->segment.start = GST_BUFFER_PTS(buffer); - else if (GST_BUFFER_DURATION_IS_VALID(buffer)) - transform->segment.start += GST_BUFFER_DURATION(buffer); - if (!(sample = gst_sample_new(buffer, transform->output_caps, NULL, NULL))) { GST_ERROR("Failed to allocate transform %p output sample.", transform); @@ -104,6 +101,26 @@ static GstFlowReturn transform_sink_chain_cb(GstPad *pad, GstObject *parent, Gst return GST_FLOW_OK; } +static gboolean transform_src_query_latency(struct wg_transform *transform, GstQuery *query) +{ + GST_LOG("transform %p, query %p", transform, query); + gst_query_set_latency(query, transform->attrs.low_latency, 0, 0); + return true; +} + +static gboolean transform_src_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) +{ + struct wg_transform *transform = gst_pad_get_element_private(pad); + + switch (query->type) + { + case GST_QUERY_LATENCY: + return transform_src_query_latency(transform, query); + default: + return gst_pad_query_default(pad, parent, query); + } +} + static gboolean transform_sink_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) { struct wg_transform *transform = gst_pad_get_element_private(pad); @@ -114,7 +131,7 @@ static gboolean transform_sink_query_cb(GstPad *pad, GstObject *parent, GstQuery { case GST_QUERY_ALLOCATION: { - gsize plane_align = transform->output_plane_align; + gsize plane_align = transform->attrs.output_plane_align; GstStructure *config, *params; GstVideoAlignment align; gboolean needs_pool; @@ -177,11 +194,9 @@ static gboolean transform_sink_query_cb(GstPad *pad, GstObject *parent, GstQuery GstCaps *caps, *filter, *temp; gchar *str; - if (!transform->setting_output_format) - return gst_pad_query_default(pad, parent, query); - gst_query_parse_caps(query, &filter); - caps = gst_caps_ref(transform->output_caps); + if (!(caps = wg_format_to_caps(&transform->output_format))) + break; if (filter) { @@ -219,7 +234,6 @@ static gboolean transform_sink_event_cb(GstPad *pad, GstObject *parent, GstEvent { GstCaps *caps; - transform->setting_output_format = false; gst_event_parse_caps(event, &caps); transform->output_caps_changed = transform->output_caps_changed @@ -276,6 +290,11 @@ static struct wg_sample *transform_request_sample(gsize size, void *context) return InterlockedExchangePointer((void **)&transform->output_wg_sample, NULL); } +static bool wg_format_video_is_flipped(const struct wg_format *format) +{ + return format->major_type == WG_MAJOR_TYPE_VIDEO && (format->u.video.height < 0); +} + NTSTATUS wg_transform_create(void *args) { struct wg_transform_create_params *params = args; @@ -284,7 +303,6 @@ NTSTATUS wg_transform_create(void *args) GstElement *first = NULL, *last = NULL, *element; GstCaps *raw_caps = NULL, *src_caps = NULL; NTSTATUS status = STATUS_UNSUCCESSFUL; - GstPadTemplate *template = NULL; struct wg_transform *transform; const gchar *media_type; GstEvent *event; @@ -305,25 +323,20 @@ NTSTATUS wg_transform_create(void *args) goto out; if (!(transform->allocator = wg_allocator_create(transform_request_sample, transform))) goto out; - transform->input_max_length = 1; - transform->output_plane_align = 0; + transform->attrs = *params->attrs; + transform->output_format = output_format; if (!(src_caps = wg_format_to_caps(&input_format))) goto out; - if (!(template = gst_pad_template_new("src", GST_PAD_SRC, GST_PAD_ALWAYS, src_caps))) - goto out; - transform->my_src = gst_pad_new_from_template(template, "src"); - g_object_unref(template); - if (!transform->my_src) + if (!(transform->my_src = create_pad_with_caps(GST_PAD_SRC, src_caps))) goto out; + gst_pad_set_element_private(transform->my_src, transform); + gst_pad_set_query_function(transform->my_src, transform_src_query_cb); + if (!(transform->output_caps = wg_format_to_caps(&output_format))) goto out; - if (!(template = gst_pad_template_new("sink", GST_PAD_SINK, GST_PAD_ALWAYS, transform->output_caps))) - goto out; - transform->my_sink = gst_pad_new_from_template(template, "sink"); - g_object_unref(template); - if (!transform->my_sink) + if (!(transform->my_sink = create_pad_with_caps(GST_PAD_SINK, transform->output_caps))) goto out; gst_pad_set_element_private(transform->my_sink, transform); @@ -342,18 +355,6 @@ NTSTATUS wg_transform_create(void *args) switch (input_format.major_type) { case WG_MAJOR_TYPE_VIDEO_H264: - /* Call of Duty: Black Ops 3 doesn't care about the ProcessInput/ProcessOutput - * return values, it calls them in a specific order and expects the decoder - * transform to be able to queue its input buffers. We need to use a buffer list - * to match its expectations. - */ - transform->input_max_length = 16; - transform->output_plane_align = 15; - if ((element = create_element("h264parse", "base")) - && !append_element(transform->container, element, &first, &last)) - goto out; - transform->broken_timestamps = !element; - /* fallthrough */ case WG_MAJOR_TYPE_AUDIO_MPEG1: case WG_MAJOR_TYPE_AUDIO_MPEG4: case WG_MAJOR_TYPE_AUDIO_WMA: @@ -370,7 +371,6 @@ NTSTATUS wg_transform_create(void *args) case WG_MAJOR_TYPE_AUDIO: case WG_MAJOR_TYPE_VIDEO: - transform->input_max_length = 16; break; case WG_MAJOR_TYPE_UNKNOWN: GST_FIXME("Format %u not implemented!", input_format.major_type); @@ -401,6 +401,28 @@ NTSTATUS wg_transform_create(void *args) break; case WG_MAJOR_TYPE_VIDEO: + { + const char *sgi; + if ((sgi = getenv("SteamGameId")) && (!strcmp(sgi, "2009100"))) + { + if (!(element = create_element("videoconvert", "base")) + || !append_element(transform->container, element, &first, &last)) + goto out; + gst_util_set_object_arg(G_OBJECT(element), "n-threads", "0"); + /* HACK: skip slow?? videoflip for some games */ + break; + } + } + + if (!(element = create_element("videoconvert", "base")) + || !append_element(transform->container, element, &first, &last)) + goto out; + if (!(transform->video_flip = create_element("videoflip", "base")) + || !append_element(transform->container, transform->video_flip, &first, &last)) + goto out; + transform->input_is_flipped = wg_format_video_is_flipped(&input_format); + if (transform->input_is_flipped != wg_format_video_is_flipped(&output_format)) + gst_util_set_object_arg(G_OBJECT(transform->video_flip), "method", "vertical-flip"); if (!(element = create_element("videoconvert", "base")) || !append_element(transform->container, element, &first, &last)) goto out; @@ -510,6 +532,7 @@ NTSTATUS wg_transform_set_output_format(void *args) GST_ERROR("Failed to convert format %p to caps.", format); return STATUS_UNSUCCESSFUL; } + transform->output_format = *format; if (gst_caps_is_always_compatible(transform->output_caps, caps)) { @@ -526,8 +549,15 @@ NTSTATUS wg_transform_set_output_format(void *args) gst_caps_unref(transform->output_caps); transform->output_caps = caps; - transform->setting_output_format = true; - + if (transform->video_flip) + { + const char *value; + if (transform->input_is_flipped != wg_format_video_is_flipped(format)) + value = "vertical-flip"; + else + value = "none"; + gst_util_set_object_arg(G_OBJECT(transform->video_flip), "method", value); + } if (!gst_pad_push_event(transform->my_sink, gst_event_new_reconfigure())) { GST_ERROR("Failed to reconfigure transform %p.", transform); @@ -551,62 +581,6 @@ NTSTATUS wg_transform_set_output_format(void *args) return STATUS_SUCCESS; } -NTSTATUS wg_transform_drain(void *args) -{ - struct wg_transform_drain_params *params = args; - struct wg_transform *transform = params->transform; - GstBuffer *input_buffer; - GstSample *sample; - GstFlowReturn ret; - GstEvent *event; - - - while ((input_buffer = gst_atomic_queue_pop(transform->input_queue))) - { - if (params->flush) - gst_buffer_unref(input_buffer); - else if ((ret = gst_pad_push(transform->my_src, input_buffer))) - { - GST_ERROR("Failed to push transform input, error %d", ret); - return S_OK; - } - } - - if (!gst_pad_peer_query(transform->my_src, transform->drain_query)) - { - GST_ERROR("Drain query failed, transform %p.", transform); - return MF_E_STREAM_ERROR; - } - if (!(event = gst_event_new_segment_done(GST_FORMAT_TIME, transform->segment.start)) - || !gst_pad_push_event(transform->my_src, event)) - { - GST_ERROR("Sending segment done event failed, transform %p.", transform); - return MF_E_STREAM_ERROR; - } - if (!gst_pad_peer_query(transform->my_src, transform->drain_query)) - { - GST_ERROR("Drain query failed, transform %p.", transform); - return MF_E_STREAM_ERROR; - } - if (!(event = gst_event_new_segment(&transform->segment)) - || !gst_pad_push_event(transform->my_src, event)) - { - GST_ERROR("Sending new segment event failed, transform %p.", transform); - return MF_E_STREAM_ERROR; - } - - if (params->flush) - { - if (transform->output_sample) - gst_sample_unref(transform->output_sample); - while ((sample = gst_atomic_queue_pop(transform->output_queue))) - gst_sample_unref(sample); - transform->output_sample = NULL; - } - - return S_OK; -} - static void wg_sample_free_notify(void *arg) { struct wg_sample *sample = arg; @@ -623,7 +597,7 @@ NTSTATUS wg_transform_push_data(void *args) guint length; length = gst_atomic_queue_length(transform->input_queue); - if (length >= transform->input_max_length) + if (length >= transform->attrs.input_queue_length + 1) { GST_INFO("Refusing %u bytes, %u buffers already queued", sample->size, length); params->result = MF_E_NOTACCEPTING; @@ -741,8 +715,8 @@ static NTSTATUS read_transform_output_data(GstBuffer *buffer, GstCaps *caps, gsi { gsize total_size; bool needs_copy; - GstMapInfo info; NTSTATUS status; + GstMapInfo info; if (!gst_buffer_map(buffer, &info, GST_MAP_READ)) { @@ -751,20 +725,15 @@ static NTSTATUS read_transform_output_data(GstBuffer *buffer, GstCaps *caps, gsi return STATUS_UNSUCCESSFUL; } needs_copy = info.data != sample->data; + total_size = sample->size = info.size; gst_buffer_unmap(buffer, &info); if (!needs_copy) - { - total_size = sample->size = info.size; status = STATUS_SUCCESS; - } + else if (stream_type_from_caps(caps) == GST_STREAM_TYPE_VIDEO) + status = copy_video_buffer(buffer, caps, plane_align, sample, &total_size); else - { - if (stream_type_from_caps(caps) == GST_STREAM_TYPE_VIDEO) - status = copy_video_buffer(buffer, caps, plane_align, sample, &total_size); - else - status = copy_buffer(buffer, caps, sample, &total_size); - } + status = copy_buffer(buffer, caps, sample, &total_size); if (status) { @@ -812,30 +781,25 @@ static NTSTATUS read_transform_output_data(GstBuffer *buffer, GstCaps *caps, gsi static bool get_transform_output(struct wg_transform *transform, struct wg_sample *sample) { - GstFlowReturn ret = GST_FLOW_OK; GstBuffer *input_buffer; + GstFlowReturn ret; /* Provide the sample for transform_request_sample to pick it up */ InterlockedIncrement(&sample->refcount); InterlockedExchangePointer((void **)&transform->output_wg_sample, sample); - while (!(transform->output_sample = gst_atomic_queue_pop(transform->output_queue))) + while (!(transform->output_sample = gst_atomic_queue_pop(transform->output_queue)) + && (input_buffer = gst_atomic_queue_pop(transform->input_queue))) { - if (!(input_buffer = gst_atomic_queue_pop(transform->input_queue))) - break; - if ((ret = gst_pad_push(transform->my_src, input_buffer))) - { - GST_ERROR("Failed to push transform input, error %d", ret); - break; - } + GST_WARNING("Failed to push transform input, error %d", ret); } /* Remove the sample so transform_request_sample cannot use it */ if (InterlockedExchangePointer((void **)&transform->output_wg_sample, NULL)) InterlockedDecrement(&sample->refcount); - return ret == GST_FLOW_OK; + return !!transform->output_sample; } NTSTATUS wg_transform_read_data(void *args) @@ -850,12 +814,6 @@ NTSTATUS wg_transform_read_data(void *args) NTSTATUS status; if (!transform->output_sample && !get_transform_output(transform, sample)) - { - wg_allocator_release_sample(transform->allocator, sample, false); - return STATUS_UNSUCCESSFUL; - } - - if (!transform->output_sample) { sample->size = 0; params->result = MF_E_TRANSFORM_NEED_MORE_INPUT; @@ -873,7 +831,7 @@ NTSTATUS wg_transform_read_data(void *args) if (format) { - gsize plane_align = transform->output_plane_align; + gsize plane_align = transform->attrs.output_plane_align; GstVideoAlignment align; GstVideoInfo info; @@ -905,13 +863,8 @@ NTSTATUS wg_transform_read_data(void *args) } if ((status = read_transform_output_data(output_buffer, output_caps, - transform->output_plane_align, sample))) + transform->attrs.output_plane_align, sample))) { - if (status == STATUS_BUFFER_TOO_SMALL) - { - status = 0; - params->result = E_FAIL; - } wg_allocator_release_sample(transform->allocator, sample, false); return status; } @@ -939,9 +892,6 @@ NTSTATUS wg_transform_read_data(void *args) transform->output_sample = NULL; } - if (transform->broken_timestamps) - sample->flags &= ~(WG_SAMPLE_FLAG_HAS_PTS|WG_SAMPLE_FLAG_HAS_DURATION); - params->result = S_OK; wg_allocator_release_sample(transform->allocator, sample, discard_data); return STATUS_SUCCESS; @@ -952,6 +902,65 @@ NTSTATUS wg_transform_get_status(void *args) struct wg_transform_get_status_params *params = args; struct wg_transform *transform = params->transform; - params->accepts_input = gst_atomic_queue_length(transform->input_queue) < transform->input_max_length; + params->accepts_input = gst_atomic_queue_length(transform->input_queue) < transform->attrs.input_queue_length + 1; + return STATUS_SUCCESS; +} + +NTSTATUS wg_transform_drain(void *args) +{ + struct wg_transform *transform = args; + GstBuffer *input_buffer; + GstFlowReturn ret; + GstEvent *event; + + GST_LOG("transform %p", transform); + + while ((input_buffer = gst_atomic_queue_pop(transform->input_queue))) + { + if ((ret = gst_pad_push(transform->my_src, input_buffer))) + GST_WARNING("Failed to push transform input, error %d", ret); + } + + if (!(event = gst_event_new_segment_done(GST_FORMAT_TIME, -1)) + || !gst_pad_push_event(transform->my_src, event)) + goto error; + if (!(event = gst_event_new_eos()) + || !gst_pad_push_event(transform->my_src, event)) + goto error; + if (!(event = gst_event_new_stream_start("stream")) + || !gst_pad_push_event(transform->my_src, event)) + goto error; + if (!(event = gst_event_new_segment(&transform->segment)) + || !gst_pad_push_event(transform->my_src, event)) + goto error; + + return STATUS_SUCCESS; + +error: + GST_ERROR("Failed to drain transform %p.", transform); + return STATUS_UNSUCCESSFUL; +} + +NTSTATUS wg_transform_flush(void *args) +{ + struct wg_transform *transform = args; + GstBuffer *input_buffer; + GstSample *sample; + NTSTATUS status; + + GST_LOG("transform %p", transform); + + while ((input_buffer = gst_atomic_queue_pop(transform->input_queue))) + gst_buffer_unref(input_buffer); + + if ((status = wg_transform_drain(transform))) + return status; + + while ((sample = gst_atomic_queue_pop(transform->output_queue))) + gst_sample_unref(sample); + if ((sample = transform->output_sample)) + gst_sample_unref(sample); + transform->output_sample = NULL; + return STATUS_SUCCESS; } diff --git a/dlls/winegstreamer/winegstreamer_classes.idl b/dlls/winegstreamer/winegstreamer_classes.idl index 1b87dcde716..13b8a044695 100644 --- a/dlls/winegstreamer/winegstreamer_classes.idl +++ b/dlls/winegstreamer/winegstreamer_classes.idl @@ -113,8 +113,7 @@ coclass CResamplerMediaObject {} coclass CColorConvertDMO {} [ - helpstring("GStreamer scheme handler"), threading(both), uuid(587eeb6a-7336-4ebd-a4f2-91c948de622c) ] -coclass GStreamerSchemePlugin { } +coclass GStreamerSchemeHandler {} diff --git a/dlls/winegstreamer/wm_reader.c b/dlls/winegstreamer/wm_reader.c index 5e86b4f2b33..05b6960abe4 100644 --- a/dlls/winegstreamer/wm_reader.c +++ b/dlls/winegstreamer/wm_reader.c @@ -1522,6 +1522,10 @@ static HRESULT init_stream(struct wm_reader *reader, QWORD file_size) if (id && !strcmp(id, "1113000")) stream->format.u.video.format = WG_VIDEO_FORMAT_BGRx; } + + /* API consumers expect RGB video to be bottom-up. */ + if (stream->format.u.video.height > 0) + stream->format.u.video.height = -stream->format.u.video.height; } wg_parser_stream_enable(stream->wg_stream, &stream->format, STREAM_ENABLE_FLAG_FLIP_RGB); } @@ -1935,6 +1939,9 @@ static HRESULT WINAPI reader_GetOutputFormat(IWMSyncReader2 *iface, return NS_E_INVALID_OUTPUT_FORMAT; } format.u.video.format = video_formats[index]; + /* API consumers expect RGB video to be bottom-up. */ + if (format.u.video.height > 0 && wg_video_format_is_rgb(format.u.video.format)) + format.u.video.height = -format.u.video.height; break; case WG_MAJOR_TYPE_AUDIO: @@ -2227,7 +2234,7 @@ static HRESULT WINAPI reader_SetOutputProps(IWMSyncReader2 *iface, DWORD output, hr = NS_E_INVALID_OUTPUT_FORMAT; else if (pref_format.u.video.width != format.u.video.width) hr = NS_E_INVALID_OUTPUT_FORMAT; - else if (pref_format.u.video.height != format.u.video.height) + else if (abs(pref_format.u.video.height) != abs(format.u.video.height)) hr = NS_E_INVALID_OUTPUT_FORMAT; break; diff --git a/dlls/winegstreamer/wma_decoder.c b/dlls/winegstreamer/wma_decoder.c index fa649fea63b..eff8c414ea8 100644 --- a/dlls/winegstreamer/wma_decoder.c +++ b/dlls/winegstreamer/wma_decoder.c @@ -76,6 +76,7 @@ static inline struct wma_decoder *impl_from_IUnknown(IUnknown *iface) static HRESULT try_create_wg_transform(struct wma_decoder *decoder) { struct wg_format input_format, output_format; + struct wg_transform_attrs attrs = {0}; if (decoder->wg_transform) wg_transform_destroy(decoder->wg_transform); @@ -89,7 +90,7 @@ static HRESULT try_create_wg_transform(struct wma_decoder *decoder) if (output_format.major_type == WG_MAJOR_TYPE_UNKNOWN) return MF_E_INVALIDMEDIATYPE; - if (!(decoder->wg_transform = wg_transform_create(&input_format, &output_format))) + if (!(decoder->wg_transform = wg_transform_create(&input_format, &output_format, &attrs))) return E_FAIL; return S_OK; @@ -530,7 +531,7 @@ static HRESULT WINAPI transform_ProcessMessage(IMFTransform *iface, MFT_MESSAGE_ return MF_E_TRANSFORM_TYPE_NOT_SET; if (message == MFT_MESSAGE_COMMAND_DRAIN) - return wg_transform_drain(decoder->wg_transform, FALSE); + return wg_transform_drain(decoder->wg_transform); FIXME("Ignoring message %#x.\n", message); @@ -752,6 +753,8 @@ static HRESULT WINAPI media_object_SetInputType(IMediaObject *iface, DWORD index return VFW_E_INVALIDMEDIATYPE; if (!(flags & DMO_SET_TYPEF_TEST_ONLY)) { + struct wg_transform_attrs attrs = {0}; + impl->input_format = wg_format; if (!impl->output_format.major_type) return S_OK; @@ -760,7 +763,7 @@ static HRESULT WINAPI media_object_SetInputType(IMediaObject *iface, DWORD index wg_transform_destroy(impl->wg_transform); impl->wg_transform = NULL; - if (!(impl->wg_transform = wg_transform_create(&impl->input_format, &impl->output_format))) + if (!(impl->wg_transform = wg_transform_create(&impl->input_format, &impl->output_format, &attrs))) return E_FAIL; } @@ -788,6 +791,8 @@ static HRESULT WINAPI media_object_SetOutputType(IMediaObject *iface, DWORD inde return VFW_E_INVALIDMEDIATYPE; if (!(flags & DMO_SET_TYPEF_TEST_ONLY)) { + struct wg_transform_attrs attrs = {0}; + impl->output_format = wg_format; if (!impl->input_format.major_type) return S_OK; @@ -796,7 +801,7 @@ static HRESULT WINAPI media_object_SetOutputType(IMediaObject *iface, DWORD inde wg_transform_destroy(impl->wg_transform); impl->wg_transform = NULL; - if (!(impl->wg_transform = wg_transform_create(&impl->input_format, &impl->output_format))) + if (!(impl->wg_transform = wg_transform_create(&impl->input_format, &impl->output_format, &attrs))) return E_FAIL; } @@ -1018,13 +1023,14 @@ HRESULT wma_decoder_create(IUnknown *outer, IUnknown **out) }, }; static const struct wg_format input_format = {.major_type = WG_MAJOR_TYPE_AUDIO_WMA}; + struct wg_transform_attrs attrs = {0}; struct wg_transform *transform; struct wma_decoder *decoder; HRESULT hr; TRACE("outer %p, out %p.\n", outer, out); - if (!(transform = wg_transform_create(&input_format, &output_format))) + if (!(transform = wg_transform_create(&input_format, &output_format, &attrs))) { ERR_(winediag)("GStreamer doesn't support WMA decoding, please install appropriate plugins\n"); return E_FAIL; diff --git a/dlls/winegstreamer/wmv_decoder.c b/dlls/winegstreamer/wmv_decoder.c index b81639f312a..9af4a18a5fd 100644 --- a/dlls/winegstreamer/wmv_decoder.c +++ b/dlls/winegstreamer/wmv_decoder.c @@ -382,7 +382,12 @@ static HRESULT WINAPI media_object_GetInputType(IMediaObject *iface, DWORD index if (!format.u.video.width) format.u.video.width = 1920; if (!format.u.video.height) - format.u.video.height = 1080; + { + if (wg_video_format_is_rgb(format.u.video.format)) + format.u.video.height = -1080; + else + format.u.video.height = 1080; + } if (!format.u.video.fps_d) format.u.video.fps_d = 1; if (!format.u.video.fps_n) @@ -410,12 +415,16 @@ static HRESULT WINAPI media_object_GetOutputType(IMediaObject *iface, DWORD inde if (!format.u.video.width) format.u.video.width = 1920; if (!format.u.video.height) - format.u.video.height = 1080; + { + if (wg_video_format_is_rgb(format.u.video.format)) + format.u.video.height = -1080; + else + format.u.video.height = 1080; + } if (!format.u.video.fps_d) format.u.video.fps_d = 1; if (!format.u.video.fps_n) format.u.video.fps_n = 1; - if (!amt_from_wg_format((AM_MEDIA_TYPE *)type, &format, false)) return VFW_E_NO_TYPES; diff --git a/dlls/winemac.drv/Makefile.in b/dlls/winemac.drv/Makefile.in index 9735890b221..7228dfbac4f 100644 --- a/dlls/winemac.drv/Makefile.in +++ b/dlls/winemac.drv/Makefile.in @@ -12,7 +12,6 @@ C_SRCS = \ event.c \ gdi.c \ image.c \ - ime.c \ keyboard.c \ macdrv_main.c \ mouse.c \ diff --git a/dlls/winemac.drv/cocoa_window.h b/dlls/winemac.drv/cocoa_window.h index a83f2aa803b..9539e4ebdd7 100644 --- a/dlls/winemac.drv/cocoa_window.h +++ b/dlls/winemac.drv/cocoa_window.h @@ -65,7 +65,7 @@ NSRect frameAtResizeStart; BOOL resizingFromLeft, resizingFromTop; - void* imeData; + void* himc; BOOL commandDone; NSSize savedContentMinSize; diff --git a/dlls/winemac.drv/cocoa_window.m b/dlls/winemac.drv/cocoa_window.m index 2525c894d09..67e178c8ad3 100644 --- a/dlls/winemac.drv/cocoa_window.m +++ b/dlls/winemac.drv/cocoa_window.m @@ -403,7 +403,7 @@ @interface WineWindow () @property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue; @property (nonatomic) BOOL usePerPixelAlpha; -@property (assign, nonatomic) void* imeData; +@property (assign, nonatomic) void* himc; @property (nonatomic) BOOL commandDone; @property (readonly, copy, nonatomic) NSArray* childWineWindows; @@ -714,7 +714,7 @@ - (void) completeText:(NSString*)text WineWindow* window = (WineWindow*)[self window]; event = macdrv_create_event(IM_SET_TEXT, window); - event->im_set_text.data = [window imeData]; + event->im_set_text.himc = [window himc]; event->im_set_text.text = (CFStringRef)[text copy]; event->im_set_text.complete = TRUE; @@ -797,7 +797,7 @@ - (void) setMarkedText:(id)string selectedRange:(NSRange)selectedRange replaceme markedTextSelection.location += replacementRange.location; event = macdrv_create_event(IM_SET_TEXT, window); - event->im_set_text.data = [window imeData]; + event->im_set_text.himc = [window himc]; event->im_set_text.text = (CFStringRef)[[markedText string] copy]; event->im_set_text.complete = FALSE; event->im_set_text.cursor_pos = markedTextSelection.location + markedTextSelection.length; @@ -860,7 +860,7 @@ - (NSRect) firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointe query = macdrv_create_query(); query->type = QUERY_IME_CHAR_RECT; query->window = (macdrv_window)[window retain]; - query->ime_char_rect.data = [window imeData]; + query->ime_char_rect.himc = [window himc]; query->ime_char_rect.range = CFRangeMake(aRange.location, aRange.length); if ([window.queue query:query timeout:0.3 flags:WineQueryNoPreemptWait]) @@ -947,7 +947,7 @@ @implementation WineWindow @synthesize shapeChangedSinceLastDraw; @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue; @synthesize usePerPixelAlpha; - @synthesize imeData, commandDone; + @synthesize himc, commandDone; + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf windowFrame:(NSRect)window_frame @@ -3903,7 +3903,7 @@ uint32_t macdrv_window_background_color(void) /*********************************************************************** * macdrv_send_text_input_event */ -void macdrv_send_text_input_event(int pressed, unsigned int flags, int repeat, int keyc, void* data, int* done) +void macdrv_send_text_input_event(int pressed, unsigned int flags, int repeat, int keyc, void* himc, int* done) { OnMainThreadAsync(^{ BOOL ret; @@ -3922,7 +3922,7 @@ void macdrv_send_text_input_event(int pressed, unsigned int flags, int repeat, i CGEventRef c; NSEvent* event; - window.imeData = data; + window.himc = himc; fix_device_modifiers_by_generic(&localFlags); // An NSEvent created with +keyEventWithType:... is internally marked diff --git a/dlls/winemac.drv/dllmain.c b/dlls/winemac.drv/dllmain.c index 41e9e908b61..b20f8d71dc9 100644 --- a/dlls/winemac.drv/dllmain.c +++ b/dlls/winemac.drv/dllmain.c @@ -374,8 +374,6 @@ static const kernel_callback kernel_callbacks[] = macdrv_dnd_query_drag, macdrv_dnd_query_drop, macdrv_dnd_query_exited, - macdrv_ime_query_char_rect, - macdrv_ime_set_text, }; C_ASSERT(NtUserDriverCallbackFirst + ARRAYSIZE(kernel_callbacks) == client_func_last); diff --git a/dlls/winemac.drv/event.c b/dlls/winemac.drv/event.c index 5953dc0e0d8..11f5dd3314c 100644 --- a/dlls/winemac.drv/event.c +++ b/dlls/winemac.drv/event.c @@ -26,12 +26,32 @@ #include "config.h" +#include "ntstatus.h" +#define WIN32_NO_STATUS #include "macdrv.h" #include "oleidl.h" WINE_DEFAULT_DEBUG_CHANNEL(event); WINE_DECLARE_DEBUG_CHANNEL(imm); +/* IME works synchronously, key input is passed from ImeProcessKey, to the + * host IME. We wait for it to be handled, or not, which is notified using + * the sent_text_input event. Meanwhile, while processing the key, the host + * IME may send one or more im_set_text events to update the input text. + * + * If ImeProcessKey returns TRUE, ImeToAsciiEx is then be called to retrieve + * the composition string updates. We use ime_update.comp_str != NULL as flag that + * composition is started, even if the preedit text is empty. + * + * If ImeProcessKey returns FALSE, ImeToAsciiEx will not be called. + */ +struct ime_update +{ + DWORD cursor_pos; + WCHAR *comp_str; + WCHAR *result_str; +}; +static struct ime_update ime_update; /* return the name of an Mac event */ static const char *dbgstr_event(int type) @@ -151,26 +171,35 @@ static macdrv_event_mask get_event_mask(DWORD mask) static void macdrv_im_set_text(const macdrv_event *event) { HWND hwnd = macdrv_get_window_hwnd(event->window); - struct ime_set_text_params *params; - CFIndex length = 0, size; + CFIndex length = 0; + WCHAR *text = NULL; - TRACE_(imm)("win %p/%p himc %p text %s complete %u\n", hwnd, event->window, event->im_set_text.data, + TRACE_(imm)("win %p/%p himc %p text %s complete %u\n", hwnd, event->window, event->im_set_text.himc, debugstr_cf(event->im_set_text.text), event->im_set_text.complete); if (event->im_set_text.text) + { length = CFStringGetLength(event->im_set_text.text); + if (!(text = malloc((length + 1) * sizeof(WCHAR)))) return; + if (length) CFStringGetCharacters(event->im_set_text.text, CFRangeMake(0, length), text); + text[length] = 0; + } - size = offsetof(struct ime_set_text_params, text[length]); - if (!(params = malloc(size))) return; - params->hwnd = HandleToUlong(hwnd); - params->data = (UINT_PTR)event->im_set_text.data; - params->cursor_pos = event->im_set_text.cursor_pos; - params->complete = event->im_set_text.complete; - - if (length) - CFStringGetCharacters(event->im_set_text.text, CFRangeMake(0, length), params->text); + /* discard any pending comp text */ + free(ime_update.comp_str); + ime_update.comp_str = NULL; + ime_update.cursor_pos = -1; - macdrv_client_func(client_func_ime_set_text, params, size); + if (event->im_set_text.complete) + { + free(ime_update.result_str); + ime_update.result_str = text; + } + else + { + ime_update.comp_str = text; + ime_update.cursor_pos = event->im_set_text.cursor_pos; + } } /*********************************************************************** @@ -179,7 +208,90 @@ static void macdrv_im_set_text(const macdrv_event *event) static void macdrv_sent_text_input(const macdrv_event *event) { TRACE_(imm)("handled: %s\n", event->sent_text_input.handled ? "TRUE" : "FALSE"); - *event->sent_text_input.done = event->sent_text_input.handled ? 1 : -1; + *event->sent_text_input.done = event->sent_text_input.handled || ime_update.result_str ? 1 : -1; +} + + +/*********************************************************************** + * ImeToAsciiEx (MACDRV.@) + */ +UINT macdrv_ImeToAsciiEx(UINT vkey, UINT vsc, const BYTE *state, COMPOSITIONSTRING *compstr, HIMC himc) +{ + UINT needed = sizeof(COMPOSITIONSTRING), comp_len, result_len; + struct ime_update *update = &ime_update; + void *dst; + + TRACE_(imm)("vkey %#x, vsc %#x, state %p, compstr %p, himc %p\n", vkey, vsc, state, compstr, himc); + + if (!update->comp_str) comp_len = 0; + else + { + comp_len = wcslen(update->comp_str); + needed += comp_len * sizeof(WCHAR); /* GCS_COMPSTR */ + needed += comp_len; /* GCS_COMPATTR */ + needed += 2 * sizeof(DWORD); /* GCS_COMPCLAUSE */ + } + + if (!update->result_str) result_len = 0; + else + { + result_len = wcslen(update->result_str); + needed += result_len * sizeof(WCHAR); /* GCS_RESULTSTR */ + needed += 2 * sizeof(DWORD); /* GCS_RESULTCLAUSE */ + } + + if (compstr->dwSize < needed) + { + compstr->dwSize = needed; + return STATUS_BUFFER_TOO_SMALL; + } + + memset( compstr, 0, sizeof(*compstr) ); + compstr->dwSize = sizeof(*compstr); + + if (update->comp_str) + { + compstr->dwCursorPos = update->cursor_pos; + + compstr->dwCompStrLen = comp_len; + compstr->dwCompStrOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwCompStrOffset; + memcpy(dst, update->comp_str, compstr->dwCompStrLen * sizeof(WCHAR)); + compstr->dwSize += compstr->dwCompStrLen * sizeof(WCHAR); + + compstr->dwCompClauseLen = 2 * sizeof(DWORD); + compstr->dwCompClauseOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwCompClauseOffset; + *((DWORD *)dst + 0) = 0; + *((DWORD *)dst + 1) = compstr->dwCompStrLen; + compstr->dwSize += compstr->dwCompClauseLen; + + compstr->dwCompAttrLen = compstr->dwCompStrLen; + compstr->dwCompAttrOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwCompAttrOffset; + memset(dst, ATTR_INPUT, compstr->dwCompAttrLen); + compstr->dwSize += compstr->dwCompAttrLen; + } + + if (update->result_str) + { + compstr->dwResultStrLen = result_len; + compstr->dwResultStrOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwResultStrOffset; + memcpy(dst, update->result_str, compstr->dwResultStrLen * sizeof(WCHAR)); + compstr->dwSize += compstr->dwResultStrLen * sizeof(WCHAR); + + compstr->dwResultClauseLen = 2 * sizeof(DWORD); + compstr->dwResultClauseOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwResultClauseOffset; + *((DWORD *)dst + 0) = 0; + *((DWORD *)dst + 1) = compstr->dwResultStrLen; + compstr->dwSize += compstr->dwResultClauseLen; + } + + free(update->result_str); + update->result_str = NULL; + return 0; } @@ -287,32 +399,38 @@ static BOOL query_drag_operation(macdrv_query *query) BOOL query_ime_char_rect(macdrv_query* query) { HWND hwnd = macdrv_get_window_hwnd(query->window); - void *himc = query->ime_char_rect.data; + void *himc = query->ime_char_rect.himc; CFRange *range = &query->ime_char_rect.range; - CGRect *rect = &query->ime_char_rect.rect; - struct ime_query_char_rect_result result = { .location = 0 }; - struct ime_query_char_rect_params params; - BOOL ret; + GUITHREADINFO info = {.cbSize = sizeof(info)}; + BOOL ret = FALSE; TRACE_(imm)("win %p/%p himc %p range %ld-%ld\n", hwnd, query->window, himc, range->location, range->length); - params.hwnd = HandleToUlong(hwnd); - params.data = (UINT_PTR)himc; - params.result = (UINT_PTR)&result; - params.location = range->location; - params.length = range->length; - ret = macdrv_client_func(client_func_ime_query_char_rect, ¶ms, sizeof(params)); - *range = CFRangeMake(result.location, result.length); - *rect = cgrect_from_rect(result.rect); + if (NtUserGetGUIThreadInfo(0, &info)) + { + NtUserMapWindowPoints(info.hwndCaret, 0, (POINT*)&info.rcCaret, 2); + if (range->length && info.rcCaret.left == info.rcCaret.right) info.rcCaret.right++; + query->ime_char_rect.rect = cgrect_from_rect(info.rcCaret); + } TRACE_(imm)(" -> %s range %ld-%ld rect %s\n", ret ? "TRUE" : "FALSE", range->location, - range->length, wine_dbgstr_cgrect(*rect)); + range->length, wine_dbgstr_cgrect(query->ime_char_rect.rect)); return ret; } +/*********************************************************************** + * NotifyIMEStatus (X11DRV.@) + */ +void macdrv_NotifyIMEStatus( HWND hwnd, UINT status ) +{ + TRACE_(imm)( "hwnd %p, status %#x\n", hwnd, status ); + if (!status) macdrv_clear_ime_text(); +} + + /*********************************************************************** * macdrv_query_event * diff --git a/dlls/winemac.drv/gdi.c b/dlls/winemac.drv/gdi.c index d22532fd3b7..4d2f25983f8 100644 --- a/dlls/winemac.drv/gdi.c +++ b/dlls/winemac.drv/gdi.c @@ -271,7 +271,6 @@ static const struct user_driver_funcs macdrv_funcs = .pChangeDisplaySettings = macdrv_ChangeDisplaySettings, .pClipCursor = macdrv_ClipCursor, .pClipboardWindowProc = macdrv_ClipboardWindowProc, - .pCreateDesktopWindow = macdrv_CreateDesktopWindow, .pDesktopWindowProc = macdrv_DesktopWindowProc, .pDestroyCursorIcon = macdrv_DestroyCursorIcon, .pDestroyWindow = macdrv_DestroyWindow, @@ -287,6 +286,7 @@ static const struct user_driver_funcs macdrv_funcs = .pSetCapture = macdrv_SetCapture, .pSetCursor = macdrv_SetCursor, .pSetCursorPos = macdrv_SetCursorPos, + .pSetDesktopWindow = macdrv_SetDesktopWindow, .pSetFocus = macdrv_SetFocus, .pSetLayeredWindowAttributes = macdrv_SetLayeredWindowAttributes, .pSetParent = macdrv_SetParent, @@ -302,6 +302,9 @@ static const struct user_driver_funcs macdrv_funcs = .pUpdateClipboard = macdrv_UpdateClipboard, .pUpdateLayeredWindow = macdrv_UpdateLayeredWindow, .pVkKeyScanEx = macdrv_VkKeyScanEx, + .pImeProcessKey = macdrv_ImeProcessKey, + .pImeToAsciiEx = macdrv_ImeToAsciiEx, + .pNotifyIMEStatus = macdrv_NotifyIMEStatus, .pWindowMessage = macdrv_WindowMessage, .pWindowPosChanged = macdrv_WindowPosChanged, .pWindowPosChanging = macdrv_WindowPosChanging, diff --git a/dlls/winemac.drv/ime.c b/dlls/winemac.drv/ime.c deleted file mode 100644 index 4bdfcbc6730..00000000000 --- a/dlls/winemac.drv/ime.c +++ /dev/null @@ -1,1544 +0,0 @@ -/* - * The IME for interfacing with Mac input methods - * - * Copyright 2008, 2013 CodeWeavers, Aric Stewart - * Copyright 2013 Ken Thomases for CodeWeavers Inc. - * - * 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 - */ - -/* - * Notes: - * The normal flow for IMM/IME Processing is as follows. - * 1) The Keyboard Driver generates key messages which are first passed to - * the IMM and then to IME via ImeProcessKey. If the IME returns 0 then - * it does not want the key and the keyboard driver then generates the - * WM_KEYUP/WM_KEYDOWN messages. However if the IME is going to process the - * key it returns non-zero. - * 2) If the IME is going to process the key then the IMM calls ImeToAsciiEx to - * process the key. the IME modifies the HIMC structure to reflect the - * current state and generates any messages it needs the IMM to process. - * 3) IMM checks the messages and send them to the application in question. From - * here the IMM level deals with if the application is IME aware or not. - */ - -#include "macdrv_dll.h" -#include "imm.h" -#include "immdev.h" -#include "wine/server.h" -#include "wine/debug.h" - -WINE_DEFAULT_DEBUG_CHANNEL(imm); - -#define FROM_MACDRV ((HIMC)0xcafe1337) - -typedef struct _IMEPRIVATE { - BOOL bInComposition; - BOOL bInternalState; - HFONT textfont; - HWND hwndDefault; - - UINT repeat; -} IMEPRIVATE, *LPIMEPRIVATE; - -static const WCHAR UI_CLASS_NAME[] = {'W','i','n','e',' ','M','a','c',' ','I','M','E',0}; - -static HIMC *hSelectedFrom = NULL; -static INT hSelectedCount = 0; - -/* MSIME messages */ -static UINT WM_MSIME_SERVICE; -static UINT WM_MSIME_RECONVERTOPTIONS; -static UINT WM_MSIME_MOUSE; -static UINT WM_MSIME_RECONVERTREQUEST; -static UINT WM_MSIME_RECONVERT; -static UINT WM_MSIME_QUERYPOSITION; -static UINT WM_MSIME_DOCUMENTFEED; - -static HIMC RealIMC(HIMC hIMC) -{ - if (hIMC == FROM_MACDRV) - { - INT i; - HWND wnd = GetFocus(); - HIMC winHimc = ImmGetContext(wnd); - for (i = 0; i < hSelectedCount; i++) - if (winHimc == hSelectedFrom[i]) - return winHimc; - return NULL; - } - else - return hIMC; -} - -static LPINPUTCONTEXT LockRealIMC(HIMC hIMC) -{ - HIMC real_imc = RealIMC(hIMC); - if (real_imc) - return ImmLockIMC(real_imc); - else - return NULL; -} - -static BOOL UnlockRealIMC(HIMC hIMC) -{ - HIMC real_imc = RealIMC(hIMC); - if (real_imc) - return ImmUnlockIMC(real_imc); - else - return FALSE; -} - -static HIMCC ImeCreateBlankCompStr(void) -{ - HIMCC rc; - LPCOMPOSITIONSTRING ptr; - rc = ImmCreateIMCC(sizeof(COMPOSITIONSTRING)); - ptr = ImmLockIMCC(rc); - memset(ptr, 0, sizeof(COMPOSITIONSTRING)); - ptr->dwSize = sizeof(COMPOSITIONSTRING); - ImmUnlockIMCC(rc); - return rc; -} - -static int updateField(DWORD origLen, DWORD origOffset, DWORD currentOffset, - LPBYTE target, LPBYTE source, DWORD* lenParam, - DWORD* offsetParam, BOOL wchars) -{ - if (origLen > 0 && origOffset > 0) - { - int truelen = origLen; - if (wchars) - truelen *= sizeof(WCHAR); - - memcpy(&target[currentOffset], &source[origOffset], truelen); - - *lenParam = origLen; - *offsetParam = currentOffset; - currentOffset += truelen; - } - return currentOffset; -} - -static HIMCC updateCompStr(HIMCC old, LPCWSTR compstr, DWORD len, DWORD *flags) -{ - /* We need to make sure the CompStr, CompClause and CompAttr fields are all - * set and correct. */ - int needed_size; - HIMCC rc; - LPBYTE newdata = NULL; - LPBYTE olddata = NULL; - LPCOMPOSITIONSTRING new_one; - LPCOMPOSITIONSTRING lpcs = NULL; - INT current_offset = 0; - - TRACE("%s, %li\n", debugstr_wn(compstr, len), len); - - if (old == NULL && compstr == NULL && len == 0) - return NULL; - - if (compstr == NULL && len != 0) - { - ERR("compstr is NULL however we have a len! Please report\n"); - len = 0; - } - - if (old != NULL) - { - olddata = ImmLockIMCC(old); - lpcs = (LPCOMPOSITIONSTRING)olddata; - } - - needed_size = sizeof(COMPOSITIONSTRING) + len * sizeof(WCHAR) + - len + sizeof(DWORD) * 2; - - if (lpcs != NULL) - { - needed_size += lpcs->dwCompReadAttrLen; - needed_size += lpcs->dwCompReadClauseLen; - needed_size += lpcs->dwCompReadStrLen * sizeof(WCHAR); - needed_size += lpcs->dwResultReadClauseLen; - needed_size += lpcs->dwResultReadStrLen * sizeof(WCHAR); - needed_size += lpcs->dwResultClauseLen; - needed_size += lpcs->dwResultStrLen * sizeof(WCHAR); - needed_size += lpcs->dwPrivateSize; - } - rc = ImmCreateIMCC(needed_size); - newdata = ImmLockIMCC(rc); - new_one = (LPCOMPOSITIONSTRING)newdata; - - new_one->dwSize = needed_size; - current_offset = sizeof(COMPOSITIONSTRING); - if (lpcs != NULL) - { - current_offset = updateField(lpcs->dwCompReadAttrLen, - lpcs->dwCompReadAttrOffset, - current_offset, newdata, olddata, - &new_one->dwCompReadAttrLen, - &new_one->dwCompReadAttrOffset, FALSE); - - current_offset = updateField(lpcs->dwCompReadClauseLen, - lpcs->dwCompReadClauseOffset, - current_offset, newdata, olddata, - &new_one->dwCompReadClauseLen, - &new_one->dwCompReadClauseOffset, FALSE); - - current_offset = updateField(lpcs->dwCompReadStrLen, - lpcs->dwCompReadStrOffset, - current_offset, newdata, olddata, - &new_one->dwCompReadStrLen, - &new_one->dwCompReadStrOffset, TRUE); - - /* new CompAttr, CompClause, CompStr, dwCursorPos */ - new_one->dwDeltaStart = 0; - new_one->dwCursorPos = lpcs->dwCursorPos; - - current_offset = updateField(lpcs->dwResultReadClauseLen, - lpcs->dwResultReadClauseOffset, - current_offset, newdata, olddata, - &new_one->dwResultReadClauseLen, - &new_one->dwResultReadClauseOffset, FALSE); - - current_offset = updateField(lpcs->dwResultReadStrLen, - lpcs->dwResultReadStrOffset, - current_offset, newdata, olddata, - &new_one->dwResultReadStrLen, - &new_one->dwResultReadStrOffset, TRUE); - - current_offset = updateField(lpcs->dwResultClauseLen, - lpcs->dwResultClauseOffset, - current_offset, newdata, olddata, - &new_one->dwResultClauseLen, - &new_one->dwResultClauseOffset, FALSE); - - current_offset = updateField(lpcs->dwResultStrLen, - lpcs->dwResultStrOffset, - current_offset, newdata, olddata, - &new_one->dwResultStrLen, - &new_one->dwResultStrOffset, TRUE); - - current_offset = updateField(lpcs->dwPrivateSize, - lpcs->dwPrivateOffset, - current_offset, newdata, olddata, - &new_one->dwPrivateSize, - &new_one->dwPrivateOffset, FALSE); - } - else - { - new_one->dwCursorPos = len; - *flags |= GCS_CURSORPOS; - } - - /* set new data */ - /* CompAttr */ - new_one->dwCompAttrLen = len; - if (len > 0) - { - new_one->dwCompAttrOffset = current_offset; - memset(&newdata[current_offset], ATTR_INPUT, len); - current_offset += len; - } - - /* CompClause */ - if (len > 0) - { - new_one->dwCompClauseLen = sizeof(DWORD) * 2; - new_one->dwCompClauseOffset = current_offset; - *(DWORD*)&newdata[current_offset] = 0; - current_offset += sizeof(DWORD); - *(DWORD*)&newdata[current_offset] = len; - current_offset += sizeof(DWORD); - } - else - new_one->dwCompClauseLen = 0; - - /* CompStr */ - new_one->dwCompStrLen = len; - if (len > 0) - { - new_one->dwCompStrOffset = current_offset; - memcpy(&newdata[current_offset], compstr, len * sizeof(WCHAR)); - } - - - ImmUnlockIMCC(rc); - if (lpcs) - ImmUnlockIMCC(old); - - return rc; -} - -static HIMCC updateResultStr(HIMCC old, LPWSTR resultstr, DWORD len) -{ - /* we need to make sure the ResultStr and ResultClause fields are all - * set and correct */ - int needed_size; - HIMCC rc; - LPBYTE newdata = NULL; - LPBYTE olddata = NULL; - LPCOMPOSITIONSTRING new_one; - LPCOMPOSITIONSTRING lpcs = NULL; - INT current_offset = 0; - - TRACE("%s, %li\n", debugstr_wn(resultstr, len), len); - - if (old == NULL && resultstr == NULL && len == 0) - return NULL; - - if (resultstr == NULL && len != 0) - { - ERR("resultstr is NULL however we have a len! Please report\n"); - len = 0; - } - - if (old != NULL) - { - olddata = ImmLockIMCC(old); - lpcs = (LPCOMPOSITIONSTRING)olddata; - } - - needed_size = sizeof(COMPOSITIONSTRING) + len * sizeof(WCHAR) + - sizeof(DWORD) * 2; - - if (lpcs != NULL) - { - needed_size += lpcs->dwCompReadAttrLen; - needed_size += lpcs->dwCompReadClauseLen; - needed_size += lpcs->dwCompReadStrLen * sizeof(WCHAR); - needed_size += lpcs->dwCompAttrLen; - needed_size += lpcs->dwCompClauseLen; - needed_size += lpcs->dwCompStrLen * sizeof(WCHAR); - needed_size += lpcs->dwResultReadClauseLen; - needed_size += lpcs->dwResultReadStrLen * sizeof(WCHAR); - needed_size += lpcs->dwPrivateSize; - } - rc = ImmCreateIMCC(needed_size); - newdata = ImmLockIMCC(rc); - new_one = (LPCOMPOSITIONSTRING)newdata; - - new_one->dwSize = needed_size; - current_offset = sizeof(COMPOSITIONSTRING); - if (lpcs != NULL) - { - current_offset = updateField(lpcs->dwCompReadAttrLen, - lpcs->dwCompReadAttrOffset, - current_offset, newdata, olddata, - &new_one->dwCompReadAttrLen, - &new_one->dwCompReadAttrOffset, FALSE); - - current_offset = updateField(lpcs->dwCompReadClauseLen, - lpcs->dwCompReadClauseOffset, - current_offset, newdata, olddata, - &new_one->dwCompReadClauseLen, - &new_one->dwCompReadClauseOffset, FALSE); - - current_offset = updateField(lpcs->dwCompReadStrLen, - lpcs->dwCompReadStrOffset, - current_offset, newdata, olddata, - &new_one->dwCompReadStrLen, - &new_one->dwCompReadStrOffset, TRUE); - - current_offset = updateField(lpcs->dwCompAttrLen, - lpcs->dwCompAttrOffset, - current_offset, newdata, olddata, - &new_one->dwCompAttrLen, - &new_one->dwCompAttrOffset, FALSE); - - current_offset = updateField(lpcs->dwCompClauseLen, - lpcs->dwCompClauseOffset, - current_offset, newdata, olddata, - &new_one->dwCompClauseLen, - &new_one->dwCompClauseOffset, FALSE); - - current_offset = updateField(lpcs->dwCompStrLen, - lpcs->dwCompStrOffset, - current_offset, newdata, olddata, - &new_one->dwCompStrLen, - &new_one->dwCompStrOffset, TRUE); - - new_one->dwCursorPos = lpcs->dwCursorPos; - new_one->dwDeltaStart = 0; - - current_offset = updateField(lpcs->dwResultReadClauseLen, - lpcs->dwResultReadClauseOffset, - current_offset, newdata, olddata, - &new_one->dwResultReadClauseLen, - &new_one->dwResultReadClauseOffset, FALSE); - - current_offset = updateField(lpcs->dwResultReadStrLen, - lpcs->dwResultReadStrOffset, - current_offset, newdata, olddata, - &new_one->dwResultReadStrLen, - &new_one->dwResultReadStrOffset, TRUE); - - /* new ResultClause , ResultStr */ - - current_offset = updateField(lpcs->dwPrivateSize, - lpcs->dwPrivateOffset, - current_offset, newdata, olddata, - &new_one->dwPrivateSize, - &new_one->dwPrivateOffset, FALSE); - } - - /* set new data */ - /* ResultClause */ - if (len > 0) - { - new_one->dwResultClauseLen = sizeof(DWORD) * 2; - new_one->dwResultClauseOffset = current_offset; - *(DWORD*)&newdata[current_offset] = 0; - current_offset += sizeof(DWORD); - *(DWORD*)&newdata[current_offset] = len; - current_offset += sizeof(DWORD); - } - else - new_one->dwResultClauseLen = 0; - - /* ResultStr */ - new_one->dwResultStrLen = len; - if (len > 0) - { - new_one->dwResultStrOffset = current_offset; - memcpy(&newdata[current_offset], resultstr, len * sizeof(WCHAR)); - } - ImmUnlockIMCC(rc); - if (lpcs) - ImmUnlockIMCC(old); - - return rc; -} - -static void GenerateIMEMessage(HIMC hIMC, UINT msg, WPARAM wParam, LPARAM lParam) -{ - LPINPUTCONTEXT lpIMC; - LPTRANSMSG lpTransMsg; - - lpIMC = LockRealIMC(hIMC); - if (lpIMC == NULL) - return; - - lpIMC->hMsgBuf = ImmReSizeIMCC(lpIMC->hMsgBuf, (lpIMC->dwNumMsgBuf + 1) * sizeof(TRANSMSG)); - if (!lpIMC->hMsgBuf) - return; - - lpTransMsg = ImmLockIMCC(lpIMC->hMsgBuf); - if (!lpTransMsg) - return; - - lpTransMsg += lpIMC->dwNumMsgBuf; - lpTransMsg->message = msg; - lpTransMsg->wParam = wParam; - lpTransMsg->lParam = lParam; - - ImmUnlockIMCC(lpIMC->hMsgBuf); - lpIMC->dwNumMsgBuf++; - - ImmGenerateMessage(RealIMC(hIMC)); - UnlockRealIMC(hIMC); -} - -static BOOL GenerateMessageToTransKey(TRANSMSGLIST *lpTransBuf, UINT *uNumTranMsgs, - UINT msg, WPARAM wParam, LPARAM lParam) -{ - LPTRANSMSG ptr; - - if (*uNumTranMsgs + 1 >= lpTransBuf->uMsgCount) - return FALSE; - - ptr = lpTransBuf->TransMsg + *uNumTranMsgs; - ptr->message = msg; - ptr->wParam = wParam; - ptr->lParam = lParam; - (*uNumTranMsgs)++; - - return TRUE; -} - - -static BOOL IME_RemoveFromSelected(HIMC hIMC) -{ - int i; - for (i = 0; i < hSelectedCount; i++) - { - if (hSelectedFrom[i] == hIMC) - { - if (i < hSelectedCount - 1) - memmove(&hSelectedFrom[i], &hSelectedFrom[i + 1], (hSelectedCount - i - 1) * sizeof(HIMC)); - hSelectedCount--; - return TRUE; - } - } - return FALSE; -} - -static void IME_AddToSelected(HIMC hIMC) -{ - hSelectedCount++; - if (hSelectedFrom) - hSelectedFrom = HeapReAlloc(GetProcessHeap(), 0, hSelectedFrom, hSelectedCount * sizeof(HIMC)); - else - hSelectedFrom = HeapAlloc(GetProcessHeap(), 0, sizeof(HIMC)); - hSelectedFrom[hSelectedCount - 1] = hIMC; -} - -static void UpdateDataInDefaultIMEWindow(HIMC hIMC, HWND hwnd, BOOL showable) -{ - LPCOMPOSITIONSTRING compstr; - LPINPUTCONTEXT lpIMC; - - lpIMC = LockRealIMC(hIMC); - if (lpIMC == NULL) - return; - - if (lpIMC->hCompStr) - compstr = ImmLockIMCC(lpIMC->hCompStr); - else - compstr = NULL; - - if (compstr == NULL || compstr->dwCompStrLen == 0) - ShowWindow(hwnd, SW_HIDE); - else if (showable) - ShowWindow(hwnd, SW_SHOWNOACTIVATE); - - RedrawWindow(hwnd, NULL, NULL, RDW_ERASENOW | RDW_INVALIDATE); - - if (compstr != NULL) - ImmUnlockIMCC(lpIMC->hCompStr); - - UnlockRealIMC(hIMC); -} - -BOOL WINAPI ImeConfigure(HKL hKL, HWND hWnd, DWORD dwMode, LPVOID lpData) -{ - FIXME("(%p, %p, %ld, %p): stub\n", hKL, hWnd, dwMode, lpData); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; -} - -DWORD WINAPI ImeConversionList(HIMC hIMC, LPCWSTR lpSource, LPCANDIDATELIST lpCandList, - DWORD dwBufLen, UINT uFlag) - -{ - FIXME("(%p, %s, %p, %ld, %d): stub\n", hIMC, debugstr_w(lpSource), lpCandList, - dwBufLen, uFlag); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return 0; -} - -BOOL WINAPI ImeDestroy(UINT uForce) -{ - TRACE("\n"); - HeapFree(GetProcessHeap(), 0, hSelectedFrom); - hSelectedFrom = NULL; - hSelectedCount = 0; - return TRUE; -} - -LRESULT WINAPI ImeEscape(HIMC hIMC, UINT uSubFunc, LPVOID lpData) -{ - TRACE("%x %p\n", uSubFunc, lpData); - return 0; -} - -BOOL WINAPI ImeProcessKey(HIMC hIMC, UINT vKey, LPARAM lKeyData, const LPBYTE lpbKeyState) -{ - LPINPUTCONTEXT lpIMC; - BOOL inIME; - - TRACE("hIMC %p vKey 0x%04x lKeyData 0x%08Ix lpbKeyState %p\n", hIMC, vKey, lKeyData, lpbKeyState); - - switch (vKey) - { - case VK_SHIFT: - case VK_CONTROL: - case VK_CAPITAL: - case VK_MENU: - return FALSE; - } - - inIME = MACDRV_CALL(ime_using_input_method, NULL); - lpIMC = LockRealIMC(hIMC); - if (lpIMC) - { - LPIMEPRIVATE myPrivate; - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - - if (inIME && !myPrivate->bInternalState) - ImmSetOpenStatus(RealIMC(FROM_MACDRV), TRUE); - else if (!inIME && myPrivate->bInternalState) - { - ShowWindow(myPrivate->hwndDefault, SW_HIDE); - ImmDestroyIMCC(lpIMC->hCompStr); - lpIMC->hCompStr = ImeCreateBlankCompStr(); - ImmSetOpenStatus(RealIMC(FROM_MACDRV), FALSE); - } - - myPrivate->repeat = (lKeyData >> 30) & 0x1; - - myPrivate->bInternalState = inIME; - ImmUnlockIMCC(lpIMC->hPrivate); - } - UnlockRealIMC(hIMC); - - return inIME; -} - -BOOL WINAPI ImeSelect(HIMC hIMC, BOOL fSelect) -{ - LPINPUTCONTEXT lpIMC; - TRACE("%p %s\n", hIMC, fSelect ? "TRUE" : "FALSE"); - - if (hIMC == FROM_MACDRV) - { - ERR("ImeSelect should never be called from Cocoa\n"); - return FALSE; - } - - if (!hIMC) - return TRUE; - - /* not selected */ - if (!fSelect) - return IME_RemoveFromSelected(hIMC); - - IME_AddToSelected(hIMC); - - /* Initialize our structures */ - lpIMC = LockRealIMC(hIMC); - if (lpIMC != NULL) - { - LPIMEPRIVATE myPrivate; - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - if (myPrivate->bInComposition) - GenerateIMEMessage(hIMC, WM_IME_ENDCOMPOSITION, 0, 0); - if (myPrivate->bInternalState) - ImmSetOpenStatus(RealIMC(FROM_MACDRV), FALSE); - myPrivate->bInComposition = FALSE; - myPrivate->bInternalState = FALSE; - myPrivate->textfont = NULL; - myPrivate->hwndDefault = NULL; - myPrivate->repeat = 0; - ImmUnlockIMCC(lpIMC->hPrivate); - UnlockRealIMC(hIMC); - } - - return TRUE; -} - -BOOL WINAPI ImeSetActiveContext(HIMC hIMC, BOOL fFlag) -{ - static int once; - - if (!once++) - FIXME("(%p, %x): stub\n", hIMC, fFlag); - return TRUE; -} - -UINT WINAPI ImeToAsciiEx(UINT uVKey, UINT uScanCode, const LPBYTE lpbKeyState, - TRANSMSGLIST *lpdwTransKey, UINT fuState, HIMC hIMC) -{ - struct process_text_input_params params; - UINT vkey; - LPINPUTCONTEXT lpIMC; - LPIMEPRIVATE myPrivate; - HWND hwndDefault; - UINT repeat; - int done = 0; - - TRACE("uVKey 0x%04x uScanCode 0x%04x fuState %u hIMC %p\n", uVKey, uScanCode, fuState, hIMC); - - vkey = LOWORD(uVKey); - - if (vkey == VK_KANA || vkey == VK_KANJI || vkey == VK_MENU) - { - TRACE("Skipping metakey\n"); - return 0; - } - - lpIMC = LockRealIMC(hIMC); - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - if (!myPrivate->bInternalState) - { - ImmUnlockIMCC(lpIMC->hPrivate); - UnlockRealIMC(hIMC); - return 0; - } - - repeat = myPrivate->repeat; - hwndDefault = myPrivate->hwndDefault; - ImmUnlockIMCC(lpIMC->hPrivate); - UnlockRealIMC(hIMC); - - TRACE("Processing Mac 0x%04x\n", vkey); - params.vkey = uVKey; - params.scan = uScanCode; - params.repeat = repeat; - params.key_state = lpbKeyState; - params.himc = hIMC; - params.done = &done; - MACDRV_CALL(ime_process_text_input, ¶ms); - - while (!done) - MsgWaitForMultipleObjectsEx(0, NULL, INFINITE, QS_POSTMESSAGE | QS_SENDMESSAGE, 0); - - if (done < 0) - { - UINT msgs = 0; - UINT msg = (uScanCode & 0x8000) ? WM_KEYUP : WM_KEYDOWN; - - /* KeyStroke not processed by the IME - * so we need to rebuild the KeyDown message and pass it on to WINE - */ - if (!GenerateMessageToTransKey(lpdwTransKey, &msgs, msg, vkey, MAKELONG(0x0001, uScanCode))) - GenerateIMEMessage(hIMC, msg, vkey, MAKELONG(0x0001, uScanCode)); - - return msgs; - } - else - UpdateDataInDefaultIMEWindow(hIMC, hwndDefault, FALSE); - return 0; -} - -BOOL WINAPI NotifyIME(HIMC hIMC, DWORD dwAction, DWORD dwIndex, DWORD dwValue) -{ - BOOL bRet = FALSE; - LPINPUTCONTEXT lpIMC; - - TRACE("%p %li %li %li\n", hIMC, dwAction, dwIndex, dwValue); - - lpIMC = LockRealIMC(hIMC); - if (lpIMC == NULL) - return FALSE; - - switch (dwAction) - { - case NI_OPENCANDIDATE: FIXME("NI_OPENCANDIDATE\n"); break; - case NI_CLOSECANDIDATE: FIXME("NI_CLOSECANDIDATE\n"); break; - case NI_SELECTCANDIDATESTR: FIXME("NI_SELECTCANDIDATESTR\n"); break; - case NI_CHANGECANDIDATELIST: FIXME("NI_CHANGECANDIDATELIST\n"); break; - case NI_SETCANDIDATE_PAGESTART: FIXME("NI_SETCANDIDATE_PAGESTART\n"); break; - case NI_SETCANDIDATE_PAGESIZE: FIXME("NI_SETCANDIDATE_PAGESIZE\n"); break; - case NI_CONTEXTUPDATED: - switch (dwValue) - { - case IMC_SETCOMPOSITIONWINDOW: FIXME("NI_CONTEXTUPDATED: IMC_SETCOMPOSITIONWINDOW\n"); break; - case IMC_SETCONVERSIONMODE: FIXME("NI_CONTEXTUPDATED: IMC_SETCONVERSIONMODE\n"); break; - case IMC_SETSENTENCEMODE: FIXME("NI_CONTEXTUPDATED: IMC_SETSENTENCEMODE\n"); break; - case IMC_SETCANDIDATEPOS: FIXME("NI_CONTEXTUPDATED: IMC_SETCANDIDATEPOS\n"); break; - case IMC_SETCOMPOSITIONFONT: - { - LPIMEPRIVATE myPrivate; - TRACE("NI_CONTEXTUPDATED: IMC_SETCOMPOSITIONFONT\n"); - - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - if (myPrivate->textfont) - { - DeleteObject(myPrivate->textfont); - myPrivate->textfont = NULL; - } - myPrivate->textfont = CreateFontIndirectW(&lpIMC->lfFont.W); - ImmUnlockIMCC(lpIMC->hPrivate); - } - break; - case IMC_SETOPENSTATUS: - { - LPIMEPRIVATE myPrivate; - TRACE("NI_CONTEXTUPDATED: IMC_SETOPENSTATUS\n"); - - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - if (lpIMC->fOpen != myPrivate->bInternalState && myPrivate->bInComposition) - { - if(lpIMC->fOpen == FALSE) - { - GenerateIMEMessage(hIMC, WM_IME_ENDCOMPOSITION, 0, 0); - myPrivate->bInComposition = FALSE; - } - else - { - GenerateIMEMessage(hIMC, WM_IME_STARTCOMPOSITION, 0, 0); - GenerateIMEMessage(hIMC, WM_IME_COMPOSITION, 0, 0); - } - } - myPrivate->bInternalState = lpIMC->fOpen; - bRet = TRUE; - } - break; - default: FIXME("NI_CONTEXTUPDATED: Unknown\n"); break; - } - break; - case NI_COMPOSITIONSTR: - switch (dwIndex) - { - case CPS_COMPLETE: - { - HIMCC newCompStr; - DWORD cplen = 0; - LPWSTR cpstr; - LPCOMPOSITIONSTRING cs = NULL; - LPBYTE cdata = NULL; - LPIMEPRIVATE myPrivate; - - TRACE("NI_COMPOSITIONSTR: CPS_COMPLETE\n"); - - /* clear existing result */ - newCompStr = updateResultStr(lpIMC->hCompStr, NULL, 0); - - ImmDestroyIMCC(lpIMC->hCompStr); - lpIMC->hCompStr = newCompStr; - - if (lpIMC->hCompStr) - { - cdata = ImmLockIMCC(lpIMC->hCompStr); - cs = (LPCOMPOSITIONSTRING)cdata; - cplen = cs->dwCompStrLen; - cpstr = (LPWSTR)&cdata[cs->dwCompStrOffset]; - ImmUnlockIMCC(lpIMC->hCompStr); - } - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - if (cplen > 0) - { - WCHAR param = cpstr[0]; - DWORD flags = GCS_COMPSTR; - - newCompStr = updateResultStr(lpIMC->hCompStr, cpstr, cplen); - ImmDestroyIMCC(lpIMC->hCompStr); - lpIMC->hCompStr = newCompStr; - newCompStr = updateCompStr(lpIMC->hCompStr, NULL, 0, &flags); - ImmDestroyIMCC(lpIMC->hCompStr); - lpIMC->hCompStr = newCompStr; - - GenerateIMEMessage(hIMC, WM_IME_COMPOSITION, 0, flags); - - GenerateIMEMessage(hIMC, WM_IME_COMPOSITION, param, - GCS_RESULTSTR | GCS_RESULTCLAUSE); - - GenerateIMEMessage(hIMC, WM_IME_ENDCOMPOSITION, 0, 0); - } - else if (myPrivate->bInComposition) - GenerateIMEMessage(hIMC, WM_IME_ENDCOMPOSITION, 0, 0); - - - myPrivate->bInComposition = FALSE; - ImmUnlockIMCC(lpIMC->hPrivate); - - bRet = TRUE; - } - break; - case CPS_CONVERT: FIXME("NI_COMPOSITIONSTR: CPS_CONVERT\n"); break; - case CPS_REVERT: FIXME("NI_COMPOSITIONSTR: CPS_REVERT\n"); break; - case CPS_CANCEL: - { - LPIMEPRIVATE myPrivate; - - TRACE("NI_COMPOSITIONSTR: CPS_CANCEL\n"); - - MACDRV_CALL(ime_clear, NULL); - if (lpIMC->hCompStr) - ImmDestroyIMCC(lpIMC->hCompStr); - - lpIMC->hCompStr = ImeCreateBlankCompStr(); - - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - if (myPrivate->bInComposition) - { - GenerateIMEMessage(hIMC, WM_IME_ENDCOMPOSITION, 0, 0); - myPrivate->bInComposition = FALSE; - } - ImmUnlockIMCC(lpIMC->hPrivate); - bRet = TRUE; - } - break; - default: FIXME("NI_COMPOSITIONSTR: Unknown\n"); break; - } - break; - default: FIXME("Unknown Message\n"); break; - } - - UnlockRealIMC(hIMC); - return bRet; -} - -BOOL WINAPI ImeRegisterWord(LPCWSTR lpszReading, DWORD dwStyle, LPCWSTR lpszRegister) -{ - FIXME("(%s, %ld, %s): stub\n", debugstr_w(lpszReading), dwStyle, debugstr_w(lpszRegister)); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; -} - -BOOL WINAPI ImeUnregisterWord(LPCWSTR lpszReading, DWORD dwStyle, LPCWSTR lpszUnregister) -{ - FIXME("(%s, %ld, %s): stub\n", debugstr_w(lpszReading), dwStyle, debugstr_w(lpszUnregister)); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; -} - -UINT WINAPI ImeGetRegisterWordStyle(UINT nItem, LPSTYLEBUFW lpStyleBuf) -{ - FIXME("(%d, %p): stub\n", nItem, lpStyleBuf); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return 0; -} - -UINT WINAPI ImeEnumRegisterWord(REGISTERWORDENUMPROCW lpfnEnumProc, LPCWSTR lpszReading, - DWORD dwStyle, LPCWSTR lpszRegister, LPVOID lpData) -{ - FIXME("(%p, %s, %ld, %s, %p): stub\n", lpfnEnumProc, debugstr_w(lpszReading), dwStyle, - debugstr_w(lpszRegister), lpData); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return 0; -} - -static BOOL IME_SetCompositionString(void* hIMC, DWORD dwIndex, LPCVOID lpComp, DWORD dwCompLen, DWORD cursor_pos, BOOL cursor_valid) -{ - LPINPUTCONTEXT lpIMC; - DWORD flags = 0; - WCHAR wParam = 0; - LPIMEPRIVATE myPrivate; - BOOL sendMessage = TRUE; - - TRACE("(%p, %ld, %p, %ld):\n", hIMC, dwIndex, lpComp, dwCompLen); - - /* - * Explanation: - * this sets the composition string in the imm32.dll level - * of the composition buffer. - * TODO: set the Cocoa window's marked text string and tell text input context - */ - - lpIMC = LockRealIMC(hIMC); - - if (lpIMC == NULL) - return FALSE; - - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - - if (dwIndex == SCS_SETSTR) - { - HIMCC newCompStr; - - if (!myPrivate->bInComposition) - { - GenerateIMEMessage(hIMC, WM_IME_STARTCOMPOSITION, 0, 0); - myPrivate->bInComposition = TRUE; - } - - /* clear existing result */ - newCompStr = updateResultStr(lpIMC->hCompStr, NULL, 0); - ImmDestroyIMCC(lpIMC->hCompStr); - lpIMC->hCompStr = newCompStr; - - flags = GCS_COMPSTR; - - if (dwCompLen && lpComp) - { - newCompStr = updateCompStr(lpIMC->hCompStr, (LPCWSTR)lpComp, dwCompLen / sizeof(WCHAR), &flags); - ImmDestroyIMCC(lpIMC->hCompStr); - lpIMC->hCompStr = newCompStr; - - wParam = ((const WCHAR*)lpComp)[0]; - flags |= GCS_COMPCLAUSE | GCS_COMPATTR | GCS_DELTASTART; - - if (cursor_valid) - { - LPCOMPOSITIONSTRING compstr; - compstr = ImmLockIMCC(lpIMC->hCompStr); - compstr->dwCursorPos = cursor_pos; - ImmUnlockIMCC(lpIMC->hCompStr); - flags |= GCS_CURSORPOS; - } - } - else - { - NotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0); - sendMessage = FALSE; - } - - } - - if (sendMessage) { - GenerateIMEMessage(hIMC, WM_IME_COMPOSITION, wParam, flags); - ImmUnlockIMCC(lpIMC->hPrivate); - UnlockRealIMC(hIMC); - } - - return TRUE; -} - -BOOL WINAPI ImeSetCompositionString(HIMC hIMC, DWORD dwIndex, LPCVOID lpComp, DWORD dwCompLen, - LPCVOID lpRead, DWORD dwReadLen) -{ - TRACE("(%p, %ld, %p, %ld, %p, %ld):\n", hIMC, dwIndex, lpComp, dwCompLen, lpRead, dwReadLen); - - if (lpRead && dwReadLen) - FIXME("Reading string unimplemented\n"); - - return IME_SetCompositionString(hIMC, dwIndex, lpComp, dwCompLen, 0, FALSE); -} - -DWORD WINAPI ImeGetImeMenuItems(HIMC hIMC, DWORD dwFlags, DWORD dwType, LPIMEMENUITEMINFOW lpImeParentMenu, - LPIMEMENUITEMINFOW lpImeMenu, DWORD dwSize) -{ - FIXME("(%p, %lx %lx %p %p %lx): stub\n", hIMC, dwFlags, dwType, lpImeParentMenu, lpImeMenu, dwSize); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return 0; -} - -static void IME_NotifyComplete(void* hIMC) -{ - NotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); -} - -/***** - * Internal functions to help with IME window management - */ -static void PaintDefaultIMEWnd(HIMC hIMC, HWND hwnd) -{ - PAINTSTRUCT ps; - RECT rect; - HDC hdc; - LPCOMPOSITIONSTRING compstr; - LPBYTE compdata = NULL; - HMONITOR monitor; - MONITORINFO mon_info; - INT offX = 0, offY = 0; - LPINPUTCONTEXT lpIMC; - - lpIMC = LockRealIMC(hIMC); - if (lpIMC == NULL) - return; - - hdc = BeginPaint(hwnd, &ps); - - GetClientRect(hwnd, &rect); - FillRect(hdc, &rect, (HBRUSH)(COLOR_WINDOW + 1)); - - compdata = ImmLockIMCC(lpIMC->hCompStr); - compstr = (LPCOMPOSITIONSTRING)compdata; - - if (compstr->dwCompStrLen && compstr->dwCompStrOffset) - { - SIZE size; - POINT pt; - HFONT oldfont = NULL; - LPWSTR CompString; - LPIMEPRIVATE myPrivate; - - CompString = (LPWSTR)(compdata + compstr->dwCompStrOffset); - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - - if (myPrivate->textfont) - oldfont = SelectObject(hdc, myPrivate->textfont); - - ImmUnlockIMCC(lpIMC->hPrivate); - - GetTextExtentPoint32W(hdc, CompString, compstr->dwCompStrLen, &size); - pt.x = size.cx; - pt.y = size.cy; - LPtoDP(hdc, &pt, 1); - - /* - * How this works based on tests on windows: - * CFS_POINT: then we start our window at the point and grow it as large - * as it needs to be for the string. - * CFS_RECT: we still use the ptCurrentPos as a starting point and our - * window is only as large as we need for the string, but we do not - * grow such that our window exceeds the given rect. Wrapping if - * needed and possible. If our ptCurrentPos is outside of our rect - * then no window is displayed. - * CFS_FORCE_POSITION: appears to behave just like CFS_POINT - * maybe because the default MSIME does not do any IME adjusting. - */ - if (lpIMC->cfCompForm.dwStyle != CFS_DEFAULT) - { - POINT cpt = lpIMC->cfCompForm.ptCurrentPos; - ClientToScreen(lpIMC->hWnd, &cpt); - rect.left = cpt.x; - rect.top = cpt.y; - rect.right = rect.left + pt.x; - rect.bottom = rect.top + pt.y; - monitor = MonitorFromPoint(cpt, MONITOR_DEFAULTTOPRIMARY); - } - else /* CFS_DEFAULT */ - { - /* Windows places the default IME window in the bottom left */ - HWND target = lpIMC->hWnd; - if (!target) target = GetFocus(); - - GetWindowRect(target, &rect); - rect.top = rect.bottom; - rect.right = rect.left + pt.x + 20; - rect.bottom = rect.top + pt.y + 20; - offX=offY=10; - monitor = MonitorFromWindow(target, MONITOR_DEFAULTTOPRIMARY); - } - - if (lpIMC->cfCompForm.dwStyle == CFS_RECT) - { - RECT client; - client =lpIMC->cfCompForm.rcArea; - MapWindowPoints(lpIMC->hWnd, 0, (POINT *)&client, 2); - IntersectRect(&rect, &rect, &client); - /* TODO: Wrap the input if needed */ - } - - if (lpIMC->cfCompForm.dwStyle == CFS_DEFAULT) - { - /* make sure we are on the desktop */ - mon_info.cbSize = sizeof(mon_info); - GetMonitorInfoW(monitor, &mon_info); - - if (rect.bottom > mon_info.rcWork.bottom) - { - int shift = rect.bottom - mon_info.rcWork.bottom; - rect.top -= shift; - rect.bottom -= shift; - } - if (rect.left < 0) - { - rect.right -= rect.left; - rect.left = 0; - } - if (rect.right > mon_info.rcWork.right) - { - int shift = rect.right - mon_info.rcWork.right; - rect.left -= shift; - rect.right -= shift; - } - } - - SetWindowPos(hwnd, HWND_TOPMOST, rect.left, rect.top, rect.right - rect.left, - rect.bottom - rect.top, SWP_NOACTIVATE); - - TextOutW(hdc, offX, offY, CompString, compstr->dwCompStrLen); - - if (oldfont) - SelectObject(hdc, oldfont); - } - - ImmUnlockIMCC(lpIMC->hCompStr); - - EndPaint(hwnd, &ps); - UnlockRealIMC(hIMC); -} - -static void DefaultIMEComposition(HIMC hIMC, HWND hwnd, LPARAM lParam) -{ - TRACE("IME message WM_IME_COMPOSITION 0x%Ix\n", lParam); - if (!(lParam & GCS_RESULTSTR)) - UpdateDataInDefaultIMEWindow(hIMC, hwnd, TRUE); -} - -static void DefaultIMEStartComposition(HIMC hIMC, HWND hwnd) -{ - LPINPUTCONTEXT lpIMC; - - lpIMC = LockRealIMC(hIMC); - if (lpIMC == NULL) - return; - - TRACE("IME message WM_IME_STARTCOMPOSITION\n"); - lpIMC->hWnd = GetFocus(); - ShowWindow(hwnd, SW_SHOWNOACTIVATE); - UnlockRealIMC(hIMC); -} - -static LRESULT ImeHandleNotify(HIMC hIMC, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (wParam) - { - case IMN_OPENSTATUSWINDOW: - FIXME("WM_IME_NOTIFY:IMN_OPENSTATUSWINDOW\n"); - break; - case IMN_CLOSESTATUSWINDOW: - FIXME("WM_IME_NOTIFY:IMN_CLOSESTATUSWINDOW\n"); - break; - case IMN_OPENCANDIDATE: - FIXME("WM_IME_NOTIFY:IMN_OPENCANDIDATE\n"); - break; - case IMN_CHANGECANDIDATE: - FIXME("WM_IME_NOTIFY:IMN_CHANGECANDIDATE\n"); - break; - case IMN_CLOSECANDIDATE: - FIXME("WM_IME_NOTIFY:IMN_CLOSECANDIDATE\n"); - break; - case IMN_SETCONVERSIONMODE: - FIXME("WM_IME_NOTIFY:IMN_SETCONVERSIONMODE\n"); - break; - case IMN_SETSENTENCEMODE: - FIXME("WM_IME_NOTIFY:IMN_SETSENTENCEMODE\n"); - break; - case IMN_SETOPENSTATUS: - FIXME("WM_IME_NOTIFY:IMN_SETOPENSTATUS\n"); - break; - case IMN_SETCANDIDATEPOS: - FIXME("WM_IME_NOTIFY:IMN_SETCANDIDATEPOS\n"); - break; - case IMN_SETCOMPOSITIONFONT: - FIXME("WM_IME_NOTIFY:IMN_SETCOMPOSITIONFONT\n"); - break; - case IMN_SETCOMPOSITIONWINDOW: - FIXME("WM_IME_NOTIFY:IMN_SETCOMPOSITIONWINDOW\n"); - break; - case IMN_GUIDELINE: - FIXME("WM_IME_NOTIFY:IMN_GUIDELINE\n"); - break; - case IMN_SETSTATUSWINDOWPOS: - FIXME("WM_IME_NOTIFY:IMN_SETSTATUSWINDOWPOS\n"); - break; - default: - FIXME("WM_IME_NOTIFY:\n", wParam); - break; - } - return 0; -} - -static LRESULT WINAPI IME_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - LRESULT rc = 0; - HIMC hIMC; - - TRACE("Incoming Message 0x%x (0x%08Ix, 0x%08Ix)\n", msg, wParam, lParam); - - /* - * Each UI window contains the current Input Context. - * This Input Context can be obtained by calling GetWindowLong - * with IMMGWL_IMC when the UI window receives a WM_IME_xxx message. - * The UI window can refer to this Input Context and handles the - * messages. - */ - - hIMC = (HIMC)GetWindowLongPtrW(hwnd, IMMGWL_IMC); - if (!hIMC) - hIMC = RealIMC(FROM_MACDRV); - - /* if we have no hIMC there are many messages we cannot process */ - if (hIMC == NULL) - { - switch (msg) { - case WM_IME_STARTCOMPOSITION: - case WM_IME_ENDCOMPOSITION: - case WM_IME_COMPOSITION: - case WM_IME_NOTIFY: - case WM_IME_CONTROL: - case WM_IME_COMPOSITIONFULL: - case WM_IME_SELECT: - case WM_IME_CHAR: - return 0L; - default: - break; - } - } - - switch (msg) - { - case WM_CREATE: - { - LPIMEPRIVATE myPrivate; - LPINPUTCONTEXT lpIMC; - - SetWindowTextA(hwnd, "Wine Ime Active"); - - lpIMC = LockRealIMC(hIMC); - if (lpIMC) - { - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - myPrivate->hwndDefault = hwnd; - ImmUnlockIMCC(lpIMC->hPrivate); - } - UnlockRealIMC(hIMC); - - return TRUE; - } - case WM_PAINT: - PaintDefaultIMEWnd(hIMC, hwnd); - return FALSE; - - case WM_NCCREATE: - return TRUE; - - case WM_SETFOCUS: - if (wParam) - SetFocus((HWND)wParam); - else - FIXME("Received focus, should never have focus\n"); - break; - case WM_IME_COMPOSITION: - DefaultIMEComposition(hIMC, hwnd, lParam); - break; - case WM_IME_STARTCOMPOSITION: - DefaultIMEStartComposition(hIMC, hwnd); - break; - case WM_IME_ENDCOMPOSITION: - TRACE("IME message %s, 0x%Ix, 0x%Ix\n", "WM_IME_ENDCOMPOSITION", wParam, lParam); - ShowWindow(hwnd, SW_HIDE); - break; - case WM_IME_SELECT: - TRACE("IME message %s, 0x%Ix, 0x%Ix\n", "WM_IME_SELECT", wParam, lParam); - break; - case WM_IME_CONTROL: - TRACE("IME message %s, 0x%Ix, 0x%Ix\n", "WM_IME_CONTROL", wParam, lParam); - rc = 1; - break; - case WM_IME_NOTIFY: - rc = ImeHandleNotify(hIMC, hwnd, msg, wParam, lParam); - break; - default: - TRACE("Non-standard message 0x%x\n", msg); - } - /* check the MSIME messages */ - if (msg == WM_MSIME_SERVICE) - { - TRACE("IME message %s, 0x%Ix, 0x%Ix\n", "WM_MSIME_SERVICE", wParam, lParam); - rc = FALSE; - } - else if (msg == WM_MSIME_RECONVERTOPTIONS) - { - TRACE("IME message %s, 0x%Ix, 0x%Ix\n", "WM_MSIME_RECONVERTOPTIONS", wParam, lParam); - } - else if (msg == WM_MSIME_MOUSE) - { - TRACE("IME message %s, 0x%Ix, 0x%Ix\n", "WM_MSIME_MOUSE", wParam, lParam); - } - else if (msg == WM_MSIME_RECONVERTREQUEST) - { - TRACE("IME message %s, 0x%Ix, 0x%Ix\n", "WM_MSIME_RECONVERTREQUEST", wParam, lParam); - } - else if (msg == WM_MSIME_RECONVERT) - { - TRACE("IME message %s, 0x%Ix, 0x%Ix\n", "WM_MSIME_RECONVERT", wParam, lParam); - } - else if (msg == WM_MSIME_QUERYPOSITION) - { - TRACE("IME message %s, 0x%Ix, 0x%Ix\n", "WM_MSIME_QUERYPOSITION", wParam, lParam); - } - else if (msg == WM_MSIME_DOCUMENTFEED) - { - TRACE("IME message %s, 0x%Ix, 0x%Ix\n", "WM_MSIME_DOCUMENTFEED", wParam, lParam); - } - /* DefWndProc if not an IME message */ - if (!rc && !((msg >= WM_IME_STARTCOMPOSITION && msg <= WM_IME_KEYLAST) || - (msg >= WM_IME_SETCONTEXT && msg <= WM_IME_KEYUP))) - rc = DefWindowProcW(hwnd, msg, wParam, lParam); - - return rc; -} - -static BOOL WINAPI register_classes( INIT_ONCE *once, void *param, void **context ) -{ - WNDCLASSW wndClass; - ZeroMemory(&wndClass, sizeof(WNDCLASSW)); - wndClass.style = CS_GLOBALCLASS | CS_IME | CS_HREDRAW | CS_VREDRAW; - wndClass.lpfnWndProc = (WNDPROC) IME_WindowProc; - wndClass.cbClsExtra = 0; - wndClass.cbWndExtra = 2 * sizeof(LONG_PTR); - wndClass.hInstance = macdrv_module; - wndClass.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_ARROW); - wndClass.hIcon = LoadIconW(NULL, (LPWSTR)IDI_APPLICATION); - wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); - wndClass.lpszMenuName = 0; - wndClass.lpszClassName = UI_CLASS_NAME; - - RegisterClassW(&wndClass); - - WM_MSIME_SERVICE = RegisterWindowMessageA("MSIMEService"); - WM_MSIME_RECONVERTOPTIONS = RegisterWindowMessageA("MSIMEReconvertOptions"); - WM_MSIME_MOUSE = RegisterWindowMessageA("MSIMEMouseOperation"); - WM_MSIME_RECONVERTREQUEST = RegisterWindowMessageA("MSIMEReconvertRequest"); - WM_MSIME_RECONVERT = RegisterWindowMessageA("MSIMEReconvert"); - WM_MSIME_QUERYPOSITION = RegisterWindowMessageA("MSIMEQueryPosition"); - WM_MSIME_DOCUMENTFEED = RegisterWindowMessageA("MSIMEDocumentFeed"); - return TRUE; -} - -BOOL WINAPI ImeInquire(LPIMEINFO lpIMEInfo, LPWSTR lpszUIClass, DWORD flags) -{ - static INIT_ONCE init_once = INIT_ONCE_STATIC_INIT; - - TRACE("\n"); - InitOnceExecuteOnce( &init_once, register_classes, NULL, NULL ); - lpIMEInfo->dwPrivateDataSize = sizeof(IMEPRIVATE); - lpIMEInfo->fdwProperty = IME_PROP_UNICODE | IME_PROP_AT_CARET; - lpIMEInfo->fdwConversionCaps = IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE; - lpIMEInfo->fdwSentenceCaps = IME_SMODE_AUTOMATIC; - lpIMEInfo->fdwUICaps = UI_CAP_2700; - /* Tell App we cannot accept ImeSetCompositionString calls */ - /* FIXME: Can we? */ - lpIMEInfo->fdwSCSCaps = 0; - lpIMEInfo->fdwSelectCaps = SELECT_CAP_CONVERSION; - - lstrcpyW(lpszUIClass, UI_CLASS_NAME); - - return TRUE; -} - -/* Interfaces to other parts of the Mac driver */ - -/*********************************************************************** - * macdrv_ime_set_text - */ -NTSTATUS WINAPI macdrv_ime_set_text(void *arg, ULONG size) -{ - struct ime_set_text_params *params = arg; - ULONG length = (size - offsetof(struct ime_set_text_params, text)) / sizeof(WCHAR); - void *himc = param_ptr(params->data); - HWND hwnd = UlongToHandle(params->hwnd); - - if (!himc) himc = RealIMC(FROM_MACDRV); - - if (length) - { - if (himc) - IME_SetCompositionString(himc, SCS_SETSTR, params->text, length * sizeof(WCHAR), - params->cursor_pos, !params->complete); - else - { - RAWINPUT rawinput; - INPUT input; - unsigned int i; - - input.type = INPUT_KEYBOARD; - input.ki.wVk = 0; - input.ki.time = 0; - input.ki.dwExtraInfo = 0; - - for (i = 0; i < length; i++) - { - input.ki.wScan = params->text[i]; - input.ki.dwFlags = KEYEVENTF_UNICODE; - __wine_send_input(hwnd, &input, &rawinput); - - input.ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP; - __wine_send_input(hwnd, &input, &rawinput); - } - } - } - - if (params->complete) - IME_NotifyComplete(himc); - return 0; -} - -/************************************************************************** - * macdrv_ime_query_char_rect - */ -NTSTATUS WINAPI macdrv_ime_query_char_rect(void *arg, ULONG size) -{ - struct ime_query_char_rect_params *params = arg; - struct ime_query_char_rect_result *result = param_ptr(params->result); - void *himc = param_ptr(params->data); - IMECHARPOSITION charpos; - BOOL ret = FALSE; - - result->location = params->location; - result->length = params->length; - - if (!himc) himc = RealIMC(FROM_MACDRV); - - charpos.dwSize = sizeof(charpos); - charpos.dwCharPos = params->location; - if (ImmRequestMessageW(himc, IMR_QUERYCHARPOSITION, (ULONG_PTR)&charpos)) - { - int i; - - SetRect(&result->rect, charpos.pt.x, charpos.pt.y, 0, charpos.pt.y + charpos.cLineHeight); - - /* iterate over rest of length to extend rect */ - for (i = 1; i < params->length; i++) - { - charpos.dwSize = sizeof(charpos); - charpos.dwCharPos = params->location + i; - if (!ImmRequestMessageW(himc, IMR_QUERYCHARPOSITION, (ULONG_PTR)&charpos) || - charpos.pt.y != result->rect.top) - { - result->length = i; - break; - } - - result->rect.right = charpos.pt.x; - } - - ret = TRUE; - } - - if (!ret) - { - LPINPUTCONTEXT ic = ImmLockIMC(himc); - - if (ic) - { - LPIMEPRIVATE private = ImmLockIMCC(ic->hPrivate); - LPBYTE compdata = ImmLockIMCC(ic->hCompStr); - LPCOMPOSITIONSTRING compstr = (LPCOMPOSITIONSTRING)compdata; - LPWSTR str = (LPWSTR)(compdata + compstr->dwCompStrOffset); - - if (private->hwndDefault && compstr->dwCompStrOffset && - IsWindowVisible(private->hwndDefault)) - { - HDC dc = GetDC(private->hwndDefault); - HFONT oldfont = NULL; - SIZE size; - - if (private->textfont) - oldfont = SelectObject(dc, private->textfont); - - if (result->location > compstr->dwCompStrLen) - result->location = compstr->dwCompStrLen; - if (result->location + result->length > compstr->dwCompStrLen) - result->length = compstr->dwCompStrLen - result->location; - - GetTextExtentPoint32W(dc, str, result->location, &size); - charpos.rcDocument.left = size.cx; - charpos.rcDocument.top = 0; - GetTextExtentPoint32W(dc, str, result->location + result->length, &size); - charpos.rcDocument.right = size.cx; - charpos.rcDocument.bottom = size.cy; - - if (ic->cfCompForm.dwStyle == CFS_DEFAULT) - OffsetRect(&charpos.rcDocument, 10, 10); - - LPtoDP(dc, (POINT*)&charpos.rcDocument, 2); - MapWindowPoints(private->hwndDefault, 0, (POINT*)&charpos.rcDocument, 2); - result->rect = charpos.rcDocument; - ret = TRUE; - - if (oldfont) - SelectObject(dc, oldfont); - ReleaseDC(private->hwndDefault, dc); - } - - ImmUnlockIMCC(ic->hCompStr); - ImmUnlockIMCC(ic->hPrivate); - } - - ImmUnlockIMC(himc); - } - - if (!ret) - { - GUITHREADINFO gti; - gti.cbSize = sizeof(gti); - if (GetGUIThreadInfo(0, >i)) - { - MapWindowPoints(gti.hwndCaret, 0, (POINT*)>i.rcCaret, 2); - result->rect = gti.rcCaret; - ret = TRUE; - } - } - - if (ret && result->length && result->rect.left == result->rect.right) - result->rect.right++; - - return ret; -} diff --git a/dlls/winemac.drv/keyboard.c b/dlls/winemac.drv/keyboard.c index 9e415bd70e3..14f0010e37e 100644 --- a/dlls/winemac.drv/keyboard.c +++ b/dlls/winemac.drv/keyboard.c @@ -1189,18 +1189,29 @@ void macdrv_hotkey_press(const macdrv_event *event) /*********************************************************************** - * macdrv_process_text_input + * ImeProcessKey (MACDRV.@) */ -NTSTATUS macdrv_ime_process_text_input(void *arg) +UINT macdrv_ImeProcessKey(HIMC himc, UINT wparam, UINT lparam, const BYTE *key_state) { - struct process_text_input_params *params = arg; struct macdrv_thread_data *thread_data = macdrv_thread_data(); - const BYTE *key_state = params->key_state; + WORD scan = HIWORD(lparam) & 0x1ff, vkey = LOWORD(wparam); + BOOL repeat = !!(lparam >> 30), pressed = !(lparam >> 31); unsigned int flags; - int keyc; + int keyc, done = 0; + + TRACE("himc %p, scan %#x, vkey %#x, repeat %u, pressed %u\n", + himc, scan, vkey, repeat, pressed); - TRACE("vkey 0x%04x scan 0x%04x repeat %u himc %p\n", params->vkey, params->scan, - params->repeat, params->himc); + if (!macdrv_using_input_method()) return 0; + + switch (vkey) + { + case VK_SHIFT: + case VK_CONTROL: + case VK_CAPITAL: + case VK_MENU: + return 0; + } flags = thread_data->last_modifiers; if (key_state[VK_SHIFT] & 0x80) @@ -1222,19 +1233,16 @@ NTSTATUS macdrv_ime_process_text_input(void *arg) /* Find the Mac keycode corresponding to the scan code */ for (keyc = 0; keyc < ARRAY_SIZE(thread_data->keyc2vkey); keyc++) - if (thread_data->keyc2vkey[keyc] == params->vkey) break; + if (thread_data->keyc2vkey[keyc] == vkey) break; - if (keyc >= ARRAY_SIZE(thread_data->keyc2vkey)) - { - *params->done = -1; - return 0; - } + if (keyc >= ARRAY_SIZE(thread_data->keyc2vkey)) return 0; TRACE("flags 0x%08x keyc 0x%04x\n", flags, keyc); - macdrv_send_text_input_event(((params->scan & 0x8000) == 0), flags, params->repeat, keyc, - params->himc, params->done); - return 0; + macdrv_send_text_input_event(pressed, flags, repeat, keyc, himc, &done); + while (!done) NtUserMsgWaitForMultipleObjectsEx(0, NULL, INFINITE, QS_POSTMESSAGE | QS_SENDMESSAGE, 0); + + return done > 0; } diff --git a/dlls/winemac.drv/macdrv.h b/dlls/winemac.drv/macdrv.h index 281d49c1e9a..a1919596b74 100644 --- a/dlls/winemac.drv/macdrv.h +++ b/dlls/winemac.drv/macdrv.h @@ -28,6 +28,9 @@ #endif #include "macdrv_cocoa.h" + +#include "ntstatus.h" +#define WIN32_NO_STATUS #include "windef.h" #include "winbase.h" #include "ntgdi.h" @@ -130,10 +133,10 @@ extern BOOL macdrv_UpdateDisplayDevices( const struct gdi_device_manager *device BOOL force, void *param ) DECLSPEC_HIDDEN; extern BOOL macdrv_GetDeviceGammaRamp(PHYSDEV dev, LPVOID ramp) DECLSPEC_HIDDEN; extern BOOL macdrv_SetDeviceGammaRamp(PHYSDEV dev, LPVOID ramp) DECLSPEC_HIDDEN; -extern BOOL macdrv_ClipCursor(LPCRECT clip) DECLSPEC_HIDDEN; -extern BOOL macdrv_CreateDesktopWindow(HWND hwnd) DECLSPEC_HIDDEN; +extern BOOL macdrv_ClipCursor(const RECT *clip, BOOL reset) DECLSPEC_HIDDEN; extern LRESULT macdrv_DesktopWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) DECLSPEC_HIDDEN; extern void macdrv_DestroyWindow(HWND hwnd) DECLSPEC_HIDDEN; +extern void macdrv_SetDesktopWindow(HWND hwnd) DECLSPEC_HIDDEN; extern void macdrv_SetFocus(HWND hwnd) DECLSPEC_HIDDEN; extern void macdrv_SetLayeredWindowAttributes(HWND hwnd, COLORREF key, BYTE alpha, DWORD flags) DECLSPEC_HIDDEN; @@ -154,19 +157,21 @@ extern void macdrv_WindowPosChanged(HWND hwnd, HWND insert_after, UINT swp_flags const RECT *visible_rect, const RECT *valid_rects, struct window_surface *surface) DECLSPEC_HIDDEN; extern void macdrv_DestroyCursorIcon(HCURSOR cursor) DECLSPEC_HIDDEN; -extern BOOL macdrv_ClipCursor(LPCRECT clip) DECLSPEC_HIDDEN; extern BOOL macdrv_GetCursorPos(LPPOINT pos) DECLSPEC_HIDDEN; extern void macdrv_SetCapture(HWND hwnd, UINT flags) DECLSPEC_HIDDEN; -extern void macdrv_SetCursor(HCURSOR cursor) DECLSPEC_HIDDEN; +extern void macdrv_SetCursor(HWND hwnd, HCURSOR cursor) DECLSPEC_HIDDEN; extern BOOL macdrv_SetCursorPos(INT x, INT y) DECLSPEC_HIDDEN; extern BOOL macdrv_RegisterHotKey(HWND hwnd, UINT mod_flags, UINT vkey) DECLSPEC_HIDDEN; extern void macdrv_UnregisterHotKey(HWND hwnd, UINT modifiers, UINT vkey) DECLSPEC_HIDDEN; extern SHORT macdrv_VkKeyScanEx(WCHAR wChar, HKL hkl) DECLSPEC_HIDDEN; +extern UINT macdrv_ImeProcessKey(HIMC himc, UINT wparam, UINT lparam, const BYTE *state) DECLSPEC_HIDDEN; +extern UINT macdrv_ImeToAsciiEx(UINT vkey, UINT vsc, const BYTE *state, COMPOSITIONSTRING *compstr, HIMC himc) DECLSPEC_HIDDEN; extern UINT macdrv_MapVirtualKeyEx(UINT wCode, UINT wMapType, HKL hkl) DECLSPEC_HIDDEN; extern INT macdrv_ToUnicodeEx(UINT virtKey, UINT scanCode, const BYTE *lpKeyState, LPWSTR bufW, int bufW_size, UINT flags, HKL hkl) DECLSPEC_HIDDEN; extern UINT macdrv_GetKeyboardLayoutList(INT size, HKL *list) DECLSPEC_HIDDEN; extern INT macdrv_GetKeyNameText(LONG lparam, LPWSTR buffer, INT size) DECLSPEC_HIDDEN; +extern void macdrv_NotifyIMEStatus( HWND hwnd, UINT status ) DECLSPEC_HIDDEN; extern BOOL macdrv_SystemParametersInfo(UINT action, UINT int_param, void *ptr_param, UINT flags) DECLSPEC_HIDDEN; extern BOOL macdrv_ProcessEvents(DWORD mask) DECLSPEC_HIDDEN; @@ -276,7 +281,6 @@ extern NTSTATUS macdrv_dnd_get_formats(void *arg) DECLSPEC_HIDDEN; extern NTSTATUS macdrv_dnd_have_format(void *arg) DECLSPEC_HIDDEN; extern NTSTATUS macdrv_dnd_release(void *arg) DECLSPEC_HIDDEN; extern NTSTATUS macdrv_dnd_retain(void *arg) DECLSPEC_HIDDEN; -extern NTSTATUS macdrv_ime_process_text_input(void *arg) DECLSPEC_HIDDEN; extern NTSTATUS macdrv_notify_icon(void *arg) DECLSPEC_HIDDEN; extern NTSTATUS macdrv_client_func(enum macdrv_client_funcs func, const void *params, diff --git a/dlls/winemac.drv/macdrv_cocoa.h b/dlls/winemac.drv/macdrv_cocoa.h index a82dd319330..4f17d861785 100644 --- a/dlls/winemac.drv/macdrv_cocoa.h +++ b/dlls/winemac.drv/macdrv_cocoa.h @@ -378,7 +378,7 @@ typedef struct macdrv_event { unsigned long time_ms; } hotkey_press; struct { - void *data; + void *himc; CFStringRef text; /* new text or NULL if just completing existing text */ unsigned int cursor_pos; unsigned int complete; /* is completing text? */ @@ -487,7 +487,7 @@ typedef struct macdrv_query { CFTypeRef pasteboard; } drag_operation; struct { - void *data; + void *himc; CFRange range; CGRect rect; } ime_char_rect; diff --git a/dlls/winemac.drv/macdrv_dll.h b/dlls/winemac.drv/macdrv_dll.h index 3a11528eabc..1bbc7dfc7d6 100644 --- a/dlls/winemac.drv/macdrv_dll.h +++ b/dlls/winemac.drv/macdrv_dll.h @@ -31,9 +31,6 @@ extern NTSTATUS WINAPI macdrv_dnd_query_drag(void *arg, ULONG size) DECLSPEC_HID extern NTSTATUS WINAPI macdrv_dnd_query_drop(void *arg, ULONG size) DECLSPEC_HIDDEN; extern NTSTATUS WINAPI macdrv_dnd_query_exited(void *arg, ULONG size) DECLSPEC_HIDDEN; -extern NTSTATUS WINAPI macdrv_ime_set_text(void *params, ULONG size) DECLSPEC_HIDDEN; -extern NTSTATUS WINAPI macdrv_ime_query_char_rect(void *params, ULONG size) DECLSPEC_HIDDEN; - extern HMODULE macdrv_module DECLSPEC_HIDDEN; #endif /* __WINE_MACDRV_DLL_H */ diff --git a/dlls/winemac.drv/macdrv_main.c b/dlls/winemac.drv/macdrv_main.c index eeed9a4bcbe..cafd2f13347 100644 --- a/dlls/winemac.drv/macdrv_main.c +++ b/dlls/winemac.drv/macdrv_main.c @@ -605,19 +605,6 @@ NTSTATUS macdrv_client_func(enum macdrv_client_funcs id, const void *params, ULO } -static NTSTATUS macdrv_ime_clear(void *arg) -{ - macdrv_clear_ime_text(); - return 0; -} - - -static NTSTATUS macdrv_ime_using_input_method(void *arg) -{ - return macdrv_using_input_method(); -} - - static NTSTATUS macdrv_quit_result(void *arg) { struct quit_result_params *params = arg; @@ -633,9 +620,6 @@ const unixlib_entry_t __wine_unix_call_funcs[] = macdrv_dnd_have_format, macdrv_dnd_release, macdrv_dnd_retain, - macdrv_ime_clear, - macdrv_ime_process_text_input, - macdrv_ime_using_input_method, macdrv_init, macdrv_notify_icon, macdrv_quit_result, @@ -663,28 +647,6 @@ static NTSTATUS wow64_dnd_get_data(void *arg) return macdrv_dnd_get_data(¶ms); } -static NTSTATUS wow64_ime_process_text_input(void *arg) -{ - struct - { - UINT vkey; - UINT scan; - UINT repeat; - ULONG key_state; - ULONG himc; - ULONG done; - } *params32 = arg; - struct process_text_input_params params; - - params.vkey = params32->vkey; - params.scan = params32->scan; - params.repeat = params32->repeat; - params.key_state = UlongToPtr(params32->key_state); - params.himc = UlongToPtr(params32->himc); - params.done = UlongToPtr(params32->done); - return macdrv_ime_process_text_input(¶ms); -} - static NTSTATUS wow64_init(void *arg) { struct @@ -759,9 +721,6 @@ const unixlib_entry_t __wine_unix_call_wow64_funcs[] = macdrv_dnd_have_format, macdrv_dnd_release, macdrv_dnd_retain, - macdrv_ime_clear, - wow64_ime_process_text_input, - macdrv_ime_using_input_method, wow64_init, wow64_notify_icon, macdrv_quit_result, diff --git a/dlls/winemac.drv/mouse.c b/dlls/winemac.drv/mouse.c index 74c329488c4..260831c44dc 100644 --- a/dlls/winemac.drv/mouse.c +++ b/dlls/winemac.drv/mouse.c @@ -660,11 +660,13 @@ void macdrv_DestroyCursorIcon(HCURSOR cursor) * * Set the cursor clipping rectangle. */ -BOOL macdrv_ClipCursor(LPCRECT clip) +BOOL macdrv_ClipCursor(const RECT *clip, BOOL reset) { CGRect rect; - TRACE("%s\n", wine_dbgstr_rect(clip)); + TRACE("%s %u\n", wine_dbgstr_rect(clip), reset); + + if (reset) return TRUE; if (clip) { @@ -743,12 +745,12 @@ static BOOL get_icon_info(HICON handle, ICONINFOEXW *ret) /*********************************************************************** * SetCursor (MACDRV.@) */ -void macdrv_SetCursor(HCURSOR cursor) +void macdrv_SetCursor(HWND hwnd, HCURSOR cursor) { CFStringRef cursor_name = NULL; CFArrayRef cursor_frames = NULL; - TRACE("%p\n", cursor); + TRACE("%p %p\n", hwnd, cursor); if (cursor) { diff --git a/dlls/winemac.drv/unixlib.h b/dlls/winemac.drv/unixlib.h index 07f0da4a6f3..61f4f44fb75 100644 --- a/dlls/winemac.drv/unixlib.h +++ b/dlls/winemac.drv/unixlib.h @@ -26,9 +26,6 @@ enum macdrv_funcs unix_dnd_have_format, unix_dnd_release, unix_dnd_retain, - unix_ime_clear, - unix_ime_process_text_input, - unix_ime_using_input_method, unix_init, unix_notify_icon, unix_quit_result, @@ -60,17 +57,6 @@ struct dnd_have_format_params UINT format; }; -/* macdrv_ime_process_text_input params */ -struct process_text_input_params -{ - UINT vkey; - UINT scan; - UINT repeat; - const BYTE *key_state; - void *himc; - int *done; -}; - /* macdrv_init params */ struct localized_string { @@ -105,8 +91,6 @@ enum macdrv_client_funcs client_func_dnd_query_drag, client_func_dnd_query_drop, client_func_dnd_query_exited, - client_func_ime_query_char_rect, - client_func_ime_set_text, client_func_last }; @@ -164,34 +148,6 @@ struct dnd_query_exited_params UINT32 hwnd; }; -/* macdrv_ime_query_char_rect result */ -struct ime_query_char_rect_result -{ - RECT rect; - UINT32 location; - UINT32 length; -}; - -/* macdrv_ime_query_char_rect params */ -struct ime_query_char_rect_params -{ - UINT32 hwnd; - UINT32 location; - UINT64 data; - UINT64 result; /* FIXME: Use NtCallbackReturn instead */ - UINT32 length; -}; - -/* macdrv_ime_set_text params */ -struct ime_set_text_params -{ - UINT32 hwnd; - UINT32 cursor_pos; - UINT64 data; - UINT32 complete; - WCHAR text[1]; -}; - static inline void *param_ptr(UINT64 param) { return (void *)(UINT_PTR)param; diff --git a/dlls/winemac.drv/window.c b/dlls/winemac.drv/window.c index 896efb6a68e..5b053f379a5 100644 --- a/dlls/winemac.drv/window.c +++ b/dlls/winemac.drv/window.c @@ -1528,9 +1528,9 @@ static void perform_window_command(HWND hwnd, unsigned int style_any, unsigned i /********************************************************************** - * CreateDesktopWindow (MACDRV.@) + * SetDesktopWindow (MACDRV.@) */ -BOOL macdrv_CreateDesktopWindow(HWND hwnd) +void macdrv_SetDesktopWindow(HWND hwnd) { unsigned int width, height; @@ -1567,7 +1567,6 @@ BOOL macdrv_CreateDesktopWindow(HWND hwnd) } set_app_icon(); - return TRUE; } void macdrv_resize_desktop(void) diff --git a/dlls/winemac.drv/winemac.drv.spec b/dlls/winemac.drv/winemac.drv.spec index b060d1cc2a6..5f086f5c4e5 100644 --- a/dlls/winemac.drv/winemac.drv.spec +++ b/dlls/winemac.drv/winemac.drv.spec @@ -1,20 +1,2 @@ # System tray @ cdecl wine_notify_icon(long ptr) - -# IME -@ stdcall ImeConfigure(long long long ptr) -@ stdcall ImeConversionList(long wstr ptr long long) -@ stdcall ImeDestroy(long) -@ stdcall ImeEnumRegisterWord(ptr wstr long wstr ptr) -@ stdcall ImeEscape(long long ptr) -@ stdcall ImeGetImeMenuItems(long long long ptr ptr long) -@ stdcall ImeGetRegisterWordStyle(long ptr) -@ stdcall ImeInquire(ptr wstr wstr) -@ stdcall ImeProcessKey(long long long ptr) -@ stdcall ImeRegisterWord(wstr long wstr) -@ stdcall ImeSelect(long long) -@ stdcall ImeSetActiveContext(long long) -@ stdcall ImeSetCompositionString(long long ptr long ptr long) -@ stdcall ImeToAsciiEx(long long ptr ptr long long) -@ stdcall ImeUnregisterWord(wstr long wstr) -@ stdcall NotifyIME(long long long long) diff --git a/dlls/winevulkan/make_vulkan b/dlls/winevulkan/make_vulkan index 73681dcaff1..2fda357e454 100755 --- a/dlls/winevulkan/make_vulkan +++ b/dlls/winevulkan/make_vulkan @@ -297,6 +297,9 @@ FUNCTION_OVERRIDES = { # VK_KHR_synchronization2 "vkQueueSubmit2KHR" : {"dispatch": True, "driver": False, "thunk" : ThunkType.PRIVATE, "extra_param" : "pSubmits"}, + # VK_NV_low_latency2 + "vkSetLatencySleepModeNV" : {"dispatch": True, "driver": False, "thunk" : ThunkType.NONE}, + # Custom functions "wine_vkAcquireKeyedMutex" : {"dispatch": True, "driver": False, "thunk" : ThunkType.PRIVATE}, "wine_vkReleaseKeyedMutex" : {"dispatch": True, "driver": False, "thunk" : ThunkType.PRIVATE}, diff --git a/dlls/winevulkan/vk.xml b/dlls/winevulkan/vk.xml index 898bd9f967e..a696de6f012 100644 --- a/dlls/winevulkan/vk.xml +++ b/dlls/winevulkan/vk.xml @@ -175,11 +175,11 @@ branch of the member gitlab server. #define VKSC_API_VERSION_1_0 VK_MAKE_API_VERSION(VKSC_API_VARIANT, 1, 0, 0)// Patch version should always be set to 0 // Version of this file -#define VK_HEADER_VERSION 260 +#define VK_HEADER_VERSION 267 // Complete version of this file #define VK_HEADER_VERSION_COMPLETE VK_MAKE_API_VERSION(0, 1, 3, VK_HEADER_VERSION) // Version of this file -#define VK_HEADER_VERSION 12 +#define VK_HEADER_VERSION 13 // Complete version of this file #define VK_HEADER_VERSION_COMPLETE VK_MAKE_API_VERSION(VKSC_API_VARIANT, 1, 0, VK_HEADER_VERSION) @@ -190,7 +190,7 @@ branch of the member gitlab server. #ifndef VK_USE_64_BIT_PTR_DEFINES - #if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) + #if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) || (defined(__riscv) && __riscv_xlen == 64) #define VK_USE_64_BIT_PTR_DEFINES 1 #else #define VK_USE_64_BIT_PTR_DEFINES 0 @@ -478,6 +478,7 @@ typedef void* MTLSharedEvent_id; typedef VkFlags VkOpticalFlowUsageFlagsNV; typedef VkFlags VkOpticalFlowSessionCreateFlagsNV; typedef VkFlags VkOpticalFlowExecuteFlagsNV; + typedef VkFlags VkFrameBoundaryFlagsEXT; typedef VkFlags VkPresentScalingFlagsEXT; typedef VkFlags VkPresentGravityFlagsEXT; typedef VkFlags VkShaderCreateFlagsEXT; @@ -775,6 +776,7 @@ typedef void* MTLSharedEvent_id; + @@ -787,6 +789,9 @@ typedef void* MTLSharedEvent_id; + + + WSI extensions @@ -860,6 +865,8 @@ typedef void* MTLSharedEvent_id; + + Enumerated types in the header, but not used by the API @@ -1197,7 +1204,7 @@ typedef void* MTLSharedEvent_id; const void* pNext VkBufferCreateFlags flagsBuffer creation flags VkDeviceSize sizeSpecified in bytes - VkBufferUsageFlags usageBuffer usage flags + VkBufferUsageFlags usageBuffer usage flags VkSharingMode sharingMode uint32_t queueFamilyIndexCount const uint32_t* pQueueFamilyIndices @@ -4196,7 +4203,7 @@ typedef void* MTLSharedEvent_id; const void* pNext VkBool32 conditionalRenderingEnableWhether this secondary command buffer may be executed during an active conditional rendering - + VkStructureType sType void* pNext uint64_t externalFormat @@ -6972,10 +6979,10 @@ typedef void* MTLSharedEvent_id; VkStructureType sType - const void* pNext - uint32_t stdSPSCount + const void* pNext + uint32_t stdSPSCount const StdVideoH264SequenceParameterSet* pStdSPSs - uint32_t stdPPSCount + uint32_t stdPPSCount const StdVideoH264PictureParameterSet* pStdPPSsList of Picture Parameters associated with the spsStd, above @@ -7026,7 +7033,7 @@ typedef void* MTLSharedEvent_id; VkStructureType sType const void* pNext - VkVideoEncodeH264RateControlFlagsEXT flags + VkVideoEncodeH264RateControlFlagsEXT flags uint32_t gopFrameCount uint32_t idrPeriod uint32_t consecutiveBFrameCount @@ -7110,11 +7117,11 @@ typedef void* MTLSharedEvent_id; VkStructureType sType const void* pNext - uint32_t stdVPSCount + uint32_t stdVPSCount const StdVideoH265VideoParameterSet* pStdVPSs - uint32_t stdSPSCount + uint32_t stdSPSCount const StdVideoH265SequenceParameterSet* pStdSPSs - uint32_t stdPPSCount + uint32_t stdPPSCount const StdVideoH265PictureParameterSet* pStdPPSsList of Picture Parameters associated with the spsStd, above @@ -7158,7 +7165,7 @@ typedef void* MTLSharedEvent_id; VkStructureType sType const void* pNext - VkVideoEncodeH265RateControlFlagsEXT flags + VkVideoEncodeH265RateControlFlagsEXT flags uint32_t gopFrameCount uint32_t idrPeriod uint32_t consecutiveBFrameCount @@ -7766,6 +7773,18 @@ typedef void* MTLSharedEvent_id; size_t descriptorOffset uint32_t descriptorSize + + VkStructureType sType + void* pNext + VkBool32 nestedCommandBuffer + VkBool32 nestedCommandBufferRendering + VkBool32 nestedCommandBufferSimultaneousUse + + + VkStructureType sType + void* pNext + uint32_t maxCommandBufferNestingLevel + VkStructureType sType void* pNext @@ -8087,7 +8106,7 @@ typedef void* MTLSharedEvent_id; VkPipelineRobustnessImageBehaviorEXT images - VkStructureType sType + VkStructureType sType void* pNext VkPipelineRobustnessBufferBehaviorEXT defaultRobustnessStorageBuffers VkPipelineRobustnessBufferBehaviorEXT defaultRobustnessUniformBuffers @@ -8301,6 +8320,24 @@ typedef void* MTLSharedEvent_id; void* pNext VkBool32 shaderCoreBuiltins + + VkStructureType sType + const void* pNext + VkFrameBoundaryFlagsEXT flags + uint64_t frameID + uint32_t imageCount + const VkImage* pImages + uint32_t bufferCount + const VkBuffer* pBuffers + uint64_t tagName + size_t tagSize + const void* pTag + + + VkStructureType sType + void* pNext + VkBool32 frameBoundary + VkStructureType sType void* pNext @@ -8381,6 +8418,18 @@ typedef void* MTLSharedEvent_id; void* pNext VkRayTracingInvocationReorderModeNV rayTracingInvocationReorderReorderingHint + + VkStructureType sType + void* pNext + VkBool32 extendedSparseAddressSpace + + + VkStructureType sType + void* pNext + VkDeviceSize extendedSparseAddressSpaceSizeTotal address space available for extended sparse allocations (bytes) + VkImageUsageFlags extendedSparseImageUsageFlagsBitfield of which image usages are supported for extended sparse allocations + VkBufferUsageFlags extendedSparseBufferUsageFlagsBitfield of which buffer usages are supported for extended sparse allocations + VkStructureType sType void* pNext @@ -8475,18 +8524,18 @@ typedef void* MTLSharedEvent_id; const VkSpecializationInfo* pSpecializationInfo - VkStructureType sType - void* pNext - VkBool32 shaderTileImageColorReadAccess - VkBool32 shaderTileImageDepthReadAccess - VkBool32 shaderTileImageStencilReadAccess + VkStructureType sType + void* pNext + VkBool32 shaderTileImageColorReadAccess + VkBool32 shaderTileImageDepthReadAccess + VkBool32 shaderTileImageStencilReadAccess - VkStructureType sType - void* pNext - VkBool32 shaderTileImageCoherentReadAccelerated - VkBool32 shaderTileImageReadSampleFromPixelRateInvocation - VkBool32 shaderTileImageReadFromHelperInvocation + VkStructureType sType + void* pNext + VkBool32 shaderTileImageCoherentReadAccelerated + VkBool32 shaderTileImageReadSampleFromPixelRateInvocation + VkBool32 shaderTileImageReadFromHelperInvocation VkStructureType sType @@ -8593,6 +8642,143 @@ typedef void* MTLSharedEvent_id; VkDeviceOrHostAddressConstAMDX infos uint64_t stride + + VkStructureType sType + void* pNext + VkBool32 cubicRangeClamp + + + VkStructureType sType + void* pNext + VkBool32 ycbcrDegamma + + + VkStructureType sType + void* pNext + VkBool32 enableYDegamma + VkBool32 enableCbCrDegamma + + + VkStructureType sType + void* pNext + VkBool32 selectableCubicWeights + + + VkStructureType sType + const void* pNext + VkCubicFilterWeightsQCOM cubicWeights + + + VkStructureType sType + const void* pNext + VkCubicFilterWeightsQCOM cubicWeights + + + VkStructureType sType + void* pNext + VkBool32 textureBlockMatch2 + + + VkStructureType sType + void* pNext + VkExtent2D maxBlockMatchWindow + + + VkStructureType sType + const void* pNext + VkExtent2D windowExtent + VkBlockMatchWindowCompareModeQCOM windowCompareMode + + + VkStructureType sType + void* pNext + VkBool32 descriptorPoolOverallocation + + + VkStructureType sType + void* pNext + VkLayeredDriverUnderlyingApiMSFT underlyingAPI + + + VkStructureType sType + void* pNext + VkBool32 externalFormatResolve + + + VkStructureType sType + void* pNext + VkBool32 nullColorAttachmentWithExternalFormatResolve + VkChromaLocation externalFormatResolveChromaOffsetX + VkChromaLocation externalFormatResolveChromaOffsetY + + + VkStructureType sType + void* pNext + VkFormat colorAttachmentFormat + + + VkStructureType sType + const void* pNext + VkBool32 lowLatencyMode + VkBool32 lowLatencyBoost + uint32_t minimumIntervalUs + + + VkStructureType sType + const void* pNext + VkSemaphore signalSemaphore + uint64_t value + + + VkStructureType sType + const void* pNext + uint64_t presentID + VkLatencyMarkerNV marker + + + VkStructureType sType + const void* pNext + VkLatencyTimingsFrameReportNV* pTimings + + + VkStructureType sType + const void* pNext + uint64_t presentID + uint64_t inputSampleTimeUs + uint64_t simStartTimeUs + uint64_t simEndTimeUs + uint64_t renderSubmitStartTimeUs + uint64_t renderSubmitEndTimeUs + uint64_t presentStartTimeUs + uint64_t presentEndTimeUs + uint64_t driverStartTimeUs + uint64_t driverEndTimeUs + uint64_t osRenderQueueStartTimeUs + uint64_t osRenderQueueEndTimeUs + uint64_t gpuRenderStartTimeUs + uint64_t gpuRenderEndTimeUs + + + VkStructureType sType + const void* pNext + VkOutOfBandQueueTypeNV queueType + + + VkStructureType sType + const void* pNext + uint64_t presentID + + + VkStructureType sType + const void* pNext + VkBool32 latencyModeEnable + + + VkStructureType sType + const void* pNext + uint32_t presentModeCount + VkPresentModeKHR* pPresentModes + @@ -9806,6 +9992,7 @@ typedef void* MTLSharedEvent_id; + @@ -10236,6 +10423,9 @@ typedef void* MTLSharedEvent_id; + + + @@ -10315,6 +10505,7 @@ typedef void* MTLSharedEvent_id; + @@ -10357,6 +10548,8 @@ typedef void* MTLSharedEvent_id; + + @@ -10473,6 +10666,8 @@ typedef void* MTLSharedEvent_id; + + @@ -10667,6 +10862,38 @@ typedef void* MTLSharedEvent_id; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -11754,7 +11981,7 @@ typedef void* MTLSharedEvent_id; void vkCmdEndRenderPass VkCommandBuffer commandBuffer - + void vkCmdExecuteCommands VkCommandBuffer commandBuffer uint32_t commandBufferCount @@ -14497,11 +14724,11 @@ typedef void* MTLSharedEvent_id; VkShaderModuleIdentifierEXT* pIdentifier - void vkGetImageSubresourceLayout2KHR - VkDevice device - VkImage image - const VkImageSubresource2KHR* pSubresource - VkSubresourceLayout2KHR* pLayout + void vkGetImageSubresourceLayout2KHR + VkDevice device + VkImage image + const VkImageSubresource2KHR* pSubresource + VkSubresourceLayout2KHR* pLayout @@ -14680,6 +14907,36 @@ typedef void* MTLSharedEvent_id; VkDeviceAddress scratch VkDeviceAddress countInfo + + VkResult vkSetLatencySleepModeNV + VkDevice device + VkSwapchainKHR swapchain + const VkLatencySleepModeInfoNV* pSleepModeInfo + + + VkResult vkLatencySleepNV + VkDevice device + VkSwapchainKHR swapchain + const VkLatencySleepInfoNV* pSleepInfo + + + void vkSetLatencyMarkerNV + VkDevice device + VkSwapchainKHR swapchain + const VkSetLatencyMarkerInfoNV* pLatencyMarkerInfo + + + void vkGetLatencyTimingsNV + VkDevice device + VkSwapchainKHR swapchain + uint32_t* pTimingCount + VkGetLatencyMarkerInfoNV* pLatencyMarkerInfo + + + void vkQueueNotifyOutOfBandNV + VkQueue queue + const VkOutOfBandQueueTypeInfoNV* pQueueTypeInfo + @@ -16695,7 +16952,7 @@ typedef void* MTLSharedEvent_id; - + @@ -16739,7 +16996,7 @@ typedef void* MTLSharedEvent_id; - + @@ -16922,7 +17179,7 @@ typedef void* MTLSharedEvent_id; - + @@ -18821,16 +19078,10 @@ typedef void* MTLSharedEvent_id; - + - - - - - - @@ -19043,7 +19294,6 @@ typedef void* MTLSharedEvent_id; - @@ -19920,7 +20170,7 @@ typedef void* MTLSharedEvent_id; - + @@ -20087,13 +20337,13 @@ typedef void* MTLSharedEvent_id; - + - + @@ -20323,7 +20573,7 @@ typedef void* MTLSharedEvent_id; - + @@ -20355,6 +20605,7 @@ typedef void* MTLSharedEvent_id; + @@ -20417,13 +20668,13 @@ typedef void* MTLSharedEvent_id; - + - + @@ -20470,7 +20721,12 @@ typedef void* MTLSharedEvent_id; - + + + + + + @@ -21378,10 +21634,16 @@ typedef void* MTLSharedEvent_id; - + - - + + + + + + + + @@ -21658,9 +21920,6 @@ typedef void* MTLSharedEvent_id; - - - @@ -21802,13 +22061,13 @@ typedef void* MTLSharedEvent_id; - + - + @@ -22098,13 +22357,19 @@ typedef void* MTLSharedEvent_id; - + - - + + + + + + + + - + @@ -22259,7 +22524,7 @@ typedef void* MTLSharedEvent_id; - + @@ -22382,11 +22647,19 @@ typedef void* MTLSharedEvent_id; - + - - - + + + + + + + + + + + @@ -22395,7 +22668,7 @@ typedef void* MTLSharedEvent_id; - + @@ -22433,7 +22706,7 @@ typedef void* MTLSharedEvent_id; - + @@ -22444,8 +22717,8 @@ typedef void* MTLSharedEvent_id; - - + + @@ -22460,43 +22733,46 @@ typedef void* MTLSharedEvent_id; - + - + - + - + - - + + - - + + + + + - + - + - + - - + + @@ -22514,13 +22790,13 @@ typedef void* MTLSharedEvent_id; - - - + + + - - + + @@ -22703,8 +22979,9 @@ typedef void* MTLSharedEvent_id; - - + + + @@ -22808,10 +23085,14 @@ typedef void* MTLSharedEvent_id; - + - - + + + + + + @@ -22846,7 +23127,7 @@ typedef void* MTLSharedEvent_id; - + @@ -22902,13 +23183,38 @@ typedef void* MTLSharedEvent_id; - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -22923,7 +23229,7 @@ typedef void* MTLSharedEvent_id; - + @@ -22934,7 +23240,7 @@ typedef void* MTLSharedEvent_id; - + @@ -23001,31 +23307,52 @@ typedef void* MTLSharedEvent_id; - + - - + + + + + + + + + - + - - + + + + + + + + + - + - - + + + + + + - + - - + + + + + - + @@ -23089,10 +23416,13 @@ typedef void* MTLSharedEvent_id; - + - - + + + + + @@ -23173,12 +23503,40 @@ typedef void* MTLSharedEvent_id; - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -24774,6 +25132,9 @@ typedef void* MTLSharedEvent_id; + + + @@ -24792,6 +25153,9 @@ typedef void* MTLSharedEvent_id; + + + @@ -25251,6 +25615,9 @@ typedef void* MTLSharedEvent_id; + + + @@ -25334,7 +25701,7 @@ typedef void* MTLSharedEvent_id; - + @@ -25430,7 +25797,7 @@ typedef void* MTLSharedEvent_id; - + diff --git a/dlls/winevulkan/vulkan.c b/dlls/winevulkan/vulkan.c index a20ec837e2a..7c81a12d595 100644 --- a/dlls/winevulkan/vulkan.c +++ b/dlls/winevulkan/vulkan.c @@ -896,6 +896,24 @@ static void wine_vk_instance_free(struct wine_instance *instance) free(instance); } +VkResult wine_vkSetLatencySleepModeNV(VkDevice device, VkSwapchainKHR swapchain, const VkLatencySleepModeInfoNV *pSleepModeInfo) +{ + VkLatencySleepModeInfoNV sleep_mode_info_host; + + struct wine_device* wine_device = wine_device_from_handle(device); + struct wine_swapchain* wine_swapchain = wine_swapchain_from_handle(swapchain); + + wine_device->low_latency_enabled = pSleepModeInfo->lowLatencyMode; + + sleep_mode_info_host.sType = VK_STRUCTURE_TYPE_LATENCY_SLEEP_MODE_INFO_NV; + sleep_mode_info_host.pNext = NULL; + sleep_mode_info_host.lowLatencyMode = pSleepModeInfo->lowLatencyMode; + sleep_mode_info_host.lowLatencyBoost = pSleepModeInfo->lowLatencyBoost; + sleep_mode_info_host.minimumIntervalUs = pSleepModeInfo->minimumIntervalUs; + + return wine_device->funcs.p_vkSetLatencySleepModeNV(wine_device->device, wine_swapchain->swapchain, &sleep_mode_info_host); +} + VkResult wine_vkAllocateCommandBuffers(VkDevice handle, const VkCommandBufferAllocateInfo *allocate_info, VkCommandBuffer *buffers ) { @@ -3921,6 +3939,8 @@ VkResult fshack_vk_queue_present(VkQueue queue_handle, const VkPresentInfoKHR *p if (n_hacks > 0) { VkPipelineStageFlags waitStage, *waitStages, *waitStages_arr = NULL; + VkLatencySubmissionPresentIdNV latencySubmitInfo; + VkPresentIdKHR *present_id; if (pPresentInfo->waitSemaphoreCount > 1) { @@ -3944,6 +3964,15 @@ VkResult fshack_vk_queue_present(VkQueue queue_handle, const VkPresentInfoKHR *p submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = &blit_sema; + if ((queue->device->low_latency_enabled) && + (present_id = wine_vk_find_struct(&our_presentInfo, PRESENT_ID_KHR))) + { + latencySubmitInfo.sType = VK_STRUCTURE_TYPE_LATENCY_SUBMISSION_PRESENT_ID_NV; + latencySubmitInfo.pNext = NULL; + latencySubmitInfo.presentID = *present_id->pPresentIds; + submitInfo.pNext = &latencySubmitInfo; + } + res = queue->device->funcs.p_vkQueueSubmit(queue->queue, 1, &submitInfo, VK_NULL_HANDLE); if (res != VK_SUCCESS) ERR("vkQueueSubmit: %d\n", res); diff --git a/dlls/winevulkan/vulkan_private.h b/dlls/winevulkan/vulkan_private.h index c9548e944d2..47b2a9a5903 100644 --- a/dlls/winevulkan/vulkan_private.h +++ b/dlls/winevulkan/vulkan_private.h @@ -99,6 +99,7 @@ struct wine_device uint64_t sem_poll_update_value; /* set to sem_poll_update.value by signaller thread once update is processed. */ unsigned int allocated_fence_ops_count; BOOL keyed_mutexes_enabled; + BOOL low_latency_enabled; }; static inline struct wine_device *wine_device_from_handle(VkDevice handle) diff --git a/dlls/winex11.drv/Makefile.in b/dlls/winex11.drv/Makefile.in index c9e76002b2c..3629ca17c67 100644 --- a/dlls/winex11.drv/Makefile.in +++ b/dlls/winex11.drv/Makefile.in @@ -15,7 +15,6 @@ C_SRCS = \ event.c \ fs.c \ graphics.c \ - ime.c \ init.c \ keyboard.c \ mouse.c \ diff --git a/dlls/winex11.drv/desktop.c b/dlls/winex11.drv/desktop.c index f7f49f6ca4e..9bfdf24dc01 100644 --- a/dlls/winex11.drv/desktop.c +++ b/dlls/winex11.drv/desktop.c @@ -227,8 +227,8 @@ static LONG X11DRV_desktop_set_current_mode( ULONG_PTR id, const DEVMODEW *mode static void query_desktop_work_area( RECT *rc_work ) { - static const WCHAR trayW[] = {'S','h','e','l','l','_','T','r','a','y','W','n','d'}; - UNICODE_STRING str = { sizeof(trayW), sizeof(trayW), (WCHAR *)trayW }; + static const WCHAR trayW[] = {'S','h','e','l','l','_','T','r','a','y','W','n','d',0}; + UNICODE_STRING str = RTL_CONSTANT_STRING( trayW ); RECT rect; HWND hwnd = NtUserFindWindowEx( 0, 0, &str, NULL, 0 ); @@ -348,36 +348,26 @@ void X11DRV_init_desktop( Window win, unsigned int width, unsigned int height ) desktop_handler.free_monitors = X11DRV_desktop_free_monitors; desktop_handler.register_event_handlers = NULL; TRACE("Display device functions are now handled by: Virtual Desktop\n"); - X11DRV_DisplayDevices_Init( TRUE ); } /*********************************************************************** - * x11drv_create_desktop + * X11DRV_CreateDesktop * * Create the X11 desktop window for the desktop mode. */ -NTSTATUS x11drv_create_desktop( void *arg ) +BOOL X11DRV_CreateDesktop( const WCHAR *name, UINT width, UINT height ) { - static const WCHAR rootW[] = {'r','o','o','t',0}; - const struct create_desktop_params *params = arg; XSetWindowAttributes win_attr; Window win; Display *display = thread_init_display(); - WCHAR name[MAX_PATH]; - if (!NtUserGetObjectInformation( NtUserGetThreadDesktop( GetCurrentThreadId() ), - UOI_NAME, name, sizeof(name), NULL )) - name[0] = 0; - - TRACE( "%s %ux%u\n", debugstr_w(name), params->width, params->height ); - - /* magic: desktop "root" means use the root window */ - if (!wcsicmp( name, rootW )) return FALSE; + TRACE( "%s %ux%u\n", debugstr_w(name), width, height ); /* Create window */ - win_attr.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | EnterWindowMask | - PointerMotionMask | ButtonPressMask | ButtonReleaseMask | FocusChangeMask; + win_attr.event_mask = ExposureMask | FocusChangeMask | EnterWindowMask | + PointerMotionMask | ButtonPressMask | ButtonReleaseMask; + if (!input_thread_hack) win_attr.event_mask |= KeyPressMask | KeyReleaseMask; win_attr.cursor = XCreateFontCursor( display, XC_top_left_arrow ); if (default_visual.visual != DefaultVisual( display, DefaultScreen(display) )) @@ -387,21 +377,13 @@ NTSTATUS x11drv_create_desktop( void *arg ) win_attr.colormap = None; win = XCreateWindow( display, DefaultRootWindow(display), - 0, 0, params->width, params->height, 0, default_visual.depth, InputOutput, + 0, 0, width, height, 0, default_visual.depth, InputOutput, default_visual.visual, CWEventMask | CWCursor | CWColormap, &win_attr ); if (!win) return FALSE; X11DRV_XInput2_Enable( display, win, win_attr.event_mask ); - if (!create_desktop_win_data( win )) return FALSE; - - X11DRV_init_desktop( win, params->width, params->height ); - if (is_desktop_fullscreen()) - { - TRACE("setting desktop to fullscreen\n"); - XChangeProperty( display, win, x11drv_atom(_NET_WM_STATE), XA_ATOM, 32, - PropModeReplace, (unsigned char*)&x11drv_atom(_NET_WM_STATE_FULLSCREEN), - 1); - } XFlush( display ); + + X11DRV_init_desktop( win, width, height ); return TRUE; } @@ -466,14 +448,10 @@ void X11DRV_resize_desktop(void) NtUserSetWindowPos( hwnd, 0, virtual_rect.left, virtual_rect.top, virtual_rect.right - virtual_rect.left, virtual_rect.bottom - virtual_rect.top, SWP_NOZORDER | SWP_NOACTIVATE | SWP_DEFERERASE ); - ungrab_clipping_window(); /* HACK: always send the desktop resize notification, to eventually update fshack on windows */ send_message_timeout( HWND_BROADCAST, WM_X11DRV_DESKTOP_RESIZED, old_virtual_rect.left, old_virtual_rect.top, SMTO_ABORTIFHUNG, 2000, FALSE ); - /* forward clip_fullscreen_window request to the foreground window */ - send_notify_message( NtUserGetForegroundWindow(), WM_X11DRV_CLIP_CURSOR_REQUEST, TRUE, TRUE ); - old_virtual_rect = virtual_rect; } diff --git a/dlls/winex11.drv/dllmain.c b/dlls/winex11.drv/dllmain.c index 500a4a6bc44..bed88671131 100644 --- a/dlls/winex11.drv/dllmain.c +++ b/dlls/winex11.drv/dllmain.c @@ -22,6 +22,8 @@ #include "wine/debug.h" +WINE_DEFAULT_DEBUG_CHANNEL(x11drv); + HMODULE x11drv_module = 0; @@ -30,11 +32,6 @@ static const callback_func callback_funcs[] = { x11drv_dnd_drop_event, x11drv_dnd_leave_event, - x11drv_ime_get_cursor_pos, - x11drv_ime_set_composition_status, - x11drv_ime_set_cursor_pos, - x11drv_ime_set_open_status, - x11drv_ime_update_association, }; C_ASSERT( ARRAYSIZE(callback_funcs) == client_funcs_count ); @@ -52,16 +49,35 @@ static const kernel_callback kernel_callbacks[] = x11drv_dnd_enter_event, x11drv_dnd_position_event, x11drv_dnd_post_drop, - x11drv_ime_set_composition_string, - x11drv_ime_set_result, x11drv_systray_change_owner, }; C_ASSERT( NtUserDriverCallbackFirst + ARRAYSIZE(kernel_callbacks) == client_func_last ); +static DWORD CALLBACK input_thread( void *arg ) +{ + NTSTATUS status; + + SetThreadDescription( GetCurrentThread(), L"wine_x11drv_input" ); + + TRACE("\n"); + + /* wait for explorer startup sequence to complete */ + SendMessageW( GetDesktopWindow(), WM_NULL, 0, 0 ); + + for (;;) + { + status = X11DRV_CALL( input_thread, NULL ); + WARN( "input_thread returned %#lx\n", status ); + } + + return 0; +} + BOOL WINAPI DllMain( HINSTANCE instance, DWORD reason, void *reserved ) { + static HANDLE thread; void **callback_table; struct init_params params = { @@ -69,6 +85,13 @@ BOOL WINAPI DllMain( HINSTANCE instance, DWORD reason, void *reserved ) &show_systray, }; + if (reason == DLL_PROCESS_DETACH && !reserved && thread) + { + TerminateThread( thread, -1 ); + WaitForSingleObject( thread, INFINITE ); + CloseHandle( thread ); + } + if (reason != DLL_PROCESS_ATTACH) return TRUE; DisableThreadLibraryCalls( instance ); @@ -76,21 +99,17 @@ BOOL WINAPI DllMain( HINSTANCE instance, DWORD reason, void *reserved ) if (__wine_init_unix_call()) return FALSE; if (X11DRV_CALL( init, ¶ms )) return FALSE; + if (params.input_thread_hack) + { + thread = CreateThread( NULL, 0, input_thread, NULL, 0, NULL ); + if (!thread) ERR( "Failed to create input monitor thread, error %lu\n", GetLastError() ); + } + callback_table = NtCurrentTeb()->Peb->KernelCallbackTable; memcpy( callback_table + NtUserDriverCallbackFirst, kernel_callbacks, sizeof(kernel_callbacks) ); return TRUE; } - -/*********************************************************************** - * wine_create_desktop (winex11.@) - */ -BOOL CDECL wine_create_desktop( UINT width, UINT height ) -{ - struct create_desktop_params params = { .width = width, .height = height }; - return X11DRV_CALL( create_desktop, ¶ms ); -} - /*********************************************************************** * AttachEventQueueToTablet (winex11.@) */ diff --git a/dlls/winex11.drv/event.c b/dlls/winex11.drv/event.c index 760177d0622..a7d4f4a2436 100644 --- a/dlls/winex11.drv/event.c +++ b/dlls/winex11.drv/event.c @@ -49,8 +49,6 @@ WINE_DEFAULT_DEBUG_CHANNEL(event); WINE_DECLARE_DEBUG_CHANNEL(xdnd); -extern BOOL ximInComposeMode; - #define DndNotDnd -1 /* OffiX drag&drop */ #define DndUnknown 0 #define DndRawData 1 @@ -147,8 +145,74 @@ static const char * event_names[MAX_EVENT_HANDLERS] = "SelectionNotify", "ColormapNotify", "ClientMessage", "MappingNotify", "GenericEvent" }; +/* is someone else grabbing the keyboard, for example the WM, when manipulating the window */ +BOOL keyboard_grabbed = FALSE; + int xinput2_opcode = 0; +static pthread_mutex_t input_cs = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t input_cond = PTHREAD_COND_INITIALIZER; +static Display *input_display; + +/* wait for the input thread to startup and return the input display */ +static Display *x11drv_input_display(void) +{ + if (input_thread_hack && !input_display) + { + pthread_mutex_lock( &input_cs ); + while (!input_display) pthread_cond_wait( &input_cond, &input_cs ); + pthread_mutex_unlock( &input_cs ); + } + + return input_display; +} + +/* set the input display and notify waiters */ +static void x11drv_set_input_display( Display *display ) +{ + if (input_display) return; + + pthread_mutex_lock( &input_cs ); + input_display = display; + pthread_mutex_unlock( &input_cs ); + pthread_cond_broadcast( &input_cond ); +} + +/* add a window to the windows we get input for */ +void x11drv_input_add_window( HWND hwnd, Window window ) +{ + long mask = KeyPressMask | KeyReleaseMask | KeymapStateMask; + Display *display = x11drv_input_display(); + + if (!input_thread_hack) return; + + TRACE( "display %p, window %p/%lx\n", display, hwnd, window ); + + pthread_mutex_lock( &input_cs ); + XSaveContext( display, window, winContext, (char *)hwnd ); + pthread_mutex_unlock( &input_cs ); + + XSelectInput( display, window, mask ); + XFlush( display ); +} + +/* remove a window from the windows we get input for */ +void x11drv_input_remove_window( Window window ) +{ + Display *display = x11drv_input_display(); + + if (!input_thread_hack) return; + + TRACE( "display %p, window %lx\n", display, window ); + + XSelectInput( display, window, 0 ); + XFlush( display ); + + pthread_mutex_lock( &input_cs ); + XDeleteContext( display, window, winContext ); + pthread_mutex_unlock( &input_cs ); +} + /* return the name of an X event */ static const char *dbgstr_event( int type ) { @@ -273,6 +337,27 @@ static Bool filter_event( Display *display, XEvent *event, char *arg ) } } +static void wait_grab_pointer( Display *display ) +{ + RECT rect; + + /* release cursor grab held by any Wine process */ + NtUserGetClipCursor( &rect ); + NtUserClipCursor( NULL ); + + while (XGrabPointer( display, root_window, False, 0, GrabModeAsync, GrabModeAsync, + None, None, CurrentTime ) != GrabSuccess) + { + LARGE_INTEGER timeout = {.QuadPart = -10 * (ULONGLONG)10000}; + NtDelayExecution( FALSE, &timeout ); + } + + XUngrabPointer( display, CurrentTime ); + XFlush( display ); + + /* restore the previously used clipping rect */ + NtUserClipCursor( &rect ); +} enum event_merge_action { @@ -322,25 +407,6 @@ static enum event_merge_action merge_raw_motion_events( XIRawEvent *prev, XIRawE } #endif -static int try_grab_pointer( Display *display ) -{ - if (!grab_pointer) - return 1; - - /* if we are already clipping the cursor in the current thread, we should not - * call XGrabPointer here or it would change the confine-to window. */ - if (clipping_cursor && x11drv_thread_data()->clip_hwnd) - return 1; - - if (XGrabPointer( display, root_window, False, 0, GrabModeAsync, GrabModeAsync, - None, None, CurrentTime ) != GrabSuccess) - return 0; - - XUngrabPointer( display, CurrentTime ); - XFlush( display ); - return 1; -} - /*********************************************************************** * merge_events * @@ -423,11 +489,13 @@ static inline BOOL call_event_handler( Display *display, XEvent *event ) return FALSE; /* no handler, ignore it */ } + pthread_mutex_lock( &input_cs ); #ifdef GenericEvent if (event->type == GenericEvent) hwnd = 0; else #endif if (XFindContext( display, event->xany.window, winContext, (char **)&hwnd ) != 0) hwnd = 0; /* not for a registered window */ + pthread_mutex_unlock( &input_cs ); if (!hwnd && event->xany.window == root_window) hwnd = NtUserGetDesktopWindow(); TRACE( "%lu %s for hwnd/window %p/%lx\n", @@ -462,6 +530,15 @@ static BOOL process_events( Display *display, Bool (*filter)(Display*, XEvent*,X prev_event.type = 0; while (XCheckIfEvent( display, &event, filter, (char *)arg )) { + switch (event.type) + { + case KeyPress: + case KeyRelease: + case KeymapNotify: + if (input_thread_hack && display != x11drv_input_display()) continue; + break; + } + count++; if (overlay_enabled && filter_event( display, &event, (char *)overlay_filter )) continue; if (steam_keyboard_opened && filter_event( display, &event, (char *)keyboard_filter )) continue; @@ -623,24 +700,16 @@ static void set_input_focus( struct x11drv_win_data *data ) /********************************************************************** * set_focus */ -static void set_focus( XEvent *xev, HWND hwnd, Time time ) +static void set_focus( Display *display, HWND hwnd, Time time ) { HWND focus; Window win; GUITHREADINFO threadinfo; - if (!try_grab_pointer( xev->xany.display )) - { - /* ask the foreground window to release its grab before trying to get ours */ - send_message( NtUserGetForegroundWindow(), WM_X11DRV_RELEASE_CURSOR, 0, 0 ); - XSendEvent( xev->xany.display, xev->xany.window, False, 0, xev ); - return; - } - else - { - TRACE( "setting foreground window to %p\n", hwnd ); - NtUserSetForegroundWindow( hwnd ); - } + wait_grab_pointer( display ); + + TRACE( "setting foreground window to %p\n", hwnd ); + NtUserSetForegroundWindow( hwnd ); threadinfo.cbSize = sizeof(threadinfo); NtUserGetGUIThreadInfo( 0, &threadinfo ); @@ -652,7 +721,7 @@ static void set_focus( XEvent *xev, HWND hwnd, Time time ) if (win) { TRACE( "setting focus to %p (%lx) time=%ld\n", focus, win, time ); - XSetInputFocus( xev->xany.display, win, RevertToParent, time ); + XSetInputFocus( display, win, RevertToParent, time ); } } @@ -761,7 +830,7 @@ static void handle_wm_protocols( HWND hwnd, XEvent *xev ) MAKELONG( HTMENU, WM_LBUTTONDOWN ) ); if (ma != MA_NOACTIVATEANDEAT && ma != MA_NOACTIVATE) { - set_focus( xev, hwnd, event_time ); + set_focus( event->display, hwnd, event_time ); return; } } @@ -770,7 +839,7 @@ static void handle_wm_protocols( HWND hwnd, XEvent *xev ) hwnd = NtUserGetForegroundWindow(); if (!hwnd) hwnd = last_focus; if (!hwnd) hwnd = NtUserGetDesktopWindow(); - set_focus( xev, hwnd, event_time ); + set_focus( event->display, hwnd, event_time ); return; } /* try to find some other window to give the focus to */ @@ -778,7 +847,7 @@ static void handle_wm_protocols( HWND hwnd, XEvent *xev ) if (hwnd) hwnd = NtUserGetAncestor( hwnd, GA_ROOT ); if (!hwnd) hwnd = get_active_window(); if (!hwnd) hwnd = last_focus; - if (hwnd && can_activate_window(hwnd)) set_focus( xev, hwnd, event_time ); + if (hwnd && can_activate_window(hwnd)) set_focus( event->display, hwnd, event_time ); } else if (protocol == x11drv_atom(_NET_WM_PING)) { @@ -812,19 +881,33 @@ static const char * const focus_modes[] = "NotifyWhileGrabbed" }; +BOOL is_current_process_focused(void) +{ + Display *display = x11drv_thread_data()->display; + Window focus; + int revert; + HWND hwnd; + + XGetInputFocus( display, &focus, &revert ); + if (focus && !XFindContext( display, focus, winContext, (char **)&hwnd )) return TRUE; + return FALSE; +} + /********************************************************************** * X11DRV_FocusIn */ static BOOL X11DRV_FocusIn( HWND hwnd, XEvent *xev ) { XFocusChangeEvent *event = &xev->xfocus; - XIC xic; + BOOL was_grabbed; if (!hwnd) return FALSE; TRACE( "win %p xwin %lx detail=%s mode=%s\n", hwnd, event->window, focus_details[event->detail], focus_modes[event->mode] ); if (event->detail == NotifyPointer) return FALSE; + /* when focusing in the virtual desktop window, re-apply the cursor clipping rect */ + if (is_virtual_desktop() && hwnd == NtUserGetDesktopWindow()) retry_grab_clipping_window(); if (hwnd == NtUserGetDesktopWindow()) return FALSE; x11drv_thread_data()->keymapnotify_hwnd = hwnd; @@ -840,29 +923,16 @@ static BOOL X11DRV_FocusIn( HWND hwnd, XEvent *xev ) NtDelayExecution( FALSE, &timeout ); } - if (!try_grab_pointer( event->display )) - { - /* ask the desktop window to release its grab before trying to get ours */ - send_message( NtUserGetDesktopWindow(), WM_X11DRV_RELEASE_CURSOR, 0, 0 ); - XSendEvent( event->display, event->window, False, 0, xev ); - return FALSE; - } - - /* ask the foreground window to re-apply the current ClipCursor rect */ - if (!send_message_timeout( NtUserGetForegroundWindow(), WM_X11DRV_CLIP_CURSOR_REQUEST, 0, 0, - SMTO_NOTIMEOUTIFNOTHUNG, 500, NULL ) && - RtlGetLastWin32Error() == ERROR_TIMEOUT) - ERR( "WM_X11DRV_CLIP_CURSOR_REQUEST timed out.\n" ); - + /* when keyboard grab is released, re-apply the cursor clipping rect */ + was_grabbed = keyboard_grabbed; + keyboard_grabbed = event->mode == NotifyGrab || event->mode == NotifyWhileGrabbed; + if (was_grabbed > keyboard_grabbed) retry_grab_clipping_window(); /* ignore wm specific NotifyUngrab / NotifyGrab events w.r.t focus */ if (event->mode == NotifyGrab || event->mode == NotifyUngrab) return FALSE; - if ((xic = X11DRV_get_ic( hwnd ))) XSetICFocus( xic ); - if (use_take_focus) - { - if (hwnd == NtUserGetForegroundWindow()) clip_fullscreen_window( hwnd, FALSE ); - return TRUE; - } + xim_set_focus( hwnd, TRUE ); + + if (use_take_focus) return TRUE; if (!can_activate_window(hwnd)) { @@ -870,11 +940,9 @@ static BOOL X11DRV_FocusIn( HWND hwnd, XEvent *xev ) if (hwnd) hwnd = NtUserGetAncestor( hwnd, GA_ROOT ); if (!hwnd) hwnd = get_active_window(); if (!hwnd) hwnd = x11drv_thread_data()->last_focus; - if (hwnd && can_activate_window(hwnd)) set_focus( xev, hwnd, CurrentTime ); - return TRUE; + if (hwnd && can_activate_window(hwnd)) set_focus( event->display, hwnd, CurrentTime ); } - - NtUserSetForegroundWindow( hwnd ); + else NtUserSetForegroundWindow( hwnd ); return TRUE; } @@ -883,13 +951,9 @@ static BOOL X11DRV_FocusIn( HWND hwnd, XEvent *xev ) */ static void focus_out( Display *display , HWND hwnd ) { - HWND hwnd_tmp; - Window focus_win; - int revert; - XIC xic; struct x11drv_win_data *data; - if (ximInComposeMode) return; + if (xim_in_compose_mode()) return; data = get_win_data(hwnd); if(data){ @@ -912,13 +976,9 @@ static void focus_out( Display *display , HWND hwnd ) } x11drv_thread_data()->last_focus = hwnd; - if ((xic = X11DRV_get_ic( hwnd ))) XUnsetICFocus( xic ); + xim_set_focus( hwnd, FALSE ); - if (is_virtual_desktop()) - { - if (hwnd == NtUserGetDesktopWindow()) reset_clipping_window(); - return; - } + if (is_virtual_desktop()) return; if (hwnd != NtUserGetForegroundWindow()) return; if (!(NtUserGetWindowLongW( hwnd, GWL_STYLE ) & WS_MINIMIZE)) send_message( hwnd, WM_CANCELMODE, 0, 0 ); @@ -926,14 +986,7 @@ static void focus_out( Display *display , HWND hwnd ) /* don't reset the foreground window, if the window which is getting the focus is a Wine window */ - XGetInputFocus( display, &focus_win, &revert ); - if (focus_win) - { - if (XFindContext( display, focus_win, winContext, (char **)&hwnd_tmp ) != 0) - focus_win = 0; - } - - if (!focus_win) + if (!is_current_process_focused()) { /* Abey : 6-Oct-99. Check again if the focus out window is the Foreground window, because in most cases the messages sent @@ -960,13 +1013,14 @@ static BOOL X11DRV_FocusOut( HWND hwnd, XEvent *xev ) if (event->detail == NotifyPointer) { - if (!hwnd && event->window == x11drv_thread_data()->clip_window) reset_clipping_window(); + if (!hwnd && event->window == x11drv_thread_data()->clip_window) NtUserClipCursor( NULL ); return TRUE; } if (!hwnd) return FALSE; - if (hwnd == NtUserGetForegroundWindow()) ungrab_clipping_window(); - + /* in virtual desktop mode or when keyboard is grabbed, release any cursor grab but keep the clipping rect */ + keyboard_grabbed = event->mode == NotifyGrab || event->mode == NotifyWhileGrabbed; + if (is_virtual_desktop() || keyboard_grabbed) ungrab_clipping_window(); /* ignore wm specific NotifyUngrab / NotifyGrab events w.r.t focus */ if (event->mode == NotifyGrab || event->mode == NotifyUngrab) return FALSE; @@ -1058,6 +1112,8 @@ static BOOL X11DRV_MapNotify( HWND hwnd, XEvent *event ) { struct x11drv_win_data *data; + x11drv_input_add_window( hwnd, event->xany.window ); + if (event->xany.window == x11drv_thread_data()->clip_window) return TRUE; if (!(data = get_win_data( hwnd ))) return FALSE; @@ -1078,6 +1134,7 @@ static BOOL X11DRV_MapNotify( HWND hwnd, XEvent *event ) */ static BOOL X11DRV_UnmapNotify( HWND hwnd, XEvent *event ) { + x11drv_input_remove_window( event->xany.window ); return TRUE; } @@ -2101,3 +2158,19 @@ static BOOL X11DRV_ClientMessage( HWND hwnd, XEvent *xev ) TRACE( "no handler found for %ld\n", event->message_type ); return FALSE; } + +NTSTATUS x11drv_input_thread( void *arg ) +{ + struct x11drv_thread_data *data = x11drv_init_thread_data(); + + x11drv_set_input_display( data->display ); + + for (;;) + { + XEvent event; + XPeekEvent( data->display, &event ); + process_events( data->display, filter_event, QS_ALLINPUT ); + } + + return 0; +} diff --git a/dlls/winex11.drv/graphics.c b/dlls/winex11.drv/graphics.c index fbc1c9cde1b..ab861dc6bc8 100644 --- a/dlls/winex11.drv/graphics.c +++ b/dlls/winex11.drv/graphics.c @@ -1690,7 +1690,7 @@ BOOL CDECL X11DRV_GetICMProfile( PHYSDEV dev, BOOL allow_default, LPDWORD size, else if ((buffer = get_icm_profile( &buflen ))) { static const WCHAR icm[] = {'.','i','c','m',0}; - IO_STATUS_BLOCK io; + IO_STATUS_BLOCK io = {{0}}; UINT64 hash = 0; HANDLE file; int status; diff --git a/dlls/winex11.drv/ime.c b/dlls/winex11.drv/ime.c deleted file mode 100644 index a293daa6ad9..00000000000 --- a/dlls/winex11.drv/ime.c +++ /dev/null @@ -1,1410 +0,0 @@ -/* - * The IME for interfacing with XIM - * - * Copyright 2008 CodeWeavers, Aric Stewart - * - * 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 - */ - -/* - * Notes: - * The normal flow for IMM/IME Processing is as follows. - * 1) The Keyboard Driver generates key messages which are first passed to - * the IMM and then to IME via ImeProcessKey. If the IME returns 0 then - * it does not want the key and the keyboard driver then generates the - * WM_KEYUP/WM_KEYDOWN messages. However if the IME is going to process the - * key it returns non-zero. - * 2) If the IME is going to process the key then the IMM calls ImeToAsciiEx to - * process the key. the IME modifies the HIMC structure to reflect the - * current state and generates any messages it needs the IMM to process. - * 3) IMM checks the messages and send them to the application in question. From - * here the IMM level deals with if the application is IME aware or not. - * - * This flow does not work well for the X11 driver and XIM. - * (It works fine for Mac) - * As such we will have to reroute step 1. Instead the x11drv driver will - * generate an XIM events and call directly into this IME implementation. - * As such we will have to use the alternative ImmGenerateMessage path to be - * generate the messages that we want the IMM layer to send to the application. - */ - -#include "x11drv_dll.h" -#include "wine/debug.h" -#include "imm.h" -#include "immdev.h" - -WINE_DEFAULT_DEBUG_CHANNEL(imm); - -#define FROM_X11 ((HIMC)0xcafe1337) - -typedef struct _IMEPRIVATE { - BOOL bInComposition; - BOOL bInternalState; - HFONT textfont; - HWND hwndDefault; -} IMEPRIVATE, *LPIMEPRIVATE; - -static const WCHAR UI_CLASS_NAME[] = {'W','i','n','e','X','1','1','I','M','E',0}; - -static HIMC *hSelectedFrom = NULL; -static INT hSelectedCount = 0; - -/* MSIME messages */ -static UINT WM_MSIME_SERVICE; -static UINT WM_MSIME_RECONVERTOPTIONS; -static UINT WM_MSIME_MOUSE; -static UINT WM_MSIME_RECONVERTREQUEST; -static UINT WM_MSIME_RECONVERT; -static UINT WM_MSIME_QUERYPOSITION; -static UINT WM_MSIME_DOCUMENTFEED; - -static LRESULT WINAPI IME_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, - LPARAM lParam); - -static HIMC RealIMC(HIMC hIMC) -{ - if (hIMC == FROM_X11) - { - INT i; - HWND wnd = GetFocus(); - HIMC winHimc = ImmGetContext(wnd); - for (i = 0; i < hSelectedCount; i++) - if (winHimc == hSelectedFrom[i]) - return winHimc; - return NULL; - } - else - return hIMC; -} - -static LPINPUTCONTEXT LockRealIMC(HIMC hIMC) -{ - HIMC real_imc = RealIMC(hIMC); - if (real_imc) - return ImmLockIMC(real_imc); - else - return NULL; -} - -static BOOL UnlockRealIMC(HIMC hIMC) -{ - HIMC real_imc = RealIMC(hIMC); - if (real_imc) - return ImmUnlockIMC(real_imc); - else - return FALSE; -} - -static BOOL WINAPI register_classes( INIT_ONCE *once, void *param, void **context ) -{ - WNDCLASSW wndClass; - - ZeroMemory(&wndClass, sizeof(WNDCLASSW)); - wndClass.style = CS_GLOBALCLASS | CS_IME | CS_HREDRAW | CS_VREDRAW; - wndClass.lpfnWndProc = IME_WindowProc; - wndClass.cbClsExtra = 0; - wndClass.cbWndExtra = 2 * sizeof(LONG_PTR); - wndClass.hInstance = x11drv_module; - wndClass.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_ARROW); - wndClass.hIcon = LoadIconW(NULL, (LPWSTR)IDI_APPLICATION); - wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW +1); - wndClass.lpszMenuName = 0; - wndClass.lpszClassName = UI_CLASS_NAME; - - RegisterClassW(&wndClass); - - WM_MSIME_SERVICE = RegisterWindowMessageA("MSIMEService"); - WM_MSIME_RECONVERTOPTIONS = RegisterWindowMessageA("MSIMEReconvertOptions"); - WM_MSIME_MOUSE = RegisterWindowMessageA("MSIMEMouseOperation"); - WM_MSIME_RECONVERTREQUEST = RegisterWindowMessageA("MSIMEReconvertRequest"); - WM_MSIME_RECONVERT = RegisterWindowMessageA("MSIMEReconvert"); - WM_MSIME_QUERYPOSITION = RegisterWindowMessageA("MSIMEQueryPosition"); - WM_MSIME_DOCUMENTFEED = RegisterWindowMessageA("MSIMEDocumentFeed"); - return TRUE; -} - -static HIMCC ImeCreateBlankCompStr(void) -{ - HIMCC rc; - LPCOMPOSITIONSTRING ptr; - rc = ImmCreateIMCC(sizeof(COMPOSITIONSTRING)); - ptr = ImmLockIMCC(rc); - memset(ptr,0,sizeof(COMPOSITIONSTRING)); - ptr->dwSize = sizeof(COMPOSITIONSTRING); - ImmUnlockIMCC(rc); - return rc; -} - -static int updateField(DWORD origLen, DWORD origOffset, DWORD currentOffset, - LPBYTE target, LPBYTE source, DWORD* lenParam, - DWORD* offsetParam, BOOL wchars ) -{ - if (origLen > 0 && origOffset > 0) - { - int truelen = origLen; - if (wchars) - truelen *= sizeof(WCHAR); - - memcpy(&target[currentOffset], &source[origOffset], truelen); - - *lenParam = origLen; - *offsetParam = currentOffset; - currentOffset += truelen; - } - return currentOffset; -} - -static HIMCC updateCompStr(HIMCC old, LPCWSTR compstr, DWORD len) -{ - /* We need to make sure the CompStr, CompClause and CompAttr fields are all - * set and correct. */ - int needed_size; - HIMCC rc; - LPBYTE newdata = NULL; - LPBYTE olddata = NULL; - LPCOMPOSITIONSTRING new_one; - LPCOMPOSITIONSTRING lpcs = NULL; - INT current_offset = 0; - - TRACE("%s, %li\n",debugstr_wn(compstr,len),len); - - if (old == NULL && compstr == NULL && len == 0) - return NULL; - - if (compstr == NULL && len != 0) - { - ERR("compstr is NULL however we have a len! Please report\n"); - len = 0; - } - - if (old != NULL) - { - olddata = ImmLockIMCC(old); - lpcs = (LPCOMPOSITIONSTRING)olddata; - } - - needed_size = sizeof(COMPOSITIONSTRING) + len * sizeof(WCHAR) + - len + sizeof(DWORD) * 2; - - if (lpcs != NULL) - { - needed_size += lpcs->dwCompReadAttrLen; - needed_size += lpcs->dwCompReadClauseLen; - needed_size += lpcs->dwCompReadStrLen * sizeof(WCHAR); - needed_size += lpcs->dwResultReadClauseLen; - needed_size += lpcs->dwResultReadStrLen * sizeof(WCHAR); - needed_size += lpcs->dwResultClauseLen; - needed_size += lpcs->dwResultStrLen * sizeof(WCHAR); - needed_size += lpcs->dwPrivateSize; - } - rc = ImmCreateIMCC(needed_size); - newdata = ImmLockIMCC(rc); - new_one = (LPCOMPOSITIONSTRING)newdata; - - new_one->dwSize = needed_size; - current_offset = sizeof(COMPOSITIONSTRING); - if (lpcs != NULL) - { - current_offset = updateField(lpcs->dwCompReadAttrLen, - lpcs->dwCompReadAttrOffset, - current_offset, newdata, olddata, - &new_one->dwCompReadAttrLen, - &new_one->dwCompReadAttrOffset, FALSE); - - current_offset = updateField(lpcs->dwCompReadClauseLen, - lpcs->dwCompReadClauseOffset, - current_offset, newdata, olddata, - &new_one->dwCompReadClauseLen, - &new_one->dwCompReadClauseOffset, FALSE); - - current_offset = updateField(lpcs->dwCompReadStrLen, - lpcs->dwCompReadStrOffset, - current_offset, newdata, olddata, - &new_one->dwCompReadStrLen, - &new_one->dwCompReadStrOffset, TRUE); - - /* new CompAttr, CompClause, CompStr, dwCursorPos */ - new_one->dwDeltaStart = 0; - - current_offset = updateField(lpcs->dwResultReadClauseLen, - lpcs->dwResultReadClauseOffset, - current_offset, newdata, olddata, - &new_one->dwResultReadClauseLen, - &new_one->dwResultReadClauseOffset, FALSE); - - current_offset = updateField(lpcs->dwResultReadStrLen, - lpcs->dwResultReadStrOffset, - current_offset, newdata, olddata, - &new_one->dwResultReadStrLen, - &new_one->dwResultReadStrOffset, TRUE); - - current_offset = updateField(lpcs->dwResultClauseLen, - lpcs->dwResultClauseOffset, - current_offset, newdata, olddata, - &new_one->dwResultClauseLen, - &new_one->dwResultClauseOffset, FALSE); - - current_offset = updateField(lpcs->dwResultStrLen, - lpcs->dwResultStrOffset, - current_offset, newdata, olddata, - &new_one->dwResultStrLen, - &new_one->dwResultStrOffset, TRUE); - - current_offset = updateField(lpcs->dwPrivateSize, - lpcs->dwPrivateOffset, - current_offset, newdata, olddata, - &new_one->dwPrivateSize, - &new_one->dwPrivateOffset, FALSE); - } - - /* set new data */ - /* CompAttr */ - new_one->dwCompAttrLen = len; - if (len > 0) - { - new_one->dwCompAttrOffset = current_offset; - memset(&newdata[current_offset],ATTR_INPUT,len); - current_offset += len; - } - - /* CompClause */ - if (len > 0) - { - new_one->dwCompClauseLen = sizeof(DWORD) * 2; - new_one->dwCompClauseOffset = current_offset; - *(DWORD*)(&newdata[current_offset]) = 0; - current_offset += sizeof(DWORD); - *(DWORD*)(&newdata[current_offset]) = len; - current_offset += sizeof(DWORD); - } - else - new_one->dwCompClauseLen = 0; - - /* CompStr */ - new_one->dwCompStrLen = len; - if (len > 0) - { - new_one->dwCompStrOffset = current_offset; - memcpy(&newdata[current_offset],compstr,len*sizeof(WCHAR)); - } - - /* CursorPos */ - new_one->dwCursorPos = len; - - ImmUnlockIMCC(rc); - if (lpcs) - ImmUnlockIMCC(old); - - return rc; -} - -static HIMCC updateResultStr(HIMCC old, LPWSTR resultstr, DWORD len) -{ - /* we need to make sure the ResultStr and ResultClause fields are all - * set and correct */ - int needed_size; - HIMCC rc; - LPBYTE newdata = NULL; - LPBYTE olddata = NULL; - LPCOMPOSITIONSTRING new_one; - LPCOMPOSITIONSTRING lpcs = NULL; - INT current_offset = 0; - - TRACE("%s, %li\n",debugstr_wn(resultstr,len),len); - - if (old == NULL && resultstr == NULL && len == 0) - return NULL; - - if (resultstr == NULL && len != 0) - { - ERR("resultstr is NULL however we have a len! Please report\n"); - len = 0; - } - - if (old != NULL) - { - olddata = ImmLockIMCC(old); - lpcs = (LPCOMPOSITIONSTRING)olddata; - } - - needed_size = sizeof(COMPOSITIONSTRING) + len * sizeof(WCHAR) + - sizeof(DWORD) * 2; - - if (lpcs != NULL) - { - needed_size += lpcs->dwCompReadAttrLen; - needed_size += lpcs->dwCompReadClauseLen; - needed_size += lpcs->dwCompReadStrLen * sizeof(WCHAR); - needed_size += lpcs->dwCompAttrLen; - needed_size += lpcs->dwCompClauseLen; - needed_size += lpcs->dwCompStrLen * sizeof(WCHAR); - needed_size += lpcs->dwResultReadClauseLen; - needed_size += lpcs->dwResultReadStrLen * sizeof(WCHAR); - needed_size += lpcs->dwPrivateSize; - } - rc = ImmCreateIMCC(needed_size); - newdata = ImmLockIMCC(rc); - new_one = (LPCOMPOSITIONSTRING)newdata; - - new_one->dwSize = needed_size; - current_offset = sizeof(COMPOSITIONSTRING); - if (lpcs != NULL) - { - current_offset = updateField(lpcs->dwCompReadAttrLen, - lpcs->dwCompReadAttrOffset, - current_offset, newdata, olddata, - &new_one->dwCompReadAttrLen, - &new_one->dwCompReadAttrOffset, FALSE); - - current_offset = updateField(lpcs->dwCompReadClauseLen, - lpcs->dwCompReadClauseOffset, - current_offset, newdata, olddata, - &new_one->dwCompReadClauseLen, - &new_one->dwCompReadClauseOffset, FALSE); - - current_offset = updateField(lpcs->dwCompReadStrLen, - lpcs->dwCompReadStrOffset, - current_offset, newdata, olddata, - &new_one->dwCompReadStrLen, - &new_one->dwCompReadStrOffset, TRUE); - - current_offset = updateField(lpcs->dwCompAttrLen, - lpcs->dwCompAttrOffset, - current_offset, newdata, olddata, - &new_one->dwCompAttrLen, - &new_one->dwCompAttrOffset, FALSE); - - current_offset = updateField(lpcs->dwCompClauseLen, - lpcs->dwCompClauseOffset, - current_offset, newdata, olddata, - &new_one->dwCompClauseLen, - &new_one->dwCompClauseOffset, FALSE); - - current_offset = updateField(lpcs->dwCompStrLen, - lpcs->dwCompStrOffset, - current_offset, newdata, olddata, - &new_one->dwCompStrLen, - &new_one->dwCompStrOffset, TRUE); - - new_one->dwCursorPos = lpcs->dwCursorPos; - new_one->dwDeltaStart = 0; - - current_offset = updateField(lpcs->dwResultReadClauseLen, - lpcs->dwResultReadClauseOffset, - current_offset, newdata, olddata, - &new_one->dwResultReadClauseLen, - &new_one->dwResultReadClauseOffset, FALSE); - - current_offset = updateField(lpcs->dwResultReadStrLen, - lpcs->dwResultReadStrOffset, - current_offset, newdata, olddata, - &new_one->dwResultReadStrLen, - &new_one->dwResultReadStrOffset, TRUE); - - /* new ResultClause , ResultStr */ - - current_offset = updateField(lpcs->dwPrivateSize, - lpcs->dwPrivateOffset, - current_offset, newdata, olddata, - &new_one->dwPrivateSize, - &new_one->dwPrivateOffset, FALSE); - } - - /* set new data */ - /* ResultClause */ - if (len > 0) - { - new_one->dwResultClauseLen = sizeof(DWORD) * 2; - new_one->dwResultClauseOffset = current_offset; - *(DWORD*)(&newdata[current_offset]) = 0; - current_offset += sizeof(DWORD); - *(DWORD*)(&newdata[current_offset]) = len; - current_offset += sizeof(DWORD); - } - else - new_one->dwResultClauseLen = 0; - - /* ResultStr */ - new_one->dwResultStrLen = len; - if (len > 0) - { - new_one->dwResultStrOffset = current_offset; - memcpy(&newdata[current_offset],resultstr,len*sizeof(WCHAR)); - } - ImmUnlockIMCC(rc); - if (lpcs) - ImmUnlockIMCC(old); - - return rc; -} - -static void GenerateIMEMessage(HIMC hIMC, UINT msg, WPARAM wParam, - LPARAM lParam) -{ - LPINPUTCONTEXT lpIMC; - LPTRANSMSG lpTransMsg; - - lpIMC = LockRealIMC(hIMC); - if (lpIMC == NULL) - return; - - lpIMC->hMsgBuf = ImmReSizeIMCC(lpIMC->hMsgBuf, (lpIMC->dwNumMsgBuf + 1) * - sizeof(TRANSMSG)); - if (!lpIMC->hMsgBuf) - return; - - lpTransMsg = ImmLockIMCC(lpIMC->hMsgBuf); - if (!lpTransMsg) - return; - - lpTransMsg += lpIMC->dwNumMsgBuf; - lpTransMsg->message = msg; - lpTransMsg->wParam = wParam; - lpTransMsg->lParam = lParam; - - ImmUnlockIMCC(lpIMC->hMsgBuf); - lpIMC->dwNumMsgBuf++; - - ImmGenerateMessage(RealIMC(hIMC)); - UnlockRealIMC(hIMC); -} - -static BOOL IME_RemoveFromSelected(HIMC hIMC) -{ - int i; - for (i = 0; i < hSelectedCount; i++) - if (hSelectedFrom[i] == hIMC) - { - if (i < hSelectedCount - 1) - memmove(&hSelectedFrom[i], &hSelectedFrom[i+1], (hSelectedCount - i - 1)*sizeof(HIMC)); - hSelectedCount --; - return TRUE; - } - return FALSE; -} - -static void IME_AddToSelected(HIMC hIMC) -{ - hSelectedCount++; - if (hSelectedFrom) - hSelectedFrom = HeapReAlloc(GetProcessHeap(), 0, hSelectedFrom, hSelectedCount*sizeof(HIMC)); - else - hSelectedFrom = HeapAlloc(GetProcessHeap(), 0, sizeof(HIMC)); - hSelectedFrom[hSelectedCount-1] = hIMC; -} - -BOOL WINAPI ImeInquire(LPIMEINFO lpIMEInfo, LPWSTR lpszUIClass, DWORD flags) -{ - static INIT_ONCE init_once = INIT_ONCE_STATIC_INIT; - - TRACE("\n"); - InitOnceExecuteOnce( &init_once, register_classes, NULL, NULL ); - lpIMEInfo->dwPrivateDataSize = sizeof (IMEPRIVATE); - lpIMEInfo->fdwProperty = IME_PROP_UNICODE | IME_PROP_AT_CARET; - lpIMEInfo->fdwConversionCaps = IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE; - lpIMEInfo->fdwSentenceCaps = IME_SMODE_AUTOMATIC; - lpIMEInfo->fdwUICaps = UI_CAP_2700; - /* Tell App we cannot accept ImeSetCompositionString calls */ - lpIMEInfo->fdwSCSCaps = 0; - lpIMEInfo->fdwSelectCaps = SELECT_CAP_CONVERSION; - - lstrcpyW(lpszUIClass,UI_CLASS_NAME); - - return TRUE; -} - -BOOL WINAPI ImeConfigure(HKL hKL,HWND hWnd, DWORD dwMode, LPVOID lpData) -{ - FIXME("(%p, %p, %ld, %p): stub\n", hKL, hWnd, dwMode, lpData); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; -} - -DWORD WINAPI ImeConversionList(HIMC hIMC, LPCWSTR lpSource, - LPCANDIDATELIST lpCandList, DWORD dwBufLen, UINT uFlag) - -{ - FIXME("(%p, %s, %p, %ld, %d): stub\n", hIMC, debugstr_w(lpSource), - lpCandList, dwBufLen, uFlag); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return 0; -} - -BOOL WINAPI ImeDestroy(UINT uForce) -{ - TRACE("\n"); - HeapFree(GetProcessHeap(),0,hSelectedFrom); - hSelectedFrom = NULL; - hSelectedCount = 0; - return TRUE; -} - -LRESULT WINAPI ImeEscape(HIMC hIMC, UINT uSubFunc, LPVOID lpData) -{ - FIXME("(%p, %d, %p): stub\n", hIMC, uSubFunc, lpData); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return 0; -} - -BOOL WINAPI ImeProcessKey(HIMC hIMC, UINT vKey, LPARAM lKeyData, const LPBYTE lpbKeyState) -{ - /* See the comment at the head of this file */ - TRACE("We do no processing via this route\n"); - return FALSE; -} - -BOOL WINAPI ImeSelect(HIMC hIMC, BOOL fSelect) -{ - LPINPUTCONTEXT lpIMC; - TRACE("%p %s\n",hIMC,(fSelect)?"TRUE":"FALSE"); - - if (hIMC == FROM_X11) - { - ERR("ImeSelect should never be called from X11\n"); - return FALSE; - } - - if (!hIMC) - return TRUE; - - /* not selected */ - if (!fSelect) - return IME_RemoveFromSelected(hIMC); - - IME_AddToSelected(hIMC); - - /* Initialize our structures */ - lpIMC = LockRealIMC(hIMC); - if (lpIMC != NULL) - { - LPIMEPRIVATE myPrivate; - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - myPrivate->bInComposition = FALSE; - myPrivate->bInternalState = FALSE; - myPrivate->textfont = NULL; - myPrivate->hwndDefault = NULL; - ImmUnlockIMCC(lpIMC->hPrivate); - UnlockRealIMC(hIMC); - } - - return TRUE; -} - -BOOL WINAPI ImeSetActiveContext(HIMC hIMC,BOOL fFlag) -{ - static int once; - - if (!once++) - FIXME("(%p, %x): stub\n", hIMC, fFlag); - return TRUE; -} - -UINT WINAPI ImeToAsciiEx (UINT uVKey, UINT uScanCode, const LPBYTE lpbKeyState, - TRANSMSGLIST *lpdwTransKey, UINT fuState, HIMC hIMC) -{ - /* See the comment at the head of this file */ - TRACE("We do no processing via this route\n"); - return 0; -} - -BOOL WINAPI NotifyIME(HIMC hIMC, DWORD dwAction, DWORD dwIndex, DWORD dwValue) -{ - struct xim_preedit_state_params preedit_params; - BOOL bRet = FALSE; - LPINPUTCONTEXT lpIMC; - - TRACE("%p %li %li %li\n",hIMC,dwAction,dwIndex,dwValue); - - lpIMC = LockRealIMC(hIMC); - if (lpIMC == NULL) - return FALSE; - - switch (dwAction) - { - case NI_OPENCANDIDATE: FIXME("NI_OPENCANDIDATE\n"); break; - case NI_CLOSECANDIDATE: FIXME("NI_CLOSECANDIDATE\n"); break; - case NI_SELECTCANDIDATESTR: FIXME("NI_SELECTCANDIDATESTR\n"); break; - case NI_CHANGECANDIDATELIST: FIXME("NI_CHANGECANDIDATELIST\n"); break; - case NI_SETCANDIDATE_PAGESTART: FIXME("NI_SETCANDIDATE_PAGESTART\n"); break; - case NI_SETCANDIDATE_PAGESIZE: FIXME("NI_SETCANDIDATE_PAGESIZE\n"); break; - case NI_CONTEXTUPDATED: - switch (dwValue) - { - case IMC_SETCOMPOSITIONWINDOW: FIXME("IMC_SETCOMPOSITIONWINDOW\n"); break; - case IMC_SETCONVERSIONMODE: FIXME("IMC_SETCONVERSIONMODE\n"); break; - case IMC_SETSENTENCEMODE: FIXME("IMC_SETSENTENCEMODE\n"); break; - case IMC_SETCANDIDATEPOS: FIXME("IMC_SETCANDIDATEPOS\n"); break; - case IMC_SETCOMPOSITIONFONT: - { - LPIMEPRIVATE myPrivate; - TRACE("IMC_SETCOMPOSITIONFONT\n"); - - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - if (myPrivate->textfont) - { - DeleteObject(myPrivate->textfont); - myPrivate->textfont = NULL; - } - myPrivate->textfont = CreateFontIndirectW(&lpIMC->lfFont.W); - ImmUnlockIMCC(lpIMC->hPrivate); - } - break; - case IMC_SETOPENSTATUS: - TRACE("IMC_SETOPENSTATUS\n"); - - bRet = TRUE; - preedit_params.hwnd = lpIMC->hWnd; - preedit_params.open = lpIMC->fOpen; - X11DRV_CALL( xim_preedit_state, &preedit_params ); - if (!lpIMC->fOpen) - { - LPIMEPRIVATE myPrivate; - - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - if (myPrivate->bInComposition) - { - X11DRV_CALL( xim_reset, lpIMC->hWnd ); - GenerateIMEMessage(hIMC, WM_IME_ENDCOMPOSITION, 0, 0); - myPrivate->bInComposition = FALSE; - } - ImmUnlockIMCC(lpIMC->hPrivate); - } - - break; - default: FIXME("Unknown\n"); break; - } - break; - case NI_COMPOSITIONSTR: - switch (dwIndex) - { - case CPS_COMPLETE: - { - HIMCC newCompStr; - DWORD cplen = 0; - LPWSTR cpstr; - LPCOMPOSITIONSTRING cs = NULL; - LPBYTE cdata = NULL; - LPIMEPRIVATE myPrivate; - - TRACE("CPS_COMPLETE\n"); - - /* clear existing result */ - newCompStr = updateResultStr(lpIMC->hCompStr, NULL, 0); - - ImmDestroyIMCC(lpIMC->hCompStr); - lpIMC->hCompStr = newCompStr; - - if (lpIMC->hCompStr) - { - cdata = ImmLockIMCC(lpIMC->hCompStr); - cs = (LPCOMPOSITIONSTRING)cdata; - cplen = cs->dwCompStrLen; - cpstr = (LPWSTR)&(cdata[cs->dwCompStrOffset]); - ImmUnlockIMCC(lpIMC->hCompStr); - } - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - if (cplen > 0) - { - WCHAR param = cpstr[0]; - - newCompStr = updateResultStr(lpIMC->hCompStr, cpstr, cplen); - ImmDestroyIMCC(lpIMC->hCompStr); - lpIMC->hCompStr = newCompStr; - newCompStr = updateCompStr(lpIMC->hCompStr, NULL, 0); - ImmDestroyIMCC(lpIMC->hCompStr); - lpIMC->hCompStr = newCompStr; - - GenerateIMEMessage(hIMC, WM_IME_COMPOSITION, 0, - GCS_COMPSTR); - - GenerateIMEMessage(hIMC, WM_IME_COMPOSITION, param, - GCS_RESULTSTR|GCS_RESULTCLAUSE); - - GenerateIMEMessage(hIMC,WM_IME_ENDCOMPOSITION, 0, 0); - } - else if (myPrivate->bInComposition) - GenerateIMEMessage(hIMC,WM_IME_ENDCOMPOSITION, 0, 0); - - myPrivate->bInComposition = FALSE; - ImmUnlockIMCC(lpIMC->hPrivate); - - bRet = TRUE; - } - break; - case CPS_CONVERT: FIXME("CPS_CONVERT\n"); break; - case CPS_REVERT: FIXME("CPS_REVERT\n"); break; - case CPS_CANCEL: - { - LPIMEPRIVATE myPrivate; - - TRACE("CPS_CANCEL\n"); - - X11DRV_CALL( xim_reset, lpIMC->hWnd ); - - if (lpIMC->hCompStr) - ImmDestroyIMCC(lpIMC->hCompStr); - lpIMC->hCompStr = ImeCreateBlankCompStr(); - - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - if (myPrivate->bInComposition) - { - GenerateIMEMessage(hIMC, WM_IME_ENDCOMPOSITION, 0, 0); - myPrivate->bInComposition = FALSE; - } - ImmUnlockIMCC(lpIMC->hPrivate); - bRet = TRUE; - } - break; - default: FIXME("Unknown\n"); break; - } - break; - default: FIXME("Unknown Message\n"); break; - } - - UnlockRealIMC(hIMC); - return bRet; -} - -BOOL WINAPI ImeRegisterWord(LPCWSTR lpszReading, DWORD dwStyle, - LPCWSTR lpszRegister) -{ - FIXME("(%s, %ld, %s): stub\n", debugstr_w(lpszReading), dwStyle, - debugstr_w(lpszRegister)); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; -} - -BOOL WINAPI ImeUnregisterWord(LPCWSTR lpszReading, DWORD dwStyle, - LPCWSTR lpszUnregister) -{ - FIXME("(%s, %ld, %s): stub\n", debugstr_w(lpszReading), dwStyle, - debugstr_w(lpszUnregister)); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; -} - -UINT WINAPI ImeGetRegisterWordStyle(UINT nItem, LPSTYLEBUFW lpStyleBuf) -{ - FIXME("(%d, %p): stub\n", nItem, lpStyleBuf); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return 0; -} - -UINT WINAPI ImeEnumRegisterWord(REGISTERWORDENUMPROCW lpfnEnumProc, - LPCWSTR lpszReading, DWORD dwStyle, - LPCWSTR lpszRegister, LPVOID lpData) -{ - FIXME("(%p, %s, %ld, %s, %p): stub\n", lpfnEnumProc, - debugstr_w(lpszReading), dwStyle, debugstr_w(lpszRegister), - lpData); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return 0; -} - -BOOL WINAPI ImeSetCompositionString(HIMC hIMC, DWORD dwIndex, LPCVOID lpComp, - DWORD dwCompLen, LPCVOID lpRead, - DWORD dwReadLen) -{ - LPINPUTCONTEXT lpIMC; - DWORD flags = 0; - WCHAR wParam = 0; - LPIMEPRIVATE myPrivate; - - TRACE("(%p, %ld, %p, %ld, %p, %ld):\n", - hIMC, dwIndex, lpComp, dwCompLen, lpRead, dwReadLen); - - - if (hIMC != FROM_X11) - FIXME("PROBLEM: This only sets the wine level string\n"); - - /* - * Explanation: - * this sets the composition string in the imm32.dll level - * of the composition buffer. we cannot manipulate the xim level - * buffer, which means that once the xim level buffer changes again - * any call to this function from the application will be lost - */ - - if (lpRead && dwReadLen) - FIXME("Reading string unimplemented\n"); - - lpIMC = LockRealIMC(hIMC); - - if (lpIMC == NULL) - return FALSE; - - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - - if (dwIndex == SCS_SETSTR) - { - HIMCC newCompStr; - - if (!myPrivate->bInComposition) - { - GenerateIMEMessage(hIMC, WM_IME_STARTCOMPOSITION, 0, 0); - myPrivate->bInComposition = TRUE; - } - - /* clear existing result */ - newCompStr = updateResultStr(lpIMC->hCompStr, NULL, 0); - ImmDestroyIMCC(lpIMC->hCompStr); - lpIMC->hCompStr = newCompStr; - - flags = GCS_COMPSTR; - - if (dwCompLen && lpComp) - { - newCompStr = updateCompStr(lpIMC->hCompStr, (LPCWSTR)lpComp, dwCompLen / sizeof(WCHAR)); - ImmDestroyIMCC(lpIMC->hCompStr); - lpIMC->hCompStr = newCompStr; - - wParam = ((const WCHAR*)lpComp)[0]; - flags |= GCS_COMPCLAUSE | GCS_COMPATTR | GCS_DELTASTART; - } - else - { - newCompStr = updateCompStr(lpIMC->hCompStr, NULL, 0); - ImmDestroyIMCC(lpIMC->hCompStr); - lpIMC->hCompStr = newCompStr; - } - } - - GenerateIMEMessage(hIMC, WM_IME_COMPOSITION, wParam, flags); - ImmUnlockIMCC(lpIMC->hPrivate); - UnlockRealIMC(hIMC); - - return TRUE; -} - -DWORD WINAPI ImeGetImeMenuItems(HIMC hIMC, DWORD dwFlags, DWORD dwType, - LPIMEMENUITEMINFOW lpImeParentMenu, LPIMEMENUITEMINFOW lpImeMenu, - DWORD dwSize) -{ - FIXME("(%p, %lx %lx %p %p %lx): stub\n", hIMC, dwFlags, dwType, - lpImeParentMenu, lpImeMenu, dwSize); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return 0; -} - -/* Interfaces to XIM and other parts of winex11drv */ - -NTSTATUS x11drv_ime_set_open_status( UINT open ) -{ - HIMC imc; - - imc = RealIMC(FROM_X11); - ImmSetOpenStatus(imc, open); - return 0; -} - -NTSTATUS x11drv_ime_set_composition_status( UINT open ) -{ - HIMC imc; - LPINPUTCONTEXT lpIMC; - LPIMEPRIVATE myPrivate; - - imc = RealIMC(FROM_X11); - lpIMC = ImmLockIMC(imc); - if (lpIMC == NULL) - return 0; - - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - - if (open && !myPrivate->bInComposition) - { - GenerateIMEMessage(imc, WM_IME_STARTCOMPOSITION, 0, 0); - } - else if (!open && myPrivate->bInComposition) - { - ShowWindow(myPrivate->hwndDefault, SW_HIDE); - ImmDestroyIMCC(lpIMC->hCompStr); - lpIMC->hCompStr = ImeCreateBlankCompStr(); - GenerateIMEMessage(imc, WM_IME_ENDCOMPOSITION, 0, 0); - } - myPrivate->bInComposition = open; - - ImmUnlockIMCC(lpIMC->hPrivate); - ImmUnlockIMC(imc); - return 0; -} - -NTSTATUS x11drv_ime_get_cursor_pos( UINT arg ) -{ - LPINPUTCONTEXT lpIMC; - INT rc = 0; - LPCOMPOSITIONSTRING compstr; - - if (!hSelectedFrom) - return rc; - - lpIMC = LockRealIMC(FROM_X11); - if (lpIMC) - { - compstr = ImmLockIMCC(lpIMC->hCompStr); - rc = compstr->dwCursorPos; - ImmUnlockIMCC(lpIMC->hCompStr); - } - UnlockRealIMC(FROM_X11); - return rc; -} - -NTSTATUS x11drv_ime_set_cursor_pos( UINT pos ) -{ - LPINPUTCONTEXT lpIMC; - LPCOMPOSITIONSTRING compstr; - - if (!hSelectedFrom) - return 0; - - lpIMC = LockRealIMC(FROM_X11); - if (!lpIMC) - return 0; - - compstr = ImmLockIMCC(lpIMC->hCompStr); - if (!compstr) - { - UnlockRealIMC(FROM_X11); - return 0; - } - - compstr->dwCursorPos = pos; - ImmUnlockIMCC(lpIMC->hCompStr); - UnlockRealIMC(FROM_X11); - GenerateIMEMessage(FROM_X11, WM_IME_COMPOSITION, pos, GCS_CURSORPOS); - return 0; -} - -NTSTATUS x11drv_ime_update_association( UINT arg ) -{ - HWND focus = UlongToHandle( arg ); - - ImmGetContext(focus); - - if (focus && hSelectedFrom) - ImmAssociateContext(focus,RealIMC(FROM_X11)); - return 0; -} - - -NTSTATUS WINAPI x11drv_ime_set_composition_string( void *param, ULONG size ) -{ - return ImeSetCompositionString(FROM_X11, SCS_SETSTR, param, size, NULL, 0); -} - -NTSTATUS WINAPI x11drv_ime_set_result( void *params, ULONG len ) -{ - WCHAR *lpResult = params; - HIMC imc; - LPINPUTCONTEXT lpIMC; - HIMCC newCompStr; - LPIMEPRIVATE myPrivate; - BOOL inComp; - HWND focus; - - len /= sizeof(WCHAR); - if ((focus = GetFocus())) - x11drv_ime_update_association( HandleToUlong( focus )); - - imc = RealIMC(FROM_X11); - lpIMC = ImmLockIMC(imc); - if (lpIMC == NULL) - return 0; - - newCompStr = updateCompStr(lpIMC->hCompStr, NULL, 0); - ImmDestroyIMCC(lpIMC->hCompStr); - lpIMC->hCompStr = newCompStr; - - newCompStr = updateResultStr(lpIMC->hCompStr, lpResult, len); - ImmDestroyIMCC(lpIMC->hCompStr); - lpIMC->hCompStr = newCompStr; - - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - inComp = myPrivate->bInComposition; - ImmUnlockIMCC(lpIMC->hPrivate); - - if (!inComp) - { - ImmSetOpenStatus(imc, TRUE); - GenerateIMEMessage(imc, WM_IME_STARTCOMPOSITION, 0, 0); - } - - GenerateIMEMessage(imc, WM_IME_COMPOSITION, 0, GCS_COMPSTR); - GenerateIMEMessage(imc, WM_IME_COMPOSITION, lpResult[0], GCS_RESULTSTR|GCS_RESULTCLAUSE); - GenerateIMEMessage(imc, WM_IME_ENDCOMPOSITION, 0, 0); - - if (!inComp) - ImmSetOpenStatus(imc, FALSE); - - ImmUnlockIMC(imc); - return 0; -} - -/***** - * Internal functions to help with IME window management - */ -static void PaintDefaultIMEWnd(HIMC hIMC, HWND hwnd) -{ - PAINTSTRUCT ps; - RECT rect; - HDC hdc; - LPCOMPOSITIONSTRING compstr; - LPBYTE compdata = NULL; - HMONITOR monitor; - MONITORINFO mon_info; - INT offX=0, offY=0; - LPINPUTCONTEXT lpIMC; - - lpIMC = LockRealIMC(hIMC); - if (lpIMC == NULL) - return; - - hdc = BeginPaint(hwnd,&ps); - - GetClientRect(hwnd,&rect); - FillRect(hdc, &rect, (HBRUSH)(COLOR_WINDOW + 1)); - - compdata = ImmLockIMCC(lpIMC->hCompStr); - compstr = (LPCOMPOSITIONSTRING)compdata; - - if (compstr->dwCompStrLen && compstr->dwCompStrOffset) - { - SIZE size; - POINT pt; - HFONT oldfont = NULL; - LPWSTR CompString; - LPIMEPRIVATE myPrivate; - - CompString = (LPWSTR)(compdata + compstr->dwCompStrOffset); - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - - if (myPrivate->textfont) - oldfont = SelectObject(hdc,myPrivate->textfont); - - ImmUnlockIMCC(lpIMC->hPrivate); - - GetTextExtentPoint32W(hdc, CompString, compstr->dwCompStrLen, &size); - pt.x = size.cx; - pt.y = size.cy; - LPtoDP(hdc,&pt,1); - - /* - * How this works based on tests on windows: - * CFS_POINT: then we start our window at the point and grow it as large - * as it needs to be for the string. - * CFS_RECT: we still use the ptCurrentPos as a starting point and our - * window is only as large as we need for the string, but we do not - * grow such that our window exceeds the given rect. Wrapping if - * needed and possible. If our ptCurrentPos is outside of our rect - * then no window is displayed. - * CFS_FORCE_POSITION: appears to behave just like CFS_POINT - * maybe because the default MSIME does not do any IME adjusting. - */ - if (lpIMC->cfCompForm.dwStyle != CFS_DEFAULT) - { - POINT cpt = lpIMC->cfCompForm.ptCurrentPos; - ClientToScreen(lpIMC->hWnd,&cpt); - rect.left = cpt.x; - rect.top = cpt.y; - rect.right = rect.left + pt.x; - rect.bottom = rect.top + pt.y; - monitor = MonitorFromPoint(cpt, MONITOR_DEFAULTTOPRIMARY); - } - else /* CFS_DEFAULT */ - { - /* Windows places the default IME window in the bottom left */ - HWND target = lpIMC->hWnd; - if (!target) target = GetFocus(); - - GetWindowRect(target,&rect); - rect.top = rect.bottom; - rect.right = rect.left + pt.x + 20; - rect.bottom = rect.top + pt.y + 20; - offX=offY=10; - monitor = MonitorFromWindow(target, MONITOR_DEFAULTTOPRIMARY); - } - - if (lpIMC->cfCompForm.dwStyle == CFS_RECT) - { - RECT client; - client =lpIMC->cfCompForm.rcArea; - MapWindowPoints( lpIMC->hWnd, 0, (POINT *)&client, 2 ); - IntersectRect(&rect,&rect,&client); - /* TODO: Wrap the input if needed */ - } - - if (lpIMC->cfCompForm.dwStyle == CFS_DEFAULT) - { - /* make sure we are on the desktop */ - mon_info.cbSize = sizeof(mon_info); - GetMonitorInfoW(monitor, &mon_info); - - if (rect.bottom > mon_info.rcWork.bottom) - { - int shift = rect.bottom - mon_info.rcWork.bottom; - rect.top -= shift; - rect.bottom -= shift; - } - if (rect.left < 0) - { - rect.right -= rect.left; - rect.left = 0; - } - if (rect.right > mon_info.rcWork.right) - { - int shift = rect.right - mon_info.rcWork.right; - rect.left -= shift; - rect.right -= shift; - } - } - - SetWindowPos(hwnd, HWND_TOPMOST, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE); - - TextOutW(hdc, offX,offY, CompString, compstr->dwCompStrLen); - - if (oldfont) - SelectObject(hdc,oldfont); - } - - ImmUnlockIMCC(lpIMC->hCompStr); - - EndPaint(hwnd,&ps); - UnlockRealIMC(hIMC); -} - -static void UpdateDefaultIMEWindow(HIMC hIMC, HWND hwnd) -{ - LPCOMPOSITIONSTRING compstr; - LPINPUTCONTEXT lpIMC; - - lpIMC = LockRealIMC(hIMC); - if (lpIMC == NULL) - return; - - if (lpIMC->hCompStr) - compstr = ImmLockIMCC(lpIMC->hCompStr); - else - compstr = NULL; - - if (compstr == NULL || compstr->dwCompStrLen == 0) - ShowWindow(hwnd,SW_HIDE); - else - { - ShowWindow(hwnd,SW_SHOWNOACTIVATE); - RedrawWindow(hwnd, NULL, NULL, RDW_ERASENOW | RDW_INVALIDATE); - } - - if (compstr != NULL) - ImmUnlockIMCC(lpIMC->hCompStr); - - lpIMC->hWnd = GetFocus(); - UnlockRealIMC(hIMC); -} - -static void DefaultIMEComposition(HIMC hIMC, HWND hwnd, LPARAM lParam) -{ - TRACE("IME message WM_IME_COMPOSITION 0x%Ix\n", lParam); - if (!(lParam & GCS_RESULTSTR)) - UpdateDefaultIMEWindow(hIMC, hwnd); -} - -static void DefaultIMEStartComposition(HIMC hIMC, HWND hwnd ) -{ - TRACE("IME message WM_IME_STARTCOMPOSITION\n"); - UpdateDefaultIMEWindow(hIMC, hwnd); -} - -static LRESULT ImeHandleNotify(HIMC hIMC, HWND hwnd, UINT msg, WPARAM wParam, - LPARAM lParam) -{ - switch (wParam) - { - case IMN_OPENSTATUSWINDOW: - FIXME("WM_IME_NOTIFY:IMN_OPENSTATUSWINDOW\n"); - break; - case IMN_CLOSESTATUSWINDOW: - FIXME("WM_IME_NOTIFY:IMN_CLOSESTATUSWINDOW\n"); - break; - case IMN_OPENCANDIDATE: - FIXME("WM_IME_NOTIFY:IMN_OPENCANDIDATE\n"); - break; - case IMN_CHANGECANDIDATE: - FIXME("WM_IME_NOTIFY:IMN_CHANGECANDIDATE\n"); - break; - case IMN_CLOSECANDIDATE: - FIXME("WM_IME_NOTIFY:IMN_CLOSECANDIDATE\n"); - break; - case IMN_SETCONVERSIONMODE: - FIXME("WM_IME_NOTIFY:IMN_SETCONVERSIONMODE\n"); - break; - case IMN_SETSENTENCEMODE: - FIXME("WM_IME_NOTIFY:IMN_SETSENTENCEMODE\n"); - break; - case IMN_SETOPENSTATUS: - TRACE("WM_IME_NOTIFY:IMN_SETOPENSTATUS\n"); - break; - case IMN_SETCANDIDATEPOS: - FIXME("WM_IME_NOTIFY:IMN_SETCANDIDATEPOS\n"); - break; - case IMN_SETCOMPOSITIONFONT: - FIXME("WM_IME_NOTIFY:IMN_SETCOMPOSITIONFONT\n"); - break; - case IMN_SETCOMPOSITIONWINDOW: - FIXME("WM_IME_NOTIFY:IMN_SETCOMPOSITIONWINDOW\n"); - break; - case IMN_GUIDELINE: - FIXME("WM_IME_NOTIFY:IMN_GUIDELINE\n"); - break; - case IMN_SETSTATUSWINDOWPOS: - FIXME("WM_IME_NOTIFY:IMN_SETSTATUSWINDOWPOS\n"); - break; - default: - FIXME("WM_IME_NOTIFY:\n",wParam); - break; - } - return 0; -} - -static LRESULT WINAPI IME_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, - LPARAM lParam) -{ - LRESULT rc = 0; - HIMC hIMC; - - TRACE("Incoming Message 0x%x (0x%08Ix, 0x%08Ix)\n", msg, wParam, lParam); - - /* - * Each UI window contains the current Input Context. - * This Input Context can be obtained by calling GetWindowLong - * with IMMGWL_IMC when the UI window receives a WM_IME_xxx message. - * The UI window can refer to this Input Context and handles the - * messages. - */ - - hIMC = (HIMC)GetWindowLongPtrW(hwnd,IMMGWL_IMC); - if (!hIMC) - hIMC = RealIMC(FROM_X11); - - /* if we have no hIMC there are many messages we cannot process */ - if (hIMC == NULL) - { - switch (msg) { - case WM_IME_STARTCOMPOSITION: - case WM_IME_ENDCOMPOSITION: - case WM_IME_COMPOSITION: - case WM_IME_NOTIFY: - case WM_IME_CONTROL: - case WM_IME_COMPOSITIONFULL: - case WM_IME_SELECT: - case WM_IME_CHAR: - return 0L; - default: - break; - } - } - - switch(msg) - { - case WM_CREATE: - { - LPIMEPRIVATE myPrivate; - LPINPUTCONTEXT lpIMC; - - SetWindowTextA(hwnd,"Wine Ime Active"); - - lpIMC = LockRealIMC(hIMC); - if (lpIMC) - { - myPrivate = ImmLockIMCC(lpIMC->hPrivate); - myPrivate->hwndDefault = hwnd; - ImmUnlockIMCC(lpIMC->hPrivate); - } - UnlockRealIMC(hIMC); - - return TRUE; - } - case WM_PAINT: - PaintDefaultIMEWnd(hIMC, hwnd); - return FALSE; - - case WM_NCCREATE: - return TRUE; - - case WM_SETFOCUS: - if (wParam) - SetFocus((HWND)wParam); - else - FIXME("Received focus, should never have focus\n"); - break; - case WM_IME_COMPOSITION: - DefaultIMEComposition(hIMC, hwnd, lParam); - break; - case WM_IME_STARTCOMPOSITION: - DefaultIMEStartComposition(hIMC, hwnd); - break; - case WM_IME_ENDCOMPOSITION: - TRACE("IME message %s, 0x%Ix, 0x%Ix\n", - "WM_IME_ENDCOMPOSITION", wParam, lParam); - ShowWindow(hwnd,SW_HIDE); - break; - case WM_IME_SELECT: - TRACE("IME message %s, 0x%Ix, 0x%Ix\n","WM_IME_SELECT", wParam, lParam); - break; - case WM_IME_CONTROL: - TRACE("IME message %s, 0x%Ix, 0x%Ix\n","WM_IME_CONTROL", wParam, lParam); - rc = 1; - break; - case WM_IME_NOTIFY: - rc = ImeHandleNotify(hIMC,hwnd,msg,wParam,lParam); - break; - default: - TRACE("Non-standard message 0x%x\n",msg); - } - /* check the MSIME messages */ - if (msg == WM_MSIME_SERVICE) - { - TRACE("IME message %s, 0x%Ix, 0x%Ix\n","WM_MSIME_SERVICE", wParam, lParam); - rc = FALSE; - } - else if (msg == WM_MSIME_RECONVERTOPTIONS) - { - TRACE("IME message %s, 0x%Ix, 0x%Ix\n","WM_MSIME_RECONVERTOPTIONS", wParam, lParam); - } - else if (msg == WM_MSIME_MOUSE) - { - TRACE("IME message %s, 0x%Ix, 0x%Ix\n","WM_MSIME_MOUSE", wParam, lParam); - } - else if (msg == WM_MSIME_RECONVERTREQUEST) - { - TRACE("IME message %s, 0x%Ix, 0x%Ix\n","WM_MSIME_RECONVERTREQUEST", wParam, lParam); - } - else if (msg == WM_MSIME_RECONVERT) - { - TRACE("IME message %s, 0x%Ix, 0x%Ix\n","WM_MSIME_RECONVERT", wParam, lParam); - } - else if (msg == WM_MSIME_QUERYPOSITION) - { - TRACE("IME message %s, 0x%Ix, 0x%Ix\n","WM_MSIME_QUERYPOSITION", wParam, lParam); - } - else if (msg == WM_MSIME_DOCUMENTFEED) - { - TRACE("IME message %s, 0x%Ix, 0x%Ix\n","WM_MSIME_DOCUMENTFEED", wParam, lParam); - } - /* DefWndProc if not an IME message */ - if (!rc && !((msg >= WM_IME_STARTCOMPOSITION && msg <= WM_IME_KEYLAST) || - (msg >= WM_IME_SETCONTEXT && msg <= WM_IME_KEYUP))) - rc = DefWindowProcW(hwnd,msg,wParam,lParam); - - return rc; -} diff --git a/dlls/winex11.drv/init.c b/dlls/winex11.drv/init.c index ef11461ea67..173822b455f 100644 --- a/dlls/winex11.drv/init.c +++ b/dlls/winex11.drv/init.c @@ -400,6 +400,8 @@ static const struct user_driver_funcs x11drv_funcs = .pMapVirtualKeyEx = X11DRV_MapVirtualKeyEx, .pToUnicodeEx = X11DRV_ToUnicodeEx, .pVkKeyScanEx = X11DRV_VkKeyScanEx, + .pImeToAsciiEx = X11DRV_ImeToAsciiEx, + .pNotifyIMEStatus = X11DRV_NotifyIMEStatus, .pDestroyCursorIcon = X11DRV_DestroyCursorIcon, .pSetCursor = X11DRV_SetCursor, .pGetCursorPos = X11DRV_GetCursorPos, @@ -409,7 +411,7 @@ static const struct user_driver_funcs x11drv_funcs = .pGetCurrentDisplaySettings = X11DRV_GetCurrentDisplaySettings, .pGetDisplayDepth = X11DRV_GetDisplayDepth, .pUpdateDisplayDevices = X11DRV_UpdateDisplayDevices, - .pCreateDesktopWindow = X11DRV_CreateDesktopWindow, + .pCreateDesktop = X11DRV_CreateDesktop, .pCreateWindow = X11DRV_CreateWindow, .pDesktopWindowProc = X11DRV_DesktopWindowProc, .pDestroyWindow = X11DRV_DestroyWindow, @@ -419,6 +421,7 @@ static const struct user_driver_funcs x11drv_funcs = .pReleaseDC = X11DRV_ReleaseDC, .pScrollDC = X11DRV_ScrollDC, .pSetCapture = X11DRV_SetCapture, + .pSetDesktopWindow = X11DRV_SetDesktopWindow, .pSetFocus = X11DRV_SetFocus, .pSetLayeredWindowAttributes = X11DRV_SetLayeredWindowAttributes, .pSetParent = X11DRV_SetParent, diff --git a/dlls/winex11.drv/keyboard.c b/dlls/winex11.drv/keyboard.c index b51546a6340..f476919f9f5 100644 --- a/dlls/winex11.drv/keyboard.c +++ b/dlls/winex11.drv/keyboard.c @@ -34,9 +34,7 @@ #include #include #include -#ifdef HAVE_X11_XKBLIB_H #include -#endif #include #include @@ -66,7 +64,6 @@ WINE_DECLARE_DEBUG_CHANNEL(key); static const unsigned int ControlMask = 1 << 2; static int min_keycode, max_keycode, keysyms_per_keycode; -static KeySym *key_mapping; static WORD keyc2vkey[256], keyc2scan[256]; static int NumLockMask, ScrollLockMask, AltGrMask; /* mask in the XKeyEvent state */ @@ -1089,14 +1086,6 @@ static const WORD xfree86_vendor_key_vkey[256] = 0, 0, 0, 0, 0, 0, 0, 0 /* 1008FFF8 */ }; -static inline KeySym keycode_to_keysym( Display *display, KeyCode keycode, int index ) -{ -#ifdef HAVE_XKB - if (use_xkb) return XkbKeycodeToKeysym(display, keycode, 0, index); -#endif - return key_mapping[(keycode - min_keycode) * keysyms_per_keycode + index]; -} - /* Returns the Windows virtual key code associated with the X event */ /* kbd_section must be held */ static WORD EVENT_event_to_vkey( XIC xic, XKeyEvent *e) @@ -1398,7 +1387,7 @@ BOOL X11DRV_KeyEvent( HWND hwnd, XEvent *xev ) if (status == XLookupChars) { - X11DRV_XIMLookupChars( Str, ascii_chars ); + xim_set_result_string( hwnd, Str, ascii_chars ); if (buf != Str) free( Str ); return TRUE; @@ -1511,7 +1500,7 @@ X11DRV_KEYBOARD_DetectLayout( Display *display ) for (keyc = min_keycode; keyc <= max_keycode; keyc++) { /* get data for keycode from X server */ for (i = 0; i < syms; i++) { - if (!(keysym = keycode_to_keysym (display, keyc, i))) continue; + if (!(keysym = XkbKeycodeToKeysym( display, keyc, 0, i ))) continue; ckey[keyc][i] = keysym_to_char(keysym); if (TRACE_ON(keyboard)) { @@ -1592,39 +1581,6 @@ X11DRV_KEYBOARD_DetectLayout( Display *display ) TRACE("detected layout is \"%s\"\n", main_key_tab[kbd_layout].comment); } -static HKL get_locale_kbd_layout(void) -{ - LCID layout; - LANGID langid; - - /* FIXME: - * - * layout = main_key_tab[kbd_layout].lcid; - * - * Winword uses return value of GetKeyboardLayout as a codepage - * to translate ANSI keyboard messages to unicode. But we have - * a problem with it: for instance Polish keyboard layout is - * identical to the US one, and therefore instead of the Polish - * locale id we return the US one. - */ - - NtQueryDefaultLocale( TRUE, &layout ); - - /* - * Microsoft Office expects this value to be something specific - * for Japanese and Korean Windows with an IME the value is 0xe001 - * We should probably check to see if an IME exists and if so then - * set this word properly. - */ - langid = PRIMARYLANGID(LANGIDFROMLCID(layout)); - if (langid == LANG_CHINESE || langid == LANG_JAPANESE || langid == LANG_KOREAN) - layout = MAKELONG( layout, 0xe001 ); /* IME */ - else - layout |= layout << 16; - - return (HKL)(UINT_PTR)layout; -} - /********************************************************************** * X11DRV_InitKeyboard @@ -1662,9 +1618,7 @@ void X11DRV_InitKeyboard( Display *display ) pthread_mutex_lock( &kbd_mutex ); XDisplayKeycodes(display, &min_keycode, &max_keycode); - if (key_mapping) XFree( key_mapping ); - key_mapping = XGetKeyboardMapping(display, min_keycode, - max_keycode + 1 - min_keycode, &keysyms_per_keycode); + XFree( XGetKeyboardMapping( display, min_keycode, max_keycode + 1 - min_keycode, &keysyms_per_keycode ) ); mmp = XGetModifierMapping(display); kcp = mmp->modifiermap; @@ -1678,12 +1632,12 @@ void X11DRV_InitKeyboard( Display *display ) int k; for (k = 0; k < keysyms_per_keycode; k += 1) - if (keycode_to_keysym(display, *kcp, k) == XK_Num_Lock) + if (XkbKeycodeToKeysym( display, *kcp, 0, k ) == XK_Num_Lock) { NumLockMask = 1 << i; TRACE_(key)("NumLockMask is %x\n", NumLockMask); } - else if (keycode_to_keysym(display, *kcp, k) == XK_Scroll_Lock) + else if (XkbKeycodeToKeysym( display, *kcp, 0, k ) == XK_Scroll_Lock) { ScrollLockMask = 1 << i; TRACE_(key)("ScrollLockMask is %x\n", ScrollLockMask); @@ -1735,7 +1689,7 @@ void X11DRV_InitKeyboard( Display *display ) /* we seem to need to search the layout-dependent scancodes */ int maxlen=0,maxval=-1,ok; for (i=0; icached_clip_hwnd) - { - ret = data->cached_clip_hwnd; - data->cached_clip_hwnd = NULL; - return ret; - } - RtlInitUnicodeString( &class_name, messageW ); - return NtUserCreateWindowEx( 0, &class_name, &class_name, NULL, 0, 0, 0, 0, 0, - HWND_MESSAGE, 0, NtCurrentTeb()->Peb->ImageBaseAddress, - NULL, 0, NULL, 0, FALSE ); -} - -static void release_clip_hwnd( HWND hwnd ) -{ - struct x11drv_thread_data *data = x11drv_thread_data(); - - if (data->cached_clip_hwnd) - NtUserDestroyWindow( data->cached_clip_hwnd ); - data->cached_clip_hwnd = hwnd; -} - /*********************************************************************** * X11DRV_Xcursor_Init * @@ -267,24 +237,6 @@ void set_window_cursor( Window window, HCURSOR handle ) XFlush( gdi_display ); } -/*********************************************************************** - * sync_window_cursor - */ -void sync_window_cursor( Window window ) -{ - HCURSOR cursor; - - SERVER_START_REQ( set_cursor ) - { - req->flags = 0; - wine_server_call( req ); - cursor = reply->prev_count >= 0 ? wine_server_ptr_handle( reply->prev_handle ) : 0; - } - SERVER_END_REQ; - - set_window_cursor( window, cursor ); -} - struct mouse_button_mapping { int deviceid; @@ -466,25 +418,30 @@ static BOOL grab_clipping_window( const RECT *clip ) #ifdef HAVE_X11_EXTENSIONS_XINPUT2_H struct x11drv_thread_data *data = x11drv_thread_data(); Window clip_window; - HWND msg_hwnd = 0; + HCURSOR cursor; POINT pos; RECT real_clip; - if (NtUserGetWindowThread( NtUserGetDesktopWindow(), NULL ) == GetCurrentThreadId()) - return TRUE; /* don't clip in the desktop process */ + /* don't clip in the desktop process */ + if (NtUserGetWindowThread( NtUserGetDesktopWindow(), NULL ) == GetCurrentThreadId()) return TRUE; + /* don't clip the cursor if the X input focus is on another process window */ + if (!is_current_process_focused()) return TRUE; if (!data) return FALSE; if (!(clip_window = init_clip_window())) return TRUE; - if (!(msg_hwnd = get_clip_hwnd())) - return TRUE; + if (keyboard_grabbed) + { + WARN( "refusing to clip to %s\n", wine_dbgstr_rect(clip) ); + return FALSE; + } /* enable XInput2 unless we are already clipping */ - if (!data->clip_hwnd) X11DRV_XInput2_Enable( data->display, None, PointerMotionMask ); + if (!data->clipping_cursor) X11DRV_XInput2_Enable( data->display, None, PointerMotionMask ); TRACE( "clipping to %s win %lx\n", wine_dbgstr_rect(clip), clip_window ); - if (!data->clip_hwnd) XUnmapWindow( data->display, clip_window ); + if (!data->clipping_cursor) XUnmapWindow( data->display, clip_window ); TRACE( "user clip rect %s\n", wine_dbgstr_rect( clip ) ); @@ -503,7 +460,7 @@ static BOOL grab_clipping_window( const RECT *clip ) XMapWindow( data->display, clip_window ); /* if the rectangle is shrinking we may get a pointer warp */ - if (!data->clip_hwnd || clip->left > clip_rect.left || clip->top > clip_rect.top || + if (!data->clipping_cursor || clip->left > clip_rect.left || clip->top > clip_rect.top || clip->right < clip_rect.right || clip->bottom < clip_rect.bottom) data->warp_serial = NextRequest( data->display ); @@ -512,17 +469,24 @@ static BOOL grab_clipping_window( const RECT *clip ) GrabModeAsync, GrabModeAsync, clip_window, None, CurrentTime )) clipping_cursor = TRUE; + SERVER_START_REQ( set_cursor ) + { + req->flags = 0; + wine_server_call( req ); + if (reply->prev_count < 0) cursor = 0; + else cursor = wine_server_ptr_handle( reply->prev_handle ); + } + SERVER_END_REQ; + + set_window_cursor( clip_window, cursor ); + if (!clipping_cursor) { X11DRV_XInput2_Enable( data->display, None, 0 ); - release_clip_hwnd( msg_hwnd ); return FALSE; } clip_rect = *clip; - if (!data->clip_hwnd) sync_window_cursor( clip_window ); - InterlockedExchangePointer( (void **)&cursor_window, msg_hwnd ); - data->clip_hwnd = msg_hwnd; - send_notify_message( NtUserGetDesktopWindow(), WM_X11DRV_CLIP_CURSOR_NOTIFY, 0, (LPARAM)msg_hwnd ); + data->clipping_cursor = TRUE; return TRUE; #else WARN( "XInput2 was not available at compile time\n" ); @@ -537,111 +501,36 @@ static BOOL grab_clipping_window( const RECT *clip ) */ void ungrab_clipping_window(void) { - Display *display = thread_init_display(); + struct x11drv_thread_data *data = x11drv_init_thread_data(); Window clip_window = init_clip_window(); if (!clip_window) return; TRACE( "no longer clipping\n" ); - XUnmapWindow( display, clip_window ); + XUnmapWindow( data->display, clip_window ); if (clipping_cursor) { - XUngrabPointer( display, CurrentTime ); - XFlush( display ); + XUngrabPointer( data->display, CurrentTime ); + XFlush( data->display ); } clipping_cursor = FALSE; - send_notify_message( NtUserGetDesktopWindow(), WM_X11DRV_CLIP_CURSOR_NOTIFY, 0, 0 ); -} + data->clipping_cursor = FALSE; -/*********************************************************************** - * reset_clipping_window - * - * Forcibly reset the window clipping on external events. - */ -void reset_clipping_window(void) -{ - ungrab_clipping_window(); - NtUserClipCursor( NULL ); /* make sure the clip rectangle is reset too */ -} - -/*********************************************************************** - * clip_cursor_notify - * - * Notification function called upon receiving a WM_X11DRV_CLIP_CURSOR_NOTIFY. - */ -LRESULT clip_cursor_notify( HWND hwnd, HWND prev_clip_hwnd, HWND new_clip_hwnd ) -{ - struct x11drv_thread_data *data = x11drv_init_thread_data(); - - if (hwnd == NtUserGetDesktopWindow()) /* change the clip window stored in the desktop process */ - { - static HWND clip_hwnd; - - HWND prev = clip_hwnd; - clip_hwnd = new_clip_hwnd; - if (prev || new_clip_hwnd) TRACE( "clip hwnd changed from %p to %p\n", prev, new_clip_hwnd ); - if (prev) send_notify_message( prev, WM_X11DRV_CLIP_CURSOR_NOTIFY, (WPARAM)prev, 0 ); - } - else if (hwnd == data->clip_hwnd) /* this is a notification that clipping has been reset */ - { - TRACE( "clip hwnd reset from %p\n", hwnd ); - data->clip_hwnd = 0; - data->clip_reset = NtGetTickCount(); + /* desktop window needs to listen to XInput2 events all the time for rawinput to work */ + if (NtUserGetWindowThread( NtUserGetDesktopWindow(), NULL ) != GetCurrentThreadId()) X11DRV_XInput2_Enable( data->display, None, 0 ); - release_clip_hwnd( hwnd ); - } - else if (prev_clip_hwnd) - { - /* This is a notification send by the desktop window to an old - * dangling clip window. - */ - TRACE( "destroying old clip hwnd %p\n", prev_clip_hwnd ); - release_clip_hwnd( prev_clip_hwnd ); - } - return 0; } /*********************************************************************** - * clip_fullscreen_window + * retry_grab_clipping_window * - * Turn on clipping if the active window is fullscreen. + * Restore the current clip rectangle. */ -BOOL clip_fullscreen_window( HWND hwnd, BOOL reset ) +void retry_grab_clipping_window(void) { - struct x11drv_win_data *data; - struct x11drv_thread_data *thread_data; - MONITORINFO monitor_info; - HMONITOR monitor; - DWORD style; - BOOL fullscreen; - - if (hwnd == NtUserGetDesktopWindow()) return FALSE; - style = NtUserGetWindowLongW( hwnd, GWL_STYLE ); - if (!(style & WS_VISIBLE)) return FALSE; - if ((style & (WS_POPUP | WS_CHILD)) == WS_CHILD) return FALSE; - /* maximized windows don't count as full screen */ - if ((style & WS_MAXIMIZE) && (style & WS_CAPTION) == WS_CAPTION) return FALSE; - if (!(data = get_win_data( hwnd ))) return FALSE; - fullscreen = NtUserIsWindowRectFullScreen( &data->whole_rect ); - release_win_data( data ); - if (!fullscreen) return FALSE; - if (!(thread_data = x11drv_thread_data())) return FALSE; - if (!reset) { - if (NtGetTickCount() - thread_data->clip_reset < 1000) return FALSE; - if (!reset && clipping_cursor && thread_data->clip_hwnd) return FALSE; /* already clipping */ - } - monitor = NtUserMonitorFromWindow( hwnd, MONITOR_DEFAULTTONEAREST ); - if (!monitor) return FALSE; - monitor_info.cbSize = sizeof(monitor_info); - if (!NtUserGetMonitorInfo( monitor, &monitor_info )) return FALSE; - if (!grab_fullscreen) - { - RECT virtual_rect = NtUserGetVirtualScreenRect(); - if (!EqualRect( &monitor_info.rcMonitor, &virtual_rect )) return FALSE; - if (is_virtual_desktop()) return FALSE; - } - TRACE( "win %p clipping fullscreen\n", hwnd ); - return grab_clipping_window( &monitor_info.rcMonitor ); + RECT rect; + NtUserGetClipCursor( &rect ); + NtUserClipCursor( &rect ); } @@ -676,7 +565,7 @@ static void map_event_coords( HWND hwnd, Window window, Window event_root, int x if (!hwnd) { thread_data = x11drv_thread_data(); - if (!thread_data->clip_hwnd) return; + if (!thread_data->clipping_cursor) return; if (thread_data->clip_window != window) return; pt.x = clip_rect.left; pt.y = clip_rect.top; @@ -722,43 +611,19 @@ static void map_event_coords( HWND hwnd, Window window, Window event_root, int x static void send_mouse_input( HWND hwnd, Window window, unsigned int state, INPUT *input ) { struct x11drv_win_data *data; - Window win = 0; input->type = INPUT_MOUSE; if (!hwnd) { struct x11drv_thread_data *thread_data = x11drv_thread_data(); - HWND clip_hwnd = thread_data->clip_hwnd; - - if (!clip_hwnd) return; - if (thread_data->clip_window != window) return; - if (InterlockedExchangePointer( (void **)&cursor_window, clip_hwnd ) != clip_hwnd || - input->u.mi.time - last_cursor_change > 100) - { - sync_window_cursor( window ); - last_cursor_change = input->u.mi.time; - } + if (!thread_data->clipping_cursor || thread_data->clip_window != window) return; __wine_send_input( hwnd, input, NULL ); return; } if (!(data = get_win_data( hwnd ))) return; - win = data->whole_window; release_win_data( data ); - if (InterlockedExchangePointer( (void **)&cursor_window, hwnd ) != hwnd || - input->u.mi.time - last_cursor_change > 100) - { - sync_window_cursor( win ); - last_cursor_change = input->u.mi.time; - } - - if (hwnd != NtUserGetDesktopWindow()) - { - hwnd = NtUserGetAncestor( hwnd, GA_ROOT ); - if ((input->u.mi.dwFlags & (MOUSEEVENTF_LEFTDOWN|MOUSEEVENTF_RIGHTDOWN)) && hwnd == NtUserGetForegroundWindow()) - clip_fullscreen_window( hwnd, FALSE ); - } /* update the wine server Z-order */ @@ -1577,15 +1442,17 @@ void X11DRV_DestroyCursorIcon( HCURSOR handle ) /*********************************************************************** * SetCursor (X11DRV.@) */ -void X11DRV_SetCursor( HCURSOR handle ) +void X11DRV_SetCursor( HWND hwnd, HCURSOR handle ) { - if (InterlockedExchangePointer( (void **)&last_cursor, handle ) != handle || - NtGetTickCount() - last_cursor_change > 100) + struct x11drv_win_data *data; + + if ((data = get_win_data( hwnd ))) { - last_cursor_change = NtGetTickCount(); - if (cursor_window) send_notify_message( cursor_window, WM_X11DRV_SET_CURSOR, - GetCurrentThreadId(), (LPARAM)handle ); + set_window_cursor( data->whole_window, handle ); + release_win_data( data ); } + + if (clipping_cursor) set_window_cursor( x11drv_thread_data()->clip_window, handle ); } /*********************************************************************** @@ -1596,11 +1463,18 @@ BOOL X11DRV_SetCursorPos( INT x, INT y ) struct x11drv_thread_data *data = x11drv_init_thread_data(); POINT pos = virtual_screen_to_root( x, y ); + if (keyboard_grabbed) + { + WARN( "refusing to warp to %u, %u\n", (int)pos.x, (int)pos.y ); + return FALSE; + } + TRACE( "real setting to %s\n", wine_dbgstr_point( &pos ) ); + pXFixesHideCursor( data->display, root_window ); XWarpPointer( data->display, root_window, root_window, 0, 0, 0, 0, pos.x, pos.y ); data->warp_serial = NextRequest( data->display ); - XNoOp( data->display ); + pXFixesShowCursor( data->display, root_window ); XFlush( data->display ); /* avoids bad mouse lag in games that do their own mouse warping */ TRACE( "warped to (fake) %d,%d serial %lu\n", x, y, data->warp_serial ); return TRUE; @@ -1634,77 +1508,13 @@ BOOL X11DRV_GetCursorPos(LPPOINT pos) /*********************************************************************** * ClipCursor (X11DRV.@) */ -BOOL X11DRV_ClipCursor( LPCRECT clip ) +BOOL X11DRV_ClipCursor( const RECT *clip, BOOL reset ) { - RECT virtual_rect = NtUserGetVirtualScreenRect(); - - if (!clip) clip = &virtual_rect; - - if (grab_pointer) - { - HWND foreground = NtUserGetForegroundWindow(); - DWORD tid, pid; - - if (foreground == NtUserGetDesktopWindow()) - { - WARN( "desktop is foreground, ignoring ClipCursor\n" ); - ungrab_clipping_window(); - return TRUE; - } - - /* forward request to the foreground window if it's in a different thread */ - tid = NtUserGetWindowThread( foreground, &pid ); - if (tid && tid != GetCurrentThreadId() && pid == GetCurrentProcessId()) - { - TRACE( "forwarding clip request to %p\n", foreground ); - send_notify_message( foreground, WM_X11DRV_CLIP_CURSOR_REQUEST, FALSE, FALSE ); - return TRUE; - } - - /* we are clipping if the clip rectangle is smaller than the screen */ - if (clip->left > virtual_rect.left || clip->right < virtual_rect.right || - clip->top > virtual_rect.top || clip->bottom < virtual_rect.bottom) - { - if (grab_clipping_window( clip )) return TRUE; - } - else /* check if we should switch to fullscreen clipping */ - { - struct x11drv_thread_data *data = x11drv_thread_data(); - if (data) - { - if ((data->clip_hwnd && EqualRect( clip, &clip_rect ) && !EqualRect(&clip_rect, &virtual_rect)) || clip_fullscreen_window( foreground, TRUE )) - return TRUE; - } - } - } + if (!reset && clip && grab_clipping_window( clip )) return TRUE; ungrab_clipping_window(); return TRUE; } -/*********************************************************************** - * clip_cursor_request - * - * Function called upon receiving a WM_X11DRV_CLIP_CURSOR_REQUEST. - */ -LRESULT clip_cursor_request( HWND hwnd, BOOL fullscreen, BOOL reset ) -{ - RECT clip; - - if (hwnd == NtUserGetDesktopWindow()) - WARN( "ignoring clip cursor request on desktop window.\n" ); - else if (hwnd != NtUserGetForegroundWindow()) - WARN( "ignoring clip cursor request on non-foreground window.\n" ); - else if (fullscreen) - clip_fullscreen_window( hwnd, reset ); - else - { - NtUserGetClipCursor( &clip ); - X11DRV_ClipCursor( &clip ); - } - - return 0; -} - /*********************************************************************** * move_resize_window */ @@ -1864,7 +1674,7 @@ BOOL X11DRV_MotionNotify( HWND hwnd, XEvent *xev ) input.u.mi.time = x11drv_time_to_ticks( event->time ); input.u.mi.dwExtraInfo = 0; - if (!hwnd && is_old_motion_event( event->serial )) + if (is_old_motion_event( event->serial )) { TRACE( "pos %d,%d old serial %lu, ignoring\n", event->x, event->y, event->serial ); return FALSE; @@ -2125,7 +1935,7 @@ static BOOL X11DRV_XIDeviceEvent( XIDeviceEvent *event ) TRACE( "evtype %u hwnd %p/%lx pos %f,%f detail %u flags %#x serial %lu\n", event->evtype, hwnd, event->event, event->event_x, event->event_y, event->detail, event->flags, event->serial ); - if (!hwnd && is_old_motion_event( event->serial )) + if (is_old_motion_event( event->serial )) { TRACE( "pos %f,%f old serial %lu, ignoring\n", event->event_x, event->event_y, event->serial ); return FALSE; diff --git a/dlls/winex11.drv/opengl.c b/dlls/winex11.drv/opengl.c index 3cf88a7d4bc..0380aa4cc63 100644 --- a/dlls/winex11.drv/opengl.c +++ b/dlls/winex11.drv/opengl.c @@ -1522,21 +1522,7 @@ static struct gl_drawable *create_gl_drawable( HWND hwnd, const struct wgl_pixel gl->layered_type = get_gl_layered_type( hwnd ); - if (gl->layered_type) - { - detach_client_window( hwnd, 0 ); - gl->type = DC_GL_PIXMAP_WIN; - gl->pixmap = XCreatePixmap( gdi_display, root_window, width, height, visual->depth ); - if (gl->pixmap) - { - gl->drawable = pglXCreatePixmap( gdi_display, gl->format->fbconfig, gl->pixmap, NULL ); - if (!gl->drawable) XFreePixmap( gdi_display, gl->pixmap ); - gl->pixmap_size.cx = width; - gl->pixmap_size.cy = height; - } - TRACE( "%p created pixmap drawable %lx for layered window, type %u.\n", hwnd, gl->drawable, gl->layered_type ); - } - else if (!drawable_needs_clipping( hwnd, known_child )) /* childless top-level window */ + if (!gl->layered_type && !drawable_needs_clipping( hwnd, known_child )) /* childless top-level window */ { struct x11drv_win_data *data; @@ -1566,6 +1552,7 @@ static struct gl_drawable *create_gl_drawable( HWND hwnd, const struct wgl_pixel gl->fs_hack = data->fs_hack || fs_hack_get_gamma_ramp( NULL ); if (gl->fs_hack) TRACE( "Window %p has the fullscreen hack enabled\n", hwnd ); release_win_data( data ); + if (gl->layered_type) detach_client_window( hwnd, 0 ); TRACE( "%p created child %lx drawable %lx\n", hwnd, gl->window, gl->drawable ); } #endif @@ -1690,7 +1677,7 @@ void sync_gl_drawable( HWND hwnd, BOOL known_child ) known_child = drawable_needs_clipping( hwnd, known_child ); - if (old->type == DC_GL_PIXMAP_WIN || (known_child && old->type == DC_GL_WINDOW) + if (old->layered_type || (known_child && old->type == DC_GL_WINDOW) || (!known_child && old->type != DC_GL_WINDOW) || old->layered_type != new_layered_type) { @@ -3313,6 +3300,11 @@ static BOOL glxdrv_wglShareLists(struct wgl_context *org, struct wgl_context *de return FALSE; } +static int XGetImage_handler( Display *dpy, XErrorEvent *event, void *arg ) +{ + return event->request_code == X_GetImage && event->error_code == BadMatch; +} + static void update_window_surface(struct gl_drawable *gl, HWND hwnd) { char buffer[FIELD_OFFSET( BITMAPINFO, bmiColors[256] )]; @@ -3326,7 +3318,7 @@ static void update_window_surface(struct gl_drawable *gl, HWND hwnd) TRACE( "gl %p, hwnd %p, gl->layered_type %u.\n", gl, hwnd, gl->layered_type ); - if (gl->layered_type != DC_GL_LAYERED_ATTRIBUTES || !gl->pixmap) return; + if (gl->layered_type != DC_GL_LAYERED_ATTRIBUTES || !gl->window) return; if (!(data = get_win_data( hwnd ))) return; @@ -3347,11 +3339,18 @@ static void update_window_surface(struct gl_drawable *gl, HWND hwnd) rect.right = min( rect.right, abs( bmi->bmiHeader.biWidth )); rect.bottom = min( rect.bottom, abs( bmi->bmiHeader.biHeight )); - width = min( rect.right - rect.left, gl->pixmap_size.cx ); - height = min( rect.bottom - rect.top, gl->pixmap_size.cy ); + width = rect.right - rect.left; + height = rect.bottom - rect.top; - image = XGetImage( gdi_display, gl->pixmap, 0, 0, width, height, + TRACE( "client_rect %s, whole_rect %s bmi %dx%d, rect %s.\n", + wine_dbgstr_rect(&data->client_rect), wine_dbgstr_rect(&data->whole_rect), + (int)bmi->bmiHeader.biWidth, (int)bmi->bmiHeader.biHeight, + wine_dbgstr_rect(&rect) ); + + X11DRV_expect_error( gdi_display, XGetImage_handler, NULL ); + image = XGetImage( gdi_display, gl->window, 0, 0, width, height, AllPlanes, ZPixmap ); + if (X11DRV_check_error()) ERR( "XGetImage error.\n" ); if (!image) { TRACE( "NULL image.\n" ); @@ -3373,7 +3372,6 @@ static void update_window_surface(struct gl_drawable *gl, HWND hwnd) for (y = 0; y < height; ++y) memcpy( dst_bits + (y + rect.top) * pitch + rect.left * stride, src_bits + y * image->bytes_per_line, width * stride ); - add_bounds_rect( surface->funcs->get_bounds( surface ), &rect ); done: @@ -3398,7 +3396,7 @@ static void wglFinish(void) switch (gl->type) { case DC_GL_PIXMAP_WIN: if (!gl->layered_type) escape.drawable = gl->pixmap; break; - case DC_GL_CHILD_WIN: escape.drawable = gl->window; break; + case DC_GL_CHILD_WIN: if (!gl->layered_type) escape.drawable = gl->window; break; default: break; } sync_context(ctx); @@ -3439,7 +3437,7 @@ static void wglFlush(void) switch (gl->type) { case DC_GL_PIXMAP_WIN: if (!gl->layered_type) escape.drawable = gl->pixmap; break; - case DC_GL_CHILD_WIN: escape.drawable = gl->window; break; + case DC_GL_CHILD_WIN: if (!gl->layered_type) escape.drawable = gl->window; break; default: break; } sync_context(ctx); @@ -3486,7 +3484,7 @@ static const GLubyte *wglGetString(GLenum name) if ((sz = read(fd, buffer, sizeof(buffer) - 1)) > 0) { buffer[sz] = 0; - if (strstr(buffer, "\\Paradox Launcher.exe")) + if (strstr(buffer, "\\Paradox Launcher.exe") || strstr(buffer, "Red Tie Runner.exe")) { FIXME("HACK: overriding GL vendor and renderer.\n"); override_vendor = 1; @@ -4893,7 +4891,7 @@ static BOOL glxdrv_wglSwapBuffers( HDC hdc ) case DC_GL_WINDOW: case DC_GL_CHILD_WIN: if (ctx) sync_context( ctx ); - if (gl->type == DC_GL_CHILD_WIN) escape.drawable = gl->window; + if (gl->type == DC_GL_CHILD_WIN && !gl->layered_type) escape.drawable = gl->window; /* fall through */ default: if (gl->fs_hack) @@ -4908,7 +4906,7 @@ static BOOL glxdrv_wglSwapBuffers( HDC hdc ) ctx->fs_hack = FALSE; fs_hack_setup_context( ctx, gl ); } - if (escape.drawable && pglXSwapBuffersMscOML) + if ((escape.drawable || gl->layered_type) && pglXSwapBuffersMscOML) { pglFlush(); target_sbc = pglXSwapBuffersMscOML( gdi_display, gl->drawable, 0, 0, 0 ); @@ -4918,7 +4916,7 @@ static BOOL glxdrv_wglSwapBuffers( HDC hdc ) break; } - if (escape.drawable && pglXWaitForSbcOML) + if ((escape.drawable || gl->layered_type) && pglXWaitForSbcOML) pglXWaitForSbcOML( gdi_display, gl->drawable, target_sbc, &ust, &msc, &sbc ); update_window_surface( gl, hwnd ); diff --git a/dlls/winex11.drv/unixlib.h b/dlls/winex11.drv/unixlib.h index 7dc1d9f0ca7..7dc911d6846 100644 --- a/dlls/winex11.drv/unixlib.h +++ b/dlls/winex11.drv/unixlib.h @@ -21,7 +21,6 @@ enum x11drv_funcs { - unix_create_desktop, unix_init, unix_systray_clear, unix_systray_dock, @@ -31,25 +30,18 @@ enum x11drv_funcs unix_tablet_get_packet, unix_tablet_info, unix_tablet_load_info, - unix_xim_preedit_state, - unix_xim_reset, + unix_input_thread, unix_funcs_count, }; #define X11DRV_CALL(func, params) WINE_UNIX_CALL( unix_ ## func, params ) -/* x11drv_create_desktop params */ -struct create_desktop_params -{ - UINT width; - UINT height; -}; - /* x11drv_init params */ struct init_params { WNDPROC foreign_window_proc; BOOL *show_systray; + BOOL input_thread_hack; }; struct systray_dock_params @@ -83,8 +75,6 @@ enum x11drv_client_funcs client_func_dnd_enter_event, client_func_dnd_position_event, client_func_dnd_post_drop, - client_func_ime_set_composition_string, - client_func_ime_set_result, client_func_systray_change_owner, client_func_last }; @@ -96,11 +86,6 @@ enum client_callback { client_dnd_drop_event, client_dnd_leave_event, - client_ime_get_cursor_pos, - client_ime_set_composition_status, - client_ime_set_cursor_pos, - client_ime_set_open_status, - client_ime_update_association, client_funcs_count }; diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c index 2521625180b..4940ddc7804 100644 --- a/dlls/winex11.drv/window.c +++ b/dlls/winex11.drv/window.c @@ -446,11 +446,10 @@ static int get_window_attributes( struct x11drv_win_data *data, XSetWindowAttrib attr->bit_gravity = NorthWestGravity; attr->backing_store = NotUseful; attr->border_pixel = 0; - attr->event_mask = (ExposureMask | PointerMotionMask | - ButtonPressMask | ButtonReleaseMask | EnterWindowMask | - KeyPressMask | KeyReleaseMask | FocusChangeMask | - KeymapStateMask | StructureNotifyMask); + attr->event_mask = (ExposureMask | FocusChangeMask | StructureNotifyMask | + PointerMotionMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask); if (data->managed) attr->event_mask |= PropertyChangeMask; + if (!input_thread_hack) attr->event_mask |= KeyPressMask | KeyReleaseMask | KeymapStateMask; return (CWOverrideRedirect | CWSaveUnder | CWColormap | CWBorderPixel | CWEventMask | CWBitGravity | CWBackingStore); @@ -1189,7 +1188,7 @@ static void update_net_wm_fullscreen_monitors( struct x11drv_win_data *data ) if (!(data->net_wm_state & (1 << NET_WM_STATE_FULLSCREEN)) || is_virtual_desktop()) return; - /* If the current display device handler can not detect dynamic device changes, do not use + /* If the current display device handler cannot detect dynamic device changes, do not use * _NET_WM_FULLSCREEN_MONITORS because xinerama_get_fullscreen_monitors() may report wrong * indices because of stale xinerama monitor information */ if (!X11DRV_DisplayDevices_SupportEventHandlers()) @@ -2080,8 +2079,6 @@ static void create_whole_window( struct x11drv_win_data *data ) XFlush( data->display ); /* make sure the window exists before we start painting to it */ - sync_window_cursor( data->whole_window ); - done: if (win_rgn) NtGdiDeleteObjectApp( win_rgn ); } @@ -2286,13 +2283,13 @@ BOOL X11DRV_DestroyNotify( HWND hwnd, XEvent *event ) /* initialize the desktop window id in the desktop manager process */ -BOOL create_desktop_win_data( Window win ) +static BOOL create_desktop_win_data( Window win, HWND hwnd ) { struct x11drv_thread_data *thread_data = x11drv_thread_data(); Display *display = thread_data->display; struct x11drv_win_data *data; - if (!(data = alloc_win_data( display, NtUserGetDesktopWindow() ))) return FALSE; + if (!(data = alloc_win_data( display, hwnd ))) return FALSE; data->whole_window = win; data->managed = TRUE; NtUserSetProp( data->hwnd, whole_window_prop, (HANDLE)win ); @@ -2303,9 +2300,9 @@ BOOL create_desktop_win_data( Window win ) } /********************************************************************** - * CreateDesktopWindow (X11DRV.@) + * SetDesktopWindow (X11DRV.@) */ -BOOL X11DRV_CreateDesktopWindow( HWND hwnd ) +void X11DRV_SetDesktopWindow( HWND hwnd ) { unsigned int width, height; @@ -2324,7 +2321,10 @@ BOOL X11DRV_CreateDesktopWindow( HWND hwnd ) if (!width && !height) /* not initialized yet */ { - RECT rect = NtUserGetVirtualScreenRect(); + RECT rect; + + X11DRV_DisplayDevices_Init( TRUE ); + rect = NtUserGetVirtualScreenRect(); SERVER_START_REQ( set_window_pos ) { @@ -2339,13 +2339,30 @@ BOOL X11DRV_CreateDesktopWindow( HWND hwnd ) wine_server_call( req ); } SERVER_END_REQ; + + if (!is_virtual_desktop()) return; + if (!create_desktop_win_data( root_window, hwnd )) + { + ERR( "Failed to create virtual desktop window data\n" ); + root_window = DefaultRootWindow( gdi_display ); + } + else if (is_desktop_fullscreen()) + { + Display *display = x11drv_thread_data()->display; + TRACE("setting desktop to fullscreen\n"); + XChangeProperty( display, root_window, x11drv_atom(_NET_WM_STATE), XA_ATOM, 32, PropModeReplace, + (unsigned char*)&x11drv_atom(_NET_WM_STATE_FULLSCREEN), 1 ); + } } else { Window win = (Window)NtUserGetProp( hwnd, whole_window_prop ); - if (win && win != root_window) X11DRV_init_desktop( win, width, height ); + if (win && win != root_window) + { + X11DRV_init_desktop( win, width, height ); + X11DRV_DisplayDevices_Init( TRUE ); + } } - return TRUE; } @@ -2502,7 +2519,7 @@ HWND create_foreign_window( Display *display, Window xwin ) unsigned int nchildren; XWindowAttributes attr; UINT style = WS_CLIPCHILDREN; - UNICODE_STRING class_name; + UNICODE_STRING class_name = RTL_CONSTANT_STRING( classW ); if (!class_registered) { @@ -2513,7 +2530,6 @@ HWND create_foreign_window( Display *display, Window xwin ) class.cbSize = sizeof(class); class.lpfnWndProc = client_foreign_window_proc; class.lpszClassName = classW; - RtlInitUnicodeString( &class_name, classW ); if (!NtUserRegisterClassExWOW( &class, &class_name, &version, NULL, 0, 0, NULL ) && RtlGetLastWin32Error() != ERROR_CLASS_ALREADY_EXISTS) { @@ -2756,28 +2772,6 @@ Window X11DRV_get_whole_window( HWND hwnd ) } -/*********************************************************************** - * X11DRV_get_ic - * - * Return the X input context associated with a window - */ -XIC X11DRV_get_ic( HWND hwnd ) -{ - struct x11drv_win_data *data = get_win_data( hwnd ); - XIM xim; - XIC ret = 0; - - if (data) - { - x11drv_thread_data()->last_xic_hwnd = hwnd; - ret = data->xic; - if (!ret && (xim = x11drv_thread_data()->xim)) ret = X11DRV_CreateIC( xim, data ); - release_win_data( data ); - } - return ret; -} - - /*********************************************************************** * X11DRV_GetDC (X11DRV.@) */ @@ -3026,7 +3020,7 @@ static void window_update_fshack( struct x11drv_win_data *data, const RECT *wind client_rect_host.right = min( max( client_rect_host.right, 1 ), 65535 ); client_rect_host.bottom = min( max( client_rect_host.bottom, 1 ), 65535 ); - XMoveResizeWindow( gdi_display, data->client_window, top_left.x, top_left.y, client_rect_host.right, client_rect_host.bottom ); + XMoveResizeWindow( data->display, data->client_window, top_left.x, top_left.y, client_rect_host.right, client_rect_host.bottom ); sync_gl_drawable( data->hwnd, !data->whole_window ); invalidate_vk_surfaces( data->hwnd ); @@ -3268,7 +3262,7 @@ void X11DRV_WindowPosChanged( HWND hwnd, HWND insert_after, UINT swp_flags, { release_win_data( data ); unmap_window( hwnd ); - if (NtUserIsWindowRectFullScreen( &old_window_rect )) reset_clipping_window(); + if (NtUserIsWindowRectFullScreen( &old_window_rect )) NtUserClipCursor( NULL ); if (!(data = get_win_data( hwnd ))) return; } } @@ -3349,8 +3343,8 @@ void X11DRV_WindowPosChanged( HWND hwnd, HWND insert_after, UINT swp_flags, /* check if the window icon should be hidden (i.e. moved off-screen) */ static BOOL hide_icon( struct x11drv_win_data *data ) { - static const WCHAR trayW[] = {'S','h','e','l','l','_','T','r','a','y','W','n','d'}; - UNICODE_STRING str = { sizeof(trayW), sizeof(trayW), (WCHAR *)trayW }; + static const WCHAR trayW[] = {'S','h','e','l','l','_','T','r','a','y','W','n','d',0}; + UNICODE_STRING str = RTL_CONSTANT_STRING( trayW ); if (data->managed) return TRUE; /* hide icons in desktop mode when the taskbar is active */ @@ -3715,38 +3709,11 @@ LRESULT X11DRV_WindowMessage( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp ) release_win_data( data ); } return 0; - case WM_X11DRV_SET_CURSOR: - { - Window win = 0; - - if ((data = get_win_data( hwnd ))) - { - win = data->whole_window; - release_win_data( data ); - } - else if (hwnd == x11drv_thread_data()->clip_hwnd) - win = x11drv_thread_data()->clip_window; - - if (win) - { - if (wp == GetCurrentThreadId()) - set_window_cursor( win, (HCURSOR)lp ); - else - sync_window_cursor( win ); - } - return 0; - } - case WM_X11DRV_CLIP_CURSOR_NOTIFY: - return clip_cursor_notify( hwnd, (HWND)wp, (HWND)lp ); - case WM_X11DRV_CLIP_CURSOR_REQUEST: - return clip_cursor_request( hwnd, (BOOL)wp, (BOOL)lp ); case WM_X11DRV_DELETE_TAB: taskbar_delete_tab( hwnd ); return 0; case WM_X11DRV_ADD_TAB: taskbar_add_tab( hwnd ); - case WM_X11DRV_RELEASE_CURSOR: - ungrab_clipping_window(); return 0; default: FIXME( "got window msg %x hwnd %p wp %lx lp %lx\n", msg, hwnd, (long)wp, lp ); diff --git a/dlls/winex11.drv/winex11.drv.spec b/dlls/winex11.drv/winex11.drv.spec index 77e4a6285de..6dedae550e8 100644 --- a/dlls/winex11.drv/winex11.drv.spec +++ b/dlls/winex11.drv/winex11.drv.spec @@ -4,26 +4,5 @@ @ cdecl LoadTabletInfo(long) X11DRV_LoadTabletInfo @ cdecl WTInfoW(long long ptr) X11DRV_WTInfoW -# Desktop -@ cdecl wine_create_desktop(long long) - # System tray @ cdecl wine_notify_icon(long ptr) - -#IME Interface -@ stdcall ImeInquire(ptr ptr wstr) -@ stdcall ImeConfigure(long long long ptr) -@ stdcall ImeDestroy(long) -@ stdcall ImeEscape(long long ptr) -@ stdcall ImeSelect(long long) -@ stdcall ImeSetActiveContext(long long) -@ stdcall ImeToAsciiEx(long long ptr ptr long long) -@ stdcall NotifyIME(long long long long) -@ stdcall ImeRegisterWord(wstr long wstr) -@ stdcall ImeUnregisterWord(wstr long wstr) -@ stdcall ImeEnumRegisterWord(ptr wstr long wstr ptr) -@ stdcall ImeSetCompositionString(long long ptr long ptr long) -@ stdcall ImeConversionList(long wstr ptr long long) -@ stdcall ImeProcessKey(long long long ptr) -@ stdcall ImeGetRegisterWordStyle(long ptr) -@ stdcall ImeGetImeMenuItems(long long long ptr ptr long) diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h index f1ded52c2b8..37112b80903 100644 --- a/dlls/winex11.drv/x11drv.h +++ b/dlls/winex11.drv/x11drv.h @@ -210,18 +210,21 @@ extern INT X11DRV_GetKeyNameText( LONG lparam, LPWSTR buffer, INT size ) DECLSPE extern UINT X11DRV_MapVirtualKeyEx( UINT code, UINT map_type, HKL hkl ) DECLSPEC_HIDDEN; extern INT X11DRV_ToUnicodeEx( UINT virtKey, UINT scanCode, const BYTE *lpKeyState, LPWSTR bufW, int bufW_size, UINT flags, HKL hkl ) DECLSPEC_HIDDEN; +extern UINT X11DRV_ImeToAsciiEx( UINT vkey, UINT vsc, const BYTE *state, + COMPOSITIONSTRING *compstr, HIMC himc ) DECLSPEC_HIDDEN; extern SHORT X11DRV_VkKeyScanEx( WCHAR wChar, HKL hkl ) DECLSPEC_HIDDEN; +extern void X11DRV_NotifyIMEStatus( HWND hwnd, UINT status ) DECLSPEC_HIDDEN; extern void X11DRV_DestroyCursorIcon( HCURSOR handle ) DECLSPEC_HIDDEN; -extern void X11DRV_SetCursor( HCURSOR handle ) DECLSPEC_HIDDEN; +extern void X11DRV_SetCursor( HWND hwnd, HCURSOR handle ) DECLSPEC_HIDDEN; extern BOOL X11DRV_SetCursorPos( INT x, INT y ) DECLSPEC_HIDDEN; extern BOOL X11DRV_GetCursorPos( LPPOINT pos ) DECLSPEC_HIDDEN; -extern BOOL X11DRV_ClipCursor( LPCRECT clip ) DECLSPEC_HIDDEN; +extern BOOL X11DRV_ClipCursor( const RECT *clip, BOOL reset ) DECLSPEC_HIDDEN; extern LONG X11DRV_ChangeDisplaySettings( LPDEVMODEW displays, LPCWSTR primary_name, HWND hwnd, DWORD flags, LPVOID lpvoid ) DECLSPEC_HIDDEN; extern BOOL X11DRV_GetCurrentDisplaySettings( LPCWSTR name, BOOL is_primary, LPDEVMODEW devmode ) DECLSPEC_HIDDEN; extern INT X11DRV_GetDisplayDepth( LPCWSTR name, BOOL is_primary ) DECLSPEC_HIDDEN; extern BOOL X11DRV_UpdateDisplayDevices( const struct gdi_device_manager *device_manager, BOOL force, void *param ) DECLSPEC_HIDDEN; -extern BOOL X11DRV_CreateDesktopWindow( HWND hwnd ) DECLSPEC_HIDDEN; +extern BOOL X11DRV_CreateDesktop( const WCHAR *name, UINT width, UINT height ) DECLSPEC_HIDDEN; extern BOOL X11DRV_CreateWindow( HWND hwnd ) DECLSPEC_HIDDEN; extern LRESULT X11DRV_DesktopWindowProc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp ) DECLSPEC_HIDDEN; extern void X11DRV_DestroyWindow( HWND hwnd ) DECLSPEC_HIDDEN; @@ -231,6 +234,7 @@ extern void X11DRV_GetDC( HDC hdc, HWND hwnd, HWND top, const RECT *win_rect, extern void X11DRV_ReleaseDC( HWND hwnd, HDC hdc ) DECLSPEC_HIDDEN; extern BOOL X11DRV_ScrollDC( HDC hdc, INT dx, INT dy, HRGN update ) DECLSPEC_HIDDEN; extern void X11DRV_SetCapture( HWND hwnd, UINT flags ) DECLSPEC_HIDDEN; +extern void X11DRV_SetDesktopWindow( HWND hwnd ) DECLSPEC_HIDDEN; extern void X11DRV_SetLayeredWindowAttributes( HWND hwnd, COLORREF key, BYTE alpha, DWORD flags ) DECLSPEC_HIDDEN; extern void X11DRV_SetParent( HWND hwnd, HWND parent, HWND old_parent ) DECLSPEC_HIDDEN; @@ -389,8 +393,7 @@ struct x11drv_thread_data Window selection_wnd; /* window used for selection interactions */ unsigned long warp_serial; /* serial number of last pointer warp request */ Window clip_window; /* window used for cursor clipping */ - HWND clip_hwnd; /* message window stored in desktop while clipping is active */ - DWORD clip_reset; /* time when clipping was last reset */ + BOOL clipping_cursor; /* whether thread is currently clipping the cursor */ #ifdef HAVE_X11_EXTENSIONS_XINPUT2_H XIValuatorClassInfo x_valuator; XIValuatorClassInfo y_valuator; @@ -399,7 +402,6 @@ struct x11drv_thread_data int xi2_active_touches; int xi2_primary_touchid; #endif /* HAVE_X11_EXTENSIONS_XINPUT2_H */ - HWND cached_clip_hwnd; }; extern struct x11drv_thread_data *x11drv_init_thread_data(void) DECLSPEC_HIDDEN; @@ -436,16 +438,14 @@ extern Colormap default_colormap DECLSPEC_HIDDEN; extern XPixmapFormatValues **pixmap_formats DECLSPEC_HIDDEN; extern Window root_window DECLSPEC_HIDDEN; extern BOOL clipping_cursor DECLSPEC_HIDDEN; +extern BOOL keyboard_grabbed DECLSPEC_HIDDEN; extern unsigned int screen_bpp DECLSPEC_HIDDEN; -extern BOOL use_xkb DECLSPEC_HIDDEN; extern BOOL usexrandr DECLSPEC_HIDDEN; extern BOOL usexvidmode DECLSPEC_HIDDEN; -extern BOOL ximInComposeMode DECLSPEC_HIDDEN; extern BOOL use_take_focus DECLSPEC_HIDDEN; extern BOOL use_primary_selection DECLSPEC_HIDDEN; extern BOOL use_system_cursors DECLSPEC_HIDDEN; extern BOOL show_systray DECLSPEC_HIDDEN; -extern BOOL grab_pointer DECLSPEC_HIDDEN; extern BOOL grab_fullscreen DECLSPEC_HIDDEN; extern BOOL usexcomposite DECLSPEC_HIDDEN; extern BOOL use_xfixes DECLSPEC_HIDDEN; @@ -460,7 +460,7 @@ extern int xrender_error_base DECLSPEC_HIDDEN; extern int xfixes_event_base DECLSPEC_HIDDEN; extern char *process_name DECLSPEC_HIDDEN; extern Display *clipboard_display DECLSPEC_HIDDEN; -extern WNDPROC client_foreign_window_proc; +extern WNDPROC client_foreign_window_proc DECLSPEC_HIDDEN; extern HANDLE steam_overlay_event DECLSPEC_HIDDEN; extern HANDLE steam_keyboard_event DECLSPEC_HIDDEN; @@ -598,18 +598,17 @@ extern void (*pXFreeEventData)( Display *display, XEvent /*XGenericEventCookie*/ extern DWORD x11drv_time_to_ticks(Time time) DECLSPEC_HIDDEN; +extern void x11drv_input_add_window( HWND hwnd, Window window ) DECLSPEC_HIDDEN; +extern void x11drv_input_remove_window( Window window ) DECLSPEC_HIDDEN; + /* X11 driver private messages, must be in the range 0x80001000..0x80001fff */ enum x11drv_window_messages { WM_X11DRV_UPDATE_CLIPBOARD = 0x80001000, WM_X11DRV_SET_WIN_REGION, WM_X11DRV_DESKTOP_RESIZED, - WM_X11DRV_SET_CURSOR, - WM_X11DRV_CLIP_CURSOR_NOTIFY, - WM_X11DRV_CLIP_CURSOR_REQUEST, WM_X11DRV_DELETE_TAB, - WM_X11DRV_ADD_TAB, - WM_X11DRV_RELEASE_CURSOR, + WM_X11DRV_ADD_TAB }; /* _NET_WM_STATE properties that we keep track of */ @@ -668,7 +667,6 @@ struct x11drv_win_data extern struct x11drv_win_data *get_win_data( HWND hwnd ) DECLSPEC_HIDDEN; extern void release_win_data( struct x11drv_win_data *data ) DECLSPEC_HIDDEN; extern Window X11DRV_get_whole_window( HWND hwnd ) DECLSPEC_HIDDEN; -extern XIC X11DRV_get_ic( HWND hwnd ) DECLSPEC_HIDDEN; extern Window get_dummy_parent(void) DECLSPEC_HIDDEN; extern void sync_gl_drawable( HWND hwnd, BOOL known_child ) DECLSPEC_HIDDEN; @@ -733,14 +731,11 @@ extern XContext winContext DECLSPEC_HIDDEN; /* X context to associate an X cursor to a Win32 cursor handle */ extern XContext cursor_context DECLSPEC_HIDDEN; +extern BOOL is_current_process_focused(void) DECLSPEC_HIDDEN; extern void X11DRV_SetFocus( HWND hwnd ) DECLSPEC_HIDDEN; extern void set_window_cursor( Window window, HCURSOR handle ) DECLSPEC_HIDDEN; -extern void sync_window_cursor( Window window ) DECLSPEC_HIDDEN; -extern LRESULT clip_cursor_notify( HWND hwnd, HWND prev_clip_hwnd, HWND new_clip_hwnd ) DECLSPEC_HIDDEN; -extern LRESULT clip_cursor_request( HWND hwnd, BOOL fullscreen, BOOL reset ) DECLSPEC_HIDDEN; +extern void retry_grab_clipping_window(void) DECLSPEC_HIDDEN; extern void ungrab_clipping_window(void) DECLSPEC_HIDDEN; -extern void reset_clipping_window(void) DECLSPEC_HIDDEN; -extern BOOL clip_fullscreen_window( HWND hwnd, BOOL reset ) DECLSPEC_HIDDEN; extern void move_resize_window( HWND hwnd, int dir ) DECLSPEC_HIDDEN; extern void X11DRV_InitKeyboard( Display *display ) DECLSPEC_HIDDEN; extern void X11DRV_InitMouse( Display *display ) DECLSPEC_HIDDEN; @@ -819,7 +814,6 @@ extern void init_registry_display_settings(void) DECLSPEC_HIDDEN; extern BOOL is_virtual_desktop(void) DECLSPEC_HIDDEN; extern BOOL is_desktop_fullscreen(void) DECLSPEC_HIDDEN; extern BOOL is_detached_mode(const DEVMODEW *) DECLSPEC_HIDDEN; -extern BOOL create_desktop_win_data( Window win ) DECLSPEC_HIDDEN; void X11DRV_Settings_Init(void) DECLSPEC_HIDDEN; void X11DRV_XF86VM_Init(void) DECLSPEC_HIDDEN; @@ -878,10 +872,12 @@ extern BOOL X11DRV_DisplayDevices_SupportEventHandlers(void) DECLSPEC_HIDDEN; extern struct x11drv_display_device_handler desktop_handler DECLSPEC_HIDDEN; /* XIM support */ -extern BOOL X11DRV_InitXIM( const WCHAR *input_style ) DECLSPEC_HIDDEN; -extern XIC X11DRV_CreateIC(XIM xim, struct x11drv_win_data *data) DECLSPEC_HIDDEN; -extern void X11DRV_SetupXIM(void) DECLSPEC_HIDDEN; -extern void X11DRV_XIMLookupChars( const char *str, UINT count ) DECLSPEC_HIDDEN; +extern BOOL xim_init( const WCHAR *input_style ) DECLSPEC_HIDDEN; +extern void xim_thread_attach( struct x11drv_thread_data *data ) DECLSPEC_HIDDEN; +extern BOOL xim_in_compose_mode(void) DECLSPEC_HIDDEN; +extern void xim_set_result_string( HWND hwnd, const char *str, UINT count ) DECLSPEC_HIDDEN; +extern XIC X11DRV_get_ic( HWND hwnd ) DECLSPEC_HIDDEN; +extern void xim_set_focus( HWND hwnd, BOOL focus ) DECLSPEC_HIDDEN; #define XEMBED_MAPPED (1 << 0) @@ -896,7 +892,6 @@ static inline BOOL is_window_rect_mapped( const RECT *rect ) /* unixlib interface */ -extern NTSTATUS x11drv_create_desktop( void *arg ) DECLSPEC_HIDDEN; extern NTSTATUS x11drv_systray_clear( void *arg ) DECLSPEC_HIDDEN; extern NTSTATUS x11drv_systray_dock( void *arg ) DECLSPEC_HIDDEN; extern NTSTATUS x11drv_systray_hide( void *arg ) DECLSPEC_HIDDEN; @@ -905,8 +900,7 @@ extern NTSTATUS x11drv_tablet_attach_queue( void *arg ) DECLSPEC_HIDDEN; extern NTSTATUS x11drv_tablet_get_packet( void *arg ) DECLSPEC_HIDDEN; extern NTSTATUS x11drv_tablet_load_info( void *arg ) DECLSPEC_HIDDEN; extern NTSTATUS x11drv_tablet_info( void *arg ) DECLSPEC_HIDDEN; -extern NTSTATUS x11drv_xim_preedit_state( void *arg ) DECLSPEC_HIDDEN; -extern NTSTATUS x11drv_xim_reset( void *arg ) DECLSPEC_HIDDEN; +extern NTSTATUS x11drv_input_thread( void *arg ) DECLSPEC_HIDDEN; extern NTSTATUS x11drv_client_func( enum x11drv_client_funcs func, const void *params, ULONG size ) DECLSPEC_HIDDEN; @@ -1017,5 +1011,6 @@ static inline UINT asciiz_to_unicode( WCHAR *dst, const char *src ) extern BOOL layered_window_client_hack; extern BOOL vulkan_gdi_blit_source_hack; extern BOOL vulkan_disable_child_window_rendering_hack; +extern BOOL input_thread_hack; #endif /* __WINE_X11DRV_H */ diff --git a/dlls/winex11.drv/x11drv_dll.h b/dlls/winex11.drv/x11drv_dll.h index 047bb430d39..bab27afce14 100644 --- a/dlls/winex11.drv/x11drv_dll.h +++ b/dlls/winex11.drv/x11drv_dll.h @@ -30,17 +30,10 @@ extern NTSTATUS WINAPI x11drv_dnd_enter_event( void *params, ULONG size ) DECLSPEC_HIDDEN; extern NTSTATUS WINAPI x11drv_dnd_position_event( void *params, ULONG size ) DECLSPEC_HIDDEN; extern NTSTATUS WINAPI x11drv_dnd_post_drop( void *data, ULONG size ) DECLSPEC_HIDDEN; -extern NTSTATUS WINAPI x11drv_ime_set_composition_string( void *params, ULONG size ) DECLSPEC_HIDDEN; -extern NTSTATUS WINAPI x11drv_ime_set_result( void *params, ULONG size ) DECLSPEC_HIDDEN; extern NTSTATUS WINAPI x11drv_systray_change_owner( void *params, ULONG size ) DECLSPEC_HIDDEN; extern NTSTATUS x11drv_dnd_drop_event( UINT arg ) DECLSPEC_HIDDEN; extern NTSTATUS x11drv_dnd_leave_event( UINT arg ) DECLSPEC_HIDDEN; -extern NTSTATUS x11drv_ime_get_cursor_pos( UINT arg ) DECLSPEC_HIDDEN; -extern NTSTATUS x11drv_ime_set_composition_status( UINT arg ) DECLSPEC_HIDDEN; -extern NTSTATUS x11drv_ime_set_cursor_pos( UINT pos ) DECLSPEC_HIDDEN; -extern NTSTATUS x11drv_ime_set_open_status( UINT open ) DECLSPEC_HIDDEN; -extern NTSTATUS x11drv_ime_update_association( UINT arg ) DECLSPEC_HIDDEN; extern LRESULT WINAPI foreign_window_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) DECLSPEC_HIDDEN; diff --git a/dlls/winex11.drv/x11drv_main.c b/dlls/winex11.drv/x11drv_main.c index 2b5c5b9c8e8..d1257701b9d 100644 --- a/dlls/winex11.drv/x11drv_main.c +++ b/dlls/winex11.drv/x11drv_main.c @@ -36,9 +36,7 @@ #include #include #include -#ifdef HAVE_XKB #include -#endif #ifdef HAVE_X11_EXTENSIONS_XRENDER_H #include #endif @@ -74,12 +72,10 @@ BOOL usexvidmode = FALSE; BOOL usexrandr = TRUE; BOOL usexcomposite = TRUE; BOOL use_xfixes = FALSE; -BOOL use_xkb = TRUE; BOOL use_take_focus = FALSE; BOOL use_primary_selection = FALSE; BOOL use_system_cursors = TRUE; BOOL show_systray = TRUE; -BOOL grab_pointer = TRUE; BOOL grab_fullscreen = FALSE; BOOL managed_mode = TRUE; BOOL decorated_mode = TRUE; @@ -100,6 +96,7 @@ HANDLE steam_keyboard_event; BOOL layered_window_client_hack = FALSE; BOOL vulkan_gdi_blit_source_hack = FALSE; BOOL vulkan_disable_child_window_rendering_hack = FALSE; +BOOL input_thread_hack = FALSE; static x11drv_error_callback err_callback; /* current callback for error */ static Display *err_callback_display; /* display callback is set for */ @@ -527,9 +524,6 @@ static void setup_options(void) if (!get_config_key( hkey, appkey, "ShowSystray", buffer, sizeof(buffer) )) show_systray = IS_OPTION_TRUE( buffer[0] ); - if (!get_config_key( hkey, appkey, "GrabPointer", buffer, sizeof(buffer) )) - grab_pointer = IS_OPTION_TRUE( buffer[0] ); - if (!get_config_key( hkey, appkey, "GrabFullscreen", buffer, sizeof(buffer) )) grab_fullscreen = IS_OPTION_TRUE( buffer[0] ); @@ -635,11 +629,13 @@ static void X11DRV_XComposite_Init(void) #ifdef SONAME_LIBXFIXES #define MAKE_FUNCPTR(f) typeof(f) * p##f; +MAKE_FUNCPTR(XFixesHideCursor) MAKE_FUNCPTR(XFixesQueryExtension) MAKE_FUNCPTR(XFixesQueryVersion) MAKE_FUNCPTR(XFixesCreateRegion) MAKE_FUNCPTR(XFixesCreateRegionFromGC) MAKE_FUNCPTR(XFixesSelectSelectionInput) +MAKE_FUNCPTR(XFixesShowCursor) #undef MAKE_FUNCPTR static void x11drv_load_xfixes(void) @@ -660,11 +656,13 @@ static void x11drv_load_xfixes(void) dlclose(xfixes); \ return; \ } + LOAD_FUNCPTR(XFixesHideCursor) LOAD_FUNCPTR(XFixesQueryExtension) LOAD_FUNCPTR(XFixesQueryVersion) LOAD_FUNCPTR(XFixesCreateRegion) LOAD_FUNCPTR(XFixesCreateRegionFromGC) LOAD_FUNCPTR(XFixesSelectSelectionInput) + LOAD_FUNCPTR(XFixesShowCursor) #undef LOAD_FUNCPTR if (!pXFixesQueryExtension(gdi_display, &event, &error)) @@ -828,12 +826,10 @@ static NTSTATUS x11drv_init( void *arg ) #endif X11DRV_XInput2_Load(); -#ifdef HAVE_XKB - if (use_xkb) use_xkb = XkbUseExtension( gdi_display, NULL, NULL ); -#endif + XkbUseExtension( gdi_display, NULL, NULL ); X11DRV_InitKeyboard( gdi_display ); X11DRV_InitMouse( gdi_display ); - if (use_xim) use_xim = X11DRV_InitXIM( input_style ); + if (use_xim) use_xim = xim_init( input_style ); { const char *e = getenv("WINE_DISABLE_FULLSCREEN_HACK"); @@ -864,11 +860,19 @@ static NTSTATUS x11drv_init( void *arg ) !strcmp(sgi, "1009290") /* Bug 21949 : SWORD ART ONLINE Alicization Lycoris video tearing */ )) || (e && *e != '\0' && *e != '0'); + + e = getenv("WINE_INPUT_THREAD_HACK"); + input_thread_hack = + (sgi && ( + !strcmp(sgi, "1938010") + )) || + (e && *e != '\0' && *e != '0'); } init_user_driver(); X11DRV_DisplayDevices_Init(FALSE); *params->show_systray = show_systray; + params->input_thread_hack = input_thread_hack; return STATUS_SUCCESS; } @@ -941,17 +945,14 @@ struct x11drv_thread_data *x11drv_init_thread_data(void) fcntl( ConnectionNumber(data->display), F_SETFD, 1 ); /* set close on exec flag */ -#ifdef HAVE_XKB - if (use_xkb && XkbUseExtension( data->display, NULL, NULL )) - XkbSetDetectableAutoRepeat( data->display, True, NULL ); -#endif - + XkbUseExtension( data->display, NULL, NULL ); + XkbSetDetectableAutoRepeat( data->display, True, NULL ); if (TRACE_ON(synchronous)) XSynchronize( data->display, True ); set_queue_display_fd( data->display ); NtUserGetThreadInfo()->driver_data = (UINT_PTR)data; - if (use_xim) X11DRV_SetupXIM(); + if (use_xim) xim_thread_attach( data ); X11DRV_XInput2_Init(); if (NtUserGetWindowThread( NtUserGetDesktopWindow(), NULL ) == GetCurrentThreadId()) @@ -1484,7 +1485,6 @@ NTSTATUS x11drv_client_call( enum client_callback func, UINT arg ) const unixlib_entry_t __wine_unix_call_funcs[] = { - x11drv_create_desktop, x11drv_init, x11drv_systray_clear, x11drv_systray_dock, @@ -1494,8 +1494,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] = x11drv_tablet_get_packet, x11drv_tablet_info, x11drv_tablet_load_info, - x11drv_xim_preedit_state, - x11drv_xim_reset, + x11drv_input_thread, }; @@ -1572,23 +1571,8 @@ static NTSTATUS x11drv_wow64_tablet_info( void *arg ) return x11drv_tablet_info( ¶ms ); } -static NTSTATUS x11drv_wow64_xim_preedit_state( void *arg ) -{ - struct - { - ULONG hwnd; - BOOL open; - } *params32 = arg; - struct xim_preedit_state_params params; - - params.hwnd = UlongToHandle( params32->hwnd ); - params.open = params32->open; - return x11drv_xim_preedit_state( ¶ms ); -} - const unixlib_entry_t __wine_unix_call_wow64_funcs[] = { - x11drv_create_desktop, x11drv_wow64_init, x11drv_wow64_systray_clear, x11drv_wow64_systray_dock, @@ -1598,8 +1582,7 @@ const unixlib_entry_t __wine_unix_call_wow64_funcs[] = x11drv_wow64_tablet_get_packet, x11drv_wow64_tablet_info, x11drv_tablet_load_info, - x11drv_wow64_xim_preedit_state, - x11drv_xim_reset, + x11drv_input_thread, }; C_ASSERT( ARRAYSIZE(__wine_unix_call_wow64_funcs) == unix_funcs_count ); diff --git a/dlls/winex11.drv/xfixes.h b/dlls/winex11.drv/xfixes.h index 3ab31201d3d..10c9543ce3c 100644 --- a/dlls/winex11.drv/xfixes.h +++ b/dlls/winex11.drv/xfixes.h @@ -27,9 +27,11 @@ #ifdef SONAME_LIBXFIXES #include #define MAKE_FUNCPTR(f) extern typeof(f) * p##f DECLSPEC_HIDDEN; +MAKE_FUNCPTR(XFixesHideCursor) MAKE_FUNCPTR(XFixesQueryExtension) MAKE_FUNCPTR(XFixesQueryVersion) MAKE_FUNCPTR(XFixesSelectSelectionInput) +MAKE_FUNCPTR(XFixesShowCursor) #undef MAKE_FUNCPTR #endif /* defined(SONAME_LIBXFIXES) */ diff --git a/dlls/winex11.drv/xim.c b/dlls/winex11.drv/xim.c index d736dd80345..209d63f0402 100644 --- a/dlls/winex11.drv/xim.c +++ b/dlls/winex11.drv/xim.c @@ -27,6 +27,8 @@ #include #include +#include "ntstatus.h" +#define WIN32_NO_STATUS #include "windef.h" #include "winbase.h" #include "winnls.h" @@ -42,255 +44,295 @@ WINE_DEFAULT_DEBUG_CHANNEL(xim); #define XICProc XIMProc #endif -BOOL ximInComposeMode=FALSE; +struct ime_update +{ + struct list entry; + DWORD id; + DWORD cursor_pos; + WCHAR *comp_str; + WCHAR *result_str; + WCHAR buffer[]; +}; + +static pthread_mutex_t ime_mutex = PTHREAD_MUTEX_INITIALIZER; +static struct list ime_updates = LIST_INIT(ime_updates); +static DWORD ime_update_count; +static WCHAR *ime_comp_buf; + +static XIMStyle input_style = 0; +static XIMStyle input_style_req = XIMPreeditCallbacks | XIMStatusCallbacks; + +static const char *debugstr_xim_style( XIMStyle style ) +{ + char buffer[1024], *buf = buffer; + + buf += sprintf( buf, "preedit" ); + if (style & XIMPreeditArea) buf += sprintf( buf, " area" ); + if (style & XIMPreeditCallbacks) buf += sprintf( buf, " callbacks" ); + if (style & XIMPreeditPosition) buf += sprintf( buf, " position" ); + if (style & XIMPreeditNothing) buf += sprintf( buf, " nothing" ); + if (style & XIMPreeditNone) buf += sprintf( buf, " none" ); + + buf += sprintf( buf, ", status" ); + if (style & XIMStatusArea) buf += sprintf( buf, " area" ); + if (style & XIMStatusCallbacks) buf += sprintf( buf, " callbacks" ); + if (style & XIMStatusNothing) buf += sprintf( buf, " nothing" ); + if (style & XIMStatusNone) buf += sprintf( buf, " none" ); + + return wine_dbg_sprintf( "%s", buffer ); +} + +BOOL xim_in_compose_mode(void) +{ + return !!ime_comp_buf; +} + +static void post_ime_update( HWND hwnd, UINT cursor_pos, WCHAR *comp_str, WCHAR *result_str ) +{ + UINT id, comp_len, result_len; + struct ime_update *update; + + comp_len = comp_str ? wcslen( comp_str ) + 1 : 0; + result_len = result_str ? wcslen( result_str ) + 1 : 0; -/* moved here from imm32 for dll separation */ -static DWORD dwCompStringLength = 0; -static LPBYTE CompositionString = NULL; -static DWORD dwCompStringSize = 0; + if (!(update = malloc( offsetof(struct ime_update, buffer[comp_len + result_len]) ))) return; + update->cursor_pos = cursor_pos; + update->comp_str = comp_str ? memcpy( update->buffer, comp_str, comp_len * sizeof(WCHAR) ) : NULL; + update->result_str = result_str ? memcpy( update->buffer + comp_len, result_str, result_len * sizeof(WCHAR) ) : NULL; -#define STYLE_OFFTHESPOT (XIMPreeditArea | XIMStatusArea) -#define STYLE_OVERTHESPOT (XIMPreeditPosition | XIMStatusNothing) -#define STYLE_ROOT (XIMPreeditNothing | XIMStatusNothing) -/* this uses all the callbacks to utilize full IME support */ -#define STYLE_CALLBACK (XIMPreeditCallbacks | XIMStatusNothing) -/* in order to enable deadkey support */ -#define STYLE_NONE (XIMPreeditNothing | XIMStatusNothing) + pthread_mutex_lock( &ime_mutex ); + id = update->id = ++ime_update_count; + list_add_tail( &ime_updates, &update->entry ); + pthread_mutex_unlock( &ime_mutex ); -static XIMStyle ximStyle = 0; -static XIMStyle ximStyleRoot = 0; -static XIMStyle ximStyleRequest = STYLE_CALLBACK; + NtUserPostMessage( hwnd, WM_IME_NOTIFY, IMN_WINE_SET_COMP_STRING, id ); +} -static void X11DRV_ImmSetInternalString(UINT offset, UINT selLength, LPWSTR lpComp, UINT len) +static void xim_update_comp_string( UINT offset, UINT old_len, const WCHAR *text, UINT new_len ) { - /* Composition strings are edited in chunks */ - unsigned int byte_length = len * sizeof(WCHAR); - unsigned int byte_offset = offset * sizeof(WCHAR); - unsigned int byte_selection = selLength * sizeof(WCHAR); - int byte_expansion = byte_length - byte_selection; - LPBYTE ptr_new; + UINT len = ime_comp_buf ? wcslen( ime_comp_buf ) : 0; + int diff = new_len - old_len; + WCHAR *ptr; - TRACE("( %i, %i, %p, %d):\n", offset, selLength, lpComp, len ); + TRACE( "offset %u, old_len %u, text %s\n", offset, old_len, debugstr_wn(text, new_len) ); - if (byte_expansion + dwCompStringLength >= dwCompStringSize) + if (!(ptr = realloc( ime_comp_buf, (len + max(0, diff) + 1) * sizeof(WCHAR) ))) { - ptr_new = realloc( CompositionString, dwCompStringSize + byte_expansion ); - if (ptr_new == NULL) - { - ERR("Couldn't expand composition string buffer\n"); - return; - } - - CompositionString = ptr_new; - dwCompStringSize += byte_expansion; + ERR( "Failed to reallocate composition string buffer\n" ); + return; } - ptr_new = CompositionString + byte_offset; - memmove(ptr_new + byte_length, ptr_new + byte_selection, - dwCompStringLength - byte_offset - byte_selection); - if (lpComp) memcpy(ptr_new, lpComp, byte_length); - dwCompStringLength += byte_expansion; - - x11drv_client_func( client_func_ime_set_composition_string, - CompositionString, dwCompStringLength ); + ime_comp_buf = ptr; + ptr = ime_comp_buf + offset; + memmove( ptr + new_len, ptr + old_len, (len - offset - old_len) * sizeof(WCHAR) ); + if (text) memcpy( ptr, text, new_len * sizeof(WCHAR) ); + ime_comp_buf[len + diff] = 0; } -void X11DRV_XIMLookupChars( const char *str, UINT count ) +void xim_set_result_string( HWND hwnd, const char *str, UINT count ) { WCHAR *output; DWORD len; - TRACE("%p %u\n", str, count); + TRACE( "hwnd %p, string %s\n", hwnd, debugstr_an(str, count) ); - if (!(output = malloc( count * sizeof(WCHAR) ))) return; + if (!(output = malloc( (count + 1) * sizeof(WCHAR) ))) return; len = ntdll_umbstowcs( str, count, output, count ); + output[len] = 0; + + post_ime_update( hwnd, 0, NULL, output ); - x11drv_client_func( client_func_ime_set_result, output, len * sizeof(WCHAR) ); free( output ); } -static BOOL XIMPreEditStateNotifyCallback(XIC xic, XPointer p, XPointer data) +static BOOL xic_preedit_state_notify( XIC xic, XPointer user, XPointer arg ) { - const struct x11drv_win_data * const win_data = (struct x11drv_win_data *)p; - const XIMPreeditState state = ((XIMPreeditStateNotifyCallbackStruct *)data)->state; + XIMPreeditStateNotifyCallbackStruct *params = (void *)arg; + const XIMPreeditState state = params->state; + HWND hwnd = (HWND)user; + + TRACE( "xic %p, hwnd %p, state %lu\n", xic, hwnd, state ); - TRACE("xic = %p, win = %lx, state = %lu\n", xic, win_data->whole_window, state); switch (state) { case XIMPreeditEnable: - x11drv_client_call( client_ime_set_open_status, TRUE ); + NtUserPostMessage( hwnd, WM_IME_NOTIFY, IMN_WINE_SET_OPEN_STATUS, TRUE ); break; case XIMPreeditDisable: - x11drv_client_call( client_ime_set_open_status, FALSE ); - break; - default: + NtUserPostMessage( hwnd, WM_IME_NOTIFY, IMN_WINE_SET_OPEN_STATUS, FALSE ); break; } return TRUE; } -static int XIMPreEditStartCallback(XIC ic, XPointer client_data, XPointer call_data) +static int xic_preedit_start( XIC xic, XPointer user, XPointer arg ) { - TRACE("PreEditStartCallback %p\n",ic); - x11drv_client_call( client_ime_set_composition_status, TRUE ); - ximInComposeMode = TRUE; + HWND hwnd = (HWND)user; + + TRACE( "xic %p, hwnd %p, arg %p\n", xic, hwnd, arg ); + + if ((ime_comp_buf = realloc( ime_comp_buf, sizeof(WCHAR) ))) *ime_comp_buf = 0; + else ERR( "Failed to allocate preedit buffer\n" ); + + NtUserPostMessage( hwnd, WM_IME_NOTIFY, IMN_WINE_SET_OPEN_STATUS, TRUE ); + post_ime_update( hwnd, 0, ime_comp_buf, NULL ); + return -1; } -static void XIMPreEditDoneCallback(XIC ic, XPointer client_data, XPointer call_data) +static int xic_preedit_done( XIC xic, XPointer user, XPointer arg ) { - TRACE("PreeditDoneCallback %p\n",ic); - ximInComposeMode = FALSE; - if (dwCompStringSize) - free( CompositionString ); - dwCompStringSize = 0; - dwCompStringLength = 0; - CompositionString = NULL; - x11drv_client_call( client_ime_set_composition_status, FALSE ); + HWND hwnd = (HWND)user; + + TRACE( "xic %p, hwnd %p, arg %p\n", xic, hwnd, arg ); + + free( ime_comp_buf ); + ime_comp_buf = NULL; + + post_ime_update( hwnd, 0, NULL, NULL ); + NtUserPostMessage( hwnd, WM_IME_NOTIFY, IMN_WINE_SET_OPEN_STATUS, FALSE ); + + return 0; } -static void XIMPreEditDrawCallback(XIM ic, XPointer client_data, - XIMPreeditDrawCallbackStruct *P_DR) +static int xic_preedit_draw( XIC xic, XPointer user, XPointer arg ) { - TRACE("PreEditDrawCallback %p\n",ic); + XIMPreeditDrawCallbackStruct *params = (void *)arg; + HWND hwnd = (HWND)user; + size_t text_len; + XIMText *text; + WCHAR *output; + char *str; + int len; + + TRACE( "xic %p, hwnd %p, arg %p\n", xic, hwnd, arg ); + + if (!params) return 0; + + if (!(text = params->text)) str = NULL; + else if (!text->encoding_is_wchar) str = text->string.multi_byte; + else if ((len = wcstombs( NULL, text->string.wide_char, text->length )) < 0) str = NULL; + else if ((str = malloc( len + 1 ))) + { + wcstombs( str, text->string.wide_char, len ); + str[len] = 0; + } - if (P_DR) + if (!str || !(text_len = strlen( str )) || !(output = malloc( text_len * sizeof(WCHAR) ))) + xim_update_comp_string( params->chg_first, params->chg_length, NULL, 0 ); + else { - int sel = P_DR->chg_first; - int len = P_DR->chg_length; - if (P_DR->text) - { - if (! P_DR->text->encoding_is_wchar) - { - size_t text_len; - WCHAR *output; - - TRACE("multibyte\n"); - text_len = strlen( P_DR->text->string.multi_byte ); - if ((output = malloc( text_len * sizeof(WCHAR) ))) - { - text_len = ntdll_umbstowcs( P_DR->text->string.multi_byte, text_len, - output, text_len ); - - X11DRV_ImmSetInternalString( sel, len, output, text_len ); - free( output ); - } - } - else - { - FIXME("wchar PROBIBILY WRONG\n"); - X11DRV_ImmSetInternalString (sel, len, - (LPWSTR)P_DR->text->string.wide_char, - P_DR->text->length); - } - } - else - X11DRV_ImmSetInternalString (sel, len, NULL, 0); - x11drv_client_call( client_ime_set_cursor_pos, P_DR->caret ); + text_len = ntdll_umbstowcs( str, text_len, output, text_len ); + xim_update_comp_string( params->chg_first, params->chg_length, output, text_len ); + free( output ); } - TRACE("Finished\n"); + + if (text && str != text->string.multi_byte) free( str ); + + post_ime_update( hwnd, params->caret, ime_comp_buf, NULL ); + + return 0; } -static void XIMPreEditCaretCallback(XIC ic, XPointer client_data, - XIMPreeditCaretCallbackStruct *P_C) +static int xic_preedit_caret( XIC xic, XPointer user, XPointer arg ) { - TRACE("PreeditCaretCallback %p\n",ic); + static int xim_caret_pos; + XIMPreeditCaretCallbackStruct *params = (void *)arg; + HWND hwnd = (HWND)user; + int pos; + + TRACE( "xic %p, hwnd %p, arg %p\n", xic, hwnd, arg ); + + if (!params) return 0; - if (P_C) + pos = xim_caret_pos; + switch (params->direction) { - int pos = x11drv_client_call( client_ime_get_cursor_pos, 0 ); - TRACE("pos: %d\n", pos); - switch(P_C->direction) - { - case XIMForwardChar: - case XIMForwardWord: - pos++; - break; - case XIMBackwardChar: - case XIMBackwardWord: - pos--; - break; - case XIMLineStart: - pos = 0; - break; - case XIMAbsolutePosition: - pos = P_C->position; - break; - case XIMDontChange: - P_C->position = pos; - return; - case XIMCaretUp: - case XIMCaretDown: - case XIMPreviousLine: - case XIMNextLine: - case XIMLineEnd: - FIXME("Not implemented\n"); - break; - } - x11drv_client_call( client_ime_set_cursor_pos, pos ); - P_C->position = pos; + case XIMForwardChar: + case XIMForwardWord: + pos++; + break; + case XIMBackwardChar: + case XIMBackwardWord: + pos--; + break; + case XIMLineStart: + pos = 0; + break; + case XIMAbsolutePosition: + pos = params->position; + break; + case XIMDontChange: + params->position = pos; + return 0; + case XIMCaretUp: + case XIMCaretDown: + case XIMPreviousLine: + case XIMNextLine: + case XIMLineEnd: + FIXME( "Not implemented\n" ); + break; } - TRACE("Finished\n"); + params->position = xim_caret_pos = pos; + + post_ime_update( hwnd, pos, ime_comp_buf, NULL ); + + return 0; } -NTSTATUS x11drv_xim_reset( void *hwnd ) +static int xic_status_start( XIC xic, XPointer user, XPointer arg ) { - XIC ic = X11DRV_get_ic(hwnd); - if (ic) - { - char* leftover; - TRACE("Forcing Reset %p\n",ic); - leftover = XmbResetIC(ic); - XFree(leftover); - } + HWND hwnd = (HWND)user; + TRACE( "xic %p, hwnd %p, arg %p\n", xic, hwnd, arg ); return 0; } -NTSTATUS x11drv_xim_preedit_state( void *arg ) +static int xic_status_done( XIC xic, XPointer user, XPointer arg ) { - struct xim_preedit_state_params *params = arg; - XIC ic; - XIMPreeditState state; + HWND hwnd = (HWND)user; + TRACE( "xic %p, hwnd %p, arg %p\n", xic, hwnd, arg ); + return 0; +} + +static int xic_status_draw( XIC xic, XPointer user, XPointer arg ) +{ + HWND hwnd = (HWND)user; + TRACE( "xic %p, hwnd %p, arg %p\n", xic, hwnd, arg ); + return 0; +} + +/*********************************************************************** + * NotifyIMEStatus (X11DRV.@) + */ +void X11DRV_NotifyIMEStatus( HWND hwnd, UINT status ) +{ + XIMPreeditState state = status ? XIMPreeditEnable : XIMPreeditDisable; XVaNestedList attr; + XIC xic; - ic = X11DRV_get_ic( params->hwnd ); - if (!ic) - return 0; + TRACE( "hwnd %p, status %#x\n", hwnd, status ); - if (params->open) - state = XIMPreeditEnable; - else - state = XIMPreeditDisable; + if (!(xic = X11DRV_get_ic( hwnd ))) return; - attr = XVaCreateNestedList(0, XNPreeditState, state, NULL); - if (attr != NULL) + if ((attr = XVaCreateNestedList( 0, XNPreeditState, state, NULL ))) { - XSetICValues(ic, XNPreeditAttributes, attr, NULL); - XFree(attr); + XSetICValues( xic, XNPreeditAttributes, attr, NULL ); + XFree( attr ); } - return 0; -} + if (!status) XFree( XmbResetIC( xic ) ); +} /*********************************************************************** - * X11DRV_InitXIM - * - * Process-wide XIM initialization. + * xim_init */ -BOOL X11DRV_InitXIM( const WCHAR *input_style ) +BOOL xim_init( const WCHAR *input_style ) { static const WCHAR offthespotW[] = {'o','f','f','t','h','e','s','p','o','t',0}; static const WCHAR overthespotW[] = {'o','v','e','r','t','h','e','s','p','o','t',0}; static const WCHAR rootW[] = {'r','o','o','t',0}; - if (!wcsicmp( input_style, offthespotW )) - ximStyleRequest = STYLE_OFFTHESPOT; - else if (!wcsicmp( input_style, overthespotW )) - ximStyleRequest = STYLE_OVERTHESPOT; - else if (!wcsicmp( input_style, rootW )) - ximStyleRequest = STYLE_ROOT; - if (!XSupportsLocale()) { WARN("X does not support locale.\n"); @@ -301,284 +343,291 @@ BOOL X11DRV_InitXIM( const WCHAR *input_style ) WARN("Could not set locale modifiers.\n"); return FALSE; } - return TRUE; -} - -static void open_xim_callback( Display *display, XPointer ptr, XPointer data ); + if (!wcsicmp( input_style, offthespotW )) + input_style_req = XIMPreeditArea | XIMStatusArea; + else if (!wcsicmp( input_style, overthespotW )) + input_style_req = XIMPreeditPosition | XIMStatusNothing; + else if (!wcsicmp( input_style, rootW )) + input_style_req = XIMPreeditNothing | XIMStatusNothing; -static void X11DRV_DestroyIM(XIM xim, XPointer p, XPointer data) -{ - struct x11drv_thread_data *thread_data = x11drv_thread_data(); + TRACE( "requesting %s style %#lx %s\n", debugstr_w(input_style), input_style_req, + debugstr_xim_style( input_style_req ) ); - TRACE("xim = %p, p = %p\n", xim, p); - thread_data->xim = NULL; - ximStyle = 0; - XRegisterIMInstantiateCallback( thread_data->display, NULL, NULL, NULL, open_xim_callback, NULL ); + return TRUE; } -/*********************************************************************** - * X11DRV Ime creation - * - * Should always be called with the x11 lock held - */ -static BOOL open_xim( Display *display ) +static void xim_open( Display *display, XPointer user, XPointer arg ); +static void xim_destroy( XIM xim, XPointer user, XPointer arg ); + +static XIM xim_create( struct x11drv_thread_data *data ) { - struct x11drv_thread_data *thread_data = x11drv_thread_data(); - XIMStyle ximStyleNone; - XIMStyles *ximStyles = NULL; + XIMCallback destroy = {.callback = xim_destroy, .client_data = (XPointer)data}; + XIMStyle input_style_fallback = XIMPreeditNone | XIMStatusNone; + XIMStyles *styles = NULL; INT i; XIM xim; - XIMCallback destroy; - xim = XOpenIM(display, NULL, NULL, NULL); - if (xim == NULL) + if (!(xim = XOpenIM( data->display, NULL, NULL, NULL ))) { WARN("Could not open input method.\n"); - return FALSE; + return NULL; } - destroy.client_data = NULL; - destroy.callback = X11DRV_DestroyIM; - if (XSetIMValues(xim, XNDestroyCallback, &destroy, NULL)) - { - WARN("Could not set destroy callback.\n"); - } + if (XSetIMValues( xim, XNDestroyCallback, &destroy, NULL )) + WARN( "Could not set destroy callback.\n" ); - TRACE("xim = %p\n", xim); - TRACE("X display of IM = %p\n", XDisplayOfIM(xim)); - TRACE("Using %s locale of Input Method\n", XLocaleOfIM(xim)); + TRACE( "xim %p, XDisplayOfIM %p, XLocaleOfIM %s\n", xim, XDisplayOfIM( xim ), + debugstr_a(XLocaleOfIM( xim )) ); - XGetIMValues(xim, XNQueryInputStyle, &ximStyles, NULL); - if (ximStyles == 0) + XGetIMValues( xim, XNQueryInputStyle, &styles, NULL ); + if (!styles) { - WARN("Could not find supported input style.\n"); - XCloseIM(xim); - return FALSE; + WARN( "Could not find supported input style.\n" ); + XCloseIM( xim ); + return NULL; } - else + + TRACE( "input styles count %u\n", styles->count_styles ); + for (i = 0, input_style = 0; i < styles->count_styles; ++i) { - TRACE("ximStyles->count_styles = %d\n", ximStyles->count_styles); - - ximStyleRoot = 0; - ximStyleNone = 0; - - for (i = 0; i < ximStyles->count_styles; ++i) - { - int style = ximStyles->supported_styles[i]; - TRACE("ximStyles[%d] = %s%s%s%s%s\n", i, - (style&XIMPreeditArea)?"XIMPreeditArea ":"", - (style&XIMPreeditCallbacks)?"XIMPreeditCallbacks ":"", - (style&XIMPreeditPosition)?"XIMPreeditPosition ":"", - (style&XIMPreeditNothing)?"XIMPreeditNothing ":"", - (style&XIMPreeditNone)?"XIMPreeditNone ":""); - if (!ximStyle && (ximStyles->supported_styles[i] == - ximStyleRequest)) - { - ximStyle = ximStyleRequest; - TRACE("Setting Style: ximStyle = ximStyleRequest\n"); - } - else if (!ximStyleRoot &&(ximStyles->supported_styles[i] == - STYLE_ROOT)) - { - ximStyleRoot = STYLE_ROOT; - TRACE("Setting Style: ximStyleRoot = STYLE_ROOT\n"); - } - else if (!ximStyleNone && (ximStyles->supported_styles[i] == - STYLE_NONE)) - { - TRACE("Setting Style: ximStyleNone = STYLE_NONE\n"); - ximStyleNone = STYLE_NONE; - } - } - XFree(ximStyles); - - if (ximStyle == 0) - ximStyle = ximStyleRoot; - - if (ximStyle == 0) - ximStyle = ximStyleNone; + XIMStyle style = styles->supported_styles[i]; + TRACE( " %u: %#lx %s\n", i, style, debugstr_xim_style( style ) ); + + if (style == input_style_req) input_style = style; + if (!input_style && (style & input_style_req)) input_style = style; + if (input_style_fallback > style) input_style_fallback = style; } + XFree(styles); - thread_data->xim = xim; + if (!input_style) input_style = input_style_fallback; + TRACE( "selected style %#lx %s\n", input_style, debugstr_xim_style( input_style ) ); - if ((ximStyle & (XIMPreeditNothing | XIMPreeditNone)) == 0 || - (ximStyle & (XIMStatusNothing | XIMStatusNone)) == 0) - { - char **list; - int count; - thread_data->font_set = XCreateFontSet(display, "fixed", - &list, &count, NULL); - TRACE("ximFontSet = %p\n", thread_data->font_set); - TRACE("list = %p, count = %d\n", list, count); - if (list != NULL) - { - int i; - for (i = 0; i < count; ++i) - TRACE("list[%d] = %s\n", i, list[i]); - XFreeStringList(list); - } - } - else - thread_data->font_set = NULL; + return xim; +} - x11drv_client_call( client_ime_update_association, 0 ); - return TRUE; +static void xim_open( Display *display, XPointer user, XPointer arg ) +{ + struct x11drv_thread_data *data = (void *)user; + TRACE( "display %p, data %p, arg %p\n", display, user, arg ); + if (!(data->xim = xim_create( data ))) return; + XUnregisterIMInstantiateCallback( display, NULL, NULL, NULL, xim_open, user ); } -static void open_xim_callback( Display *display, XPointer ptr, XPointer data ) +static void xim_destroy( XIM xim, XPointer user, XPointer arg ) { - if (open_xim( display )) - XUnregisterIMInstantiateCallback( display, NULL, NULL, NULL, open_xim_callback, NULL); + struct x11drv_thread_data *data = x11drv_thread_data(); + TRACE( "xim %p, user %p, arg %p\n", xim, user, arg ); + if (data->xim != xim) return; + data->xim = NULL; + XRegisterIMInstantiateCallback( data->display, NULL, NULL, NULL, xim_open, user ); } -void X11DRV_SetupXIM(void) +void xim_thread_attach( struct x11drv_thread_data *data ) { - Display *display = thread_display(); + Display *display = data->display; + int i, count; + char **list; + + data->font_set = XCreateFontSet( display, "fixed", &list, &count, NULL ); + TRACE( "created XFontSet %p, list %p, count %d\n", data->font_set, list, count ); + for (i = 0; list && i < count; ++i) TRACE( " %d: %s\n", i, list[i] ); + if (list) XFreeStringList( list ); - if (!open_xim( display )) - XRegisterIMInstantiateCallback( display, NULL, NULL, NULL, open_xim_callback, NULL ); + if ((data->xim = xim_create( data ))) return; + XRegisterIMInstantiateCallback( display, NULL, NULL, NULL, xim_open, (XPointer)data ); } -static BOOL X11DRV_DestroyIC(XIC xic, XPointer p, XPointer data) +static BOOL xic_destroy( XIC xic, XPointer user, XPointer arg ) { - struct x11drv_win_data *win_data = (struct x11drv_win_data *)p; - TRACE("xic = %p, win = %lx\n", xic, win_data->whole_window); - win_data->xic = NULL; + struct x11drv_win_data *data; + HWND hwnd = (HWND)user; + + TRACE( "xic %p, hwnd %p, arg %p\n", xic, hwnd, arg ); + + if ((data = get_win_data( hwnd ))) + { + if (data->xic == xic) data->xic = NULL; + release_win_data( data ); + } + return TRUE; } - -XIC X11DRV_CreateIC(XIM xim, struct x11drv_win_data *data) +static XIC xic_create( XIM xim, HWND hwnd, Window win ) { + XICCallback destroy = {.callback = xic_destroy, .client_data = (XPointer)hwnd}; + XICCallback preedit_caret = {.callback = xic_preedit_caret, .client_data = (XPointer)hwnd}; + XICCallback preedit_done = {.callback = xic_preedit_done, .client_data = (XPointer)hwnd}; + XICCallback preedit_draw = {.callback = xic_preedit_draw, .client_data = (XPointer)hwnd}; + XICCallback preedit_start = {.callback = xic_preedit_start, .client_data = (XPointer)hwnd}; + XICCallback preedit_state_notify = {.callback = xic_preedit_state_notify, .client_data = (XPointer)hwnd}; + XICCallback status_done = {.callback = xic_status_done, .client_data = (XPointer)hwnd}; + XICCallback status_draw = {.callback = xic_status_draw, .client_data = (XPointer)hwnd}; + XICCallback status_start = {.callback = xic_status_start, .client_data = (XPointer)hwnd}; XPoint spot = {0}; - XVaNestedList preedit = NULL; - XVaNestedList status = NULL; + XVaNestedList preedit, status; XIC xic; - XICCallback destroy = {(XPointer)data, X11DRV_DestroyIC}; - XICCallback P_StateNotifyCB, P_StartCB, P_DoneCB, P_DrawCB, P_CaretCB; - LCID lcid; - Window win = data->whole_window; XFontSet fontSet = x11drv_thread_data()->font_set; - TRACE("xim = %p\n", xim); + TRACE( "xim %p, hwnd %p/%lx\n", xim, hwnd, win ); + + preedit = XVaCreateNestedList( 0, XNFontSet, fontSet, + XNPreeditCaretCallback, &preedit_caret, + XNPreeditDoneCallback, &preedit_done, + XNPreeditDrawCallback, &preedit_draw, + XNPreeditStartCallback, &preedit_start, + XNPreeditStateNotifyCallback, &preedit_state_notify, + XNSpotLocation, &spot, NULL ); + status = XVaCreateNestedList( 0, XNFontSet, fontSet, + XNStatusStartCallback, &status_start, + XNStatusDoneCallback, &status_done, + XNStatusDrawCallback, &status_draw, + NULL ); + xic = XCreateIC( xim, XNInputStyle, input_style, XNPreeditAttributes, preedit, XNStatusAttributes, status, + XNClientWindow, win, XNFocusWindow, win, XNDestroyCallback, &destroy, NULL ); + TRACE( "created XIC %p\n", xic ); + + XFree( preedit ); + XFree( status ); - lcid = NtCurrentTeb()->CurrentLocale; - if (!lcid) NtQueryDefaultLocale( TRUE, &lcid ); + return xic; +} - /* use complex and slow XIC initialization method only for CJK */ - switch (PRIMARYLANGID(LANGIDFROMLCID(lcid))) - { - case LANG_CHINESE: - case LANG_JAPANESE: - case LANG_KOREAN: - break; +XIC X11DRV_get_ic( HWND hwnd ) +{ + struct x11drv_win_data *data; + XIM xim; + XIC ret; - default: - xic = XCreateIC(xim, - XNInputStyle, XIMPreeditNothing | XIMStatusNothing, - XNClientWindow, win, - XNFocusWindow, win, - XNDestroyCallback, &destroy, - NULL); - data->xic = xic; - return xic; - } + if (!(data = get_win_data( hwnd ))) return 0; + x11drv_thread_data()->last_xic_hwnd = hwnd; + if (!(ret = data->xic) && (xim = x11drv_thread_data()->xim)) + ret = data->xic = xic_create( xim, hwnd, data->whole_window ); + release_win_data( data ); + + return ret; +} + +void xim_set_focus( HWND hwnd, BOOL focus ) +{ + struct list updates = LIST_INIT(updates); + struct ime_update *update, *next; + XIC xic; + + if (!(xic = X11DRV_get_ic( hwnd ))) return; + + if (focus) XSetICFocus( xic ); + else XUnsetICFocus( xic ); + + pthread_mutex_lock( &ime_mutex ); + list_move_tail( &updates, &ime_updates ); + pthread_mutex_unlock( &ime_mutex ); - /* create callbacks */ - P_StateNotifyCB.client_data = (XPointer)data; - P_StartCB.client_data = NULL; - P_DoneCB.client_data = NULL; - P_DrawCB.client_data = NULL; - P_CaretCB.client_data = NULL; - P_StateNotifyCB.callback = XIMPreEditStateNotifyCallback; - P_StartCB.callback = XIMPreEditStartCallback; - P_DoneCB.callback = (XICProc)XIMPreEditDoneCallback; - P_DrawCB.callback = (XICProc)XIMPreEditDrawCallback; - P_CaretCB.callback = (XICProc)XIMPreEditCaretCallback; - - if ((ximStyle & (XIMPreeditNothing | XIMPreeditNone)) == 0) + LIST_FOR_EACH_ENTRY_SAFE( update, next, &updates, struct ime_update, entry ) free( update ); +} + +static struct ime_update *find_ime_update( UINT id ) +{ + struct ime_update *update; + LIST_FOR_EACH_ENTRY( update, &ime_updates, struct ime_update, entry ) + if (update->id == id) return update; + return NULL; +} + +/*********************************************************************** + * ImeToAsciiEx (X11DRV.@) + * + * As XIM filters key events upfront, we don't use ImeProcessKey and ImeToAsciiEx is instead called + * back from the IME UI window procedure when WM_IME_NOTIFY / IMN_WINE_SET_COMP_STRING messages are + * sent to it, to retrieve composition string updates and generate WM_IME messages. + */ +UINT X11DRV_ImeToAsciiEx( UINT vkey, UINT lparam, const BYTE *state, COMPOSITIONSTRING *compstr, HIMC himc ) +{ + UINT needed = sizeof(COMPOSITIONSTRING), comp_len, result_len; + struct ime_update *update; + void *dst; + + TRACE( "vkey %#x, lparam %#x, state %p, compstr %p, himc %p\n", vkey, lparam, state, compstr, himc ); + + pthread_mutex_lock( &ime_mutex ); + + if (!(update = find_ime_update( lparam ))) { - preedit = XVaCreateNestedList(0, - XNFontSet, fontSet, - XNSpotLocation, &spot, - XNPreeditStateNotifyCallback, &P_StateNotifyCB, - XNPreeditStartCallback, &P_StartCB, - XNPreeditDoneCallback, &P_DoneCB, - XNPreeditDrawCallback, &P_DrawCB, - XNPreeditCaretCallback, &P_CaretCB, - NULL); - TRACE("preedit = %p\n", preedit); + pthread_mutex_unlock( &ime_mutex ); + return 0; } + + if (!update->comp_str) comp_len = 0; else { - preedit = XVaCreateNestedList(0, - XNPreeditStateNotifyCallback, &P_StateNotifyCB, - XNPreeditStartCallback, &P_StartCB, - XNPreeditDoneCallback, &P_DoneCB, - XNPreeditDrawCallback, &P_DrawCB, - XNPreeditCaretCallback, &P_CaretCB, - NULL); - - TRACE("preedit = %p\n", preedit); + comp_len = wcslen( update->comp_str ); + needed += comp_len * sizeof(WCHAR); /* GCS_COMPSTR */ + needed += comp_len; /* GCS_COMPATTR */ + needed += 2 * sizeof(DWORD); /* GCS_COMPCLAUSE */ } - if ((ximStyle & (XIMStatusNothing | XIMStatusNone)) == 0) + if (!update->result_str) result_len = 0; + else { - status = XVaCreateNestedList(0, - XNFontSet, fontSet, - NULL); - TRACE("status = %p\n", status); - } + result_len = wcslen( update->result_str ); + needed += result_len * sizeof(WCHAR); /* GCS_RESULTSTR */ + needed += 2 * sizeof(DWORD); /* GCS_RESULTCLAUSE */ + } - if (preedit != NULL && status != NULL) + if (compstr->dwSize < needed) { - xic = XCreateIC(xim, - XNInputStyle, ximStyle, - XNPreeditAttributes, preedit, - XNStatusAttributes, status, - XNClientWindow, win, - XNFocusWindow, win, - XNDestroyCallback, &destroy, - NULL); - } - else if (preedit != NULL) - { - xic = XCreateIC(xim, - XNInputStyle, ximStyle, - XNPreeditAttributes, preedit, - XNClientWindow, win, - XNFocusWindow, win, - XNDestroyCallback, &destroy, - NULL); + compstr->dwSize = needed; + pthread_mutex_unlock( &ime_mutex ); + return STATUS_BUFFER_TOO_SMALL; } - else if (status != NULL) + + list_remove( &update->entry ); + pthread_mutex_unlock( &ime_mutex ); + + memset( compstr, 0, sizeof(*compstr) ); + compstr->dwSize = sizeof(*compstr); + + if (update->comp_str) { - xic = XCreateIC(xim, - XNInputStyle, ximStyle, - XNStatusAttributes, status, - XNClientWindow, win, - XNFocusWindow, win, - XNDestroyCallback, &destroy, - NULL); + compstr->dwCursorPos = update->cursor_pos; + + compstr->dwCompStrLen = comp_len; + compstr->dwCompStrOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwCompStrOffset; + memcpy( dst, update->comp_str, compstr->dwCompStrLen * sizeof(WCHAR) ); + compstr->dwSize += compstr->dwCompStrLen * sizeof(WCHAR); + + compstr->dwCompClauseLen = 2 * sizeof(DWORD); + compstr->dwCompClauseOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwCompClauseOffset; + *((DWORD *)dst + 0) = 0; + *((DWORD *)dst + 1) = compstr->dwCompStrLen; + compstr->dwSize += compstr->dwCompClauseLen; + + compstr->dwCompAttrLen = compstr->dwCompStrLen; + compstr->dwCompAttrOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwCompAttrOffset; + memset( dst, ATTR_INPUT, compstr->dwCompAttrLen ); + compstr->dwSize += compstr->dwCompAttrLen; } - else + + if (update->result_str) { - xic = XCreateIC(xim, - XNInputStyle, ximStyle, - XNClientWindow, win, - XNFocusWindow, win, - XNDestroyCallback, &destroy, - NULL); + compstr->dwResultStrLen = result_len; + compstr->dwResultStrOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwResultStrOffset; + memcpy( dst, update->result_str, compstr->dwResultStrLen * sizeof(WCHAR) ); + compstr->dwSize += compstr->dwResultStrLen * sizeof(WCHAR); + + compstr->dwResultClauseLen = 2 * sizeof(DWORD); + compstr->dwResultClauseOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwResultClauseOffset; + *((DWORD *)dst + 0) = 0; + *((DWORD *)dst + 1) = compstr->dwResultStrLen; + compstr->dwSize += compstr->dwResultClauseLen; } - TRACE("xic = %p\n", xic); - data->xic = xic; - - if (preedit != NULL) - XFree(preedit); - if (status != NULL) - XFree(status); - - return xic; + free( update ); + return 0; } diff --git a/dlls/winex11.drv/xrandr.c b/dlls/winex11.drv/xrandr.c index 35998877520..4186b404e6b 100644 --- a/dlls/winex11.drv/xrandr.c +++ b/dlls/winex11.drv/xrandr.c @@ -28,19 +28,17 @@ #define NONAMELESSSTRUCT #define NONAMELESSUNION - -#include "wine/debug.h" - -WINE_DEFAULT_DEBUG_CHANNEL(xrandr); - -#ifdef SONAME_LIBXRANDR - #include #include #include #include #include #include "x11drv.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(xrandr); + +#ifdef SONAME_LIBXRANDR #define VK_NO_PROTOTYPES #define WINE_VK_HOST diff --git a/dlls/wow64win/syscall.h b/dlls/wow64win/syscall.h index 8543c877644..3ff6ecf7c0e 100644 --- a/dlls/wow64win/syscall.h +++ b/dlls/wow64win/syscall.h @@ -92,6 +92,7 @@ SYSCALL_ENTRY( NtUserAssociateInputContext ) \ SYSCALL_ENTRY( NtUserAttachThreadInput ) \ SYSCALL_ENTRY( NtUserBeginPaint ) \ + SYSCALL_ENTRY( NtUserBuildHimcList ) \ SYSCALL_ENTRY( NtUserBuildHwndList ) \ SYSCALL_ENTRY( NtUserCallHwnd ) \ SYSCALL_ENTRY( NtUserCallHwndParam ) \ @@ -219,6 +220,7 @@ SYSCALL_ENTRY( NtUserMessageCall ) \ SYSCALL_ENTRY( NtUserMoveWindow ) \ SYSCALL_ENTRY( NtUserMsgWaitForMultipleObjectsEx ) \ + SYSCALL_ENTRY( NtUserNotifyIMEStatus ) \ SYSCALL_ENTRY( NtUserNotifyWinEvent ) \ SYSCALL_ENTRY( NtUserOpenClipboard ) \ SYSCALL_ENTRY( NtUserOpenDesktop ) \ diff --git a/dlls/wow64win/user.c b/dlls/wow64win/user.c index c8ce23a05ce..11ef806929a 100644 --- a/dlls/wow64win/user.c +++ b/dlls/wow64win/user.c @@ -1182,6 +1182,25 @@ NTSTATUS WINAPI wow64_NtUserBeginPaint( UINT *args ) return HandleToUlong( ret ); } +NTSTATUS WINAPI wow64_NtUserBuildHimcList( UINT *args ) +{ + ULONG thread_id = get_ulong( &args ); + ULONG count = get_ulong( &args ); + UINT32 *buffer32 = get_ptr( &args ); + UINT *size = get_ptr( &args ); + + HIMC *buffer; + ULONG i; + NTSTATUS status; + + if (!(buffer = Wow64AllocateTemp( count * sizeof(*buffer) ))) return STATUS_NO_MEMORY; + + if ((status = NtUserBuildHimcList( thread_id, count, buffer, size ))) return status; + + for (i = 0; i < *size; i++) buffer32[i] = HandleToUlong( buffer[i] ); + return status; +} + NTSTATUS WINAPI wow64_NtUserBuildHwndList( UINT *args ) { HDESK desktop = get_handle( &args ); @@ -3107,6 +3126,21 @@ NTSTATUS WINAPI wow64_NtUserMessageCall( UINT *args ) return message_call_32to64( hwnd, msg, wparam, lparam, LongToPtr( result32 ), type, ansi ); } + + case NtUserImeDriverCall: + { + struct + { + ULONG himc; + ULONG state; + ULONG compstr; + } *params32 = result_info; + struct ime_driver_call_params params; + params.himc = UlongToPtr( params32->himc ); + params.state = UlongToPtr( params32->state ); + params.compstr = UlongToPtr( params32->compstr ); + return NtUserMessageCall( hwnd, msg, wparam, lparam, ¶ms, type, ansi ); + } } return message_call_32to64( hwnd, msg, wparam, lparam, result_info, type, ansi ); @@ -3145,6 +3179,15 @@ NTSTATUS WINAPI wow64_NtUserMsgWaitForMultipleObjectsEx( UINT *args ) return NtUserMsgWaitForMultipleObjectsEx( count, handles, timeout, mask, flags ); } +NTSTATUS WINAPI wow64_NtUserNotifyIMEStatus( UINT *args ) +{ + HWND hwnd = get_handle( &args ); + ULONG status = get_ulong( &args ); + + NtUserNotifyIMEStatus( hwnd, status ); + return 0; +} + NTSTATUS WINAPI wow64_NtUserNotifyWinEvent( UINT *args ) { DWORD event = get_ulong( &args ); diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index 21c6b81028d..ae582aba406 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -161,6 +161,7 @@ static GUID WSARecvMsg_GUID = WSAID_WSARECVMSG; static SOCKET setup_server_socket(struct sockaddr_in *addr, int *len); static SOCKET setup_connector_socket(const struct sockaddr_in *addr, int len, BOOL nonblock); +static int sync_recv(SOCKET s, void *buffer, int len, DWORD flags); static void tcp_socketpair_flags(SOCKET *src, SOCKET *dst, DWORD flags) { @@ -1230,7 +1231,7 @@ static void test_set_getsockopt(void) {AF_INET6, SOCK_DGRAM, IPPROTO_IPV6, IPV6_UNICAST_HOPS, TRUE, {1, 1, 4}, {0}, FALSE}, {AF_INET6, SOCK_DGRAM, IPPROTO_IPV6, IPV6_V6ONLY, TRUE, {1, 1, 1}, {0}, TRUE}, }; - SOCKET s, s2; + SOCKET s, s2, src, dst; int i, j, err, lasterr; int timeout; LINGER lingval; @@ -1242,6 +1243,7 @@ static void test_set_getsockopt(void) int expected_err, expected_size; DWORD value, save_value; UINT64 value64; + char buffer[4096]; struct _prottest { @@ -1307,6 +1309,61 @@ static void test_set_getsockopt(void) ok( !err, "getsockopt(SO_RCVBUF) failed error: %u\n", WSAGetLastError() ); ok( value == 4096, "expected 4096, got %lu\n", value ); + value = 0; + size = sizeof(value); + err = setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&value, size); + ok( !err, "setsockopt(SO_RCVBUF) failed error: %u\n", WSAGetLastError() ); + value = 0xdeadbeef; + err = getsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&value, &size); + ok( !err, "getsockopt(SO_RCVBUF) failed error: %u\n", WSAGetLastError() ); + ok( value == 0, "expected 0, got %lu\n", value ); + + /* Test non-blocking receive with too short SO_RCVBUF. */ + tcp_socketpair(&src, &dst); + set_blocking(src, FALSE); + set_blocking(dst, FALSE); + + value = 0; + size = sizeof(value); + err = setsockopt(src, SOL_SOCKET, SO_SNDBUF, (char *)&value, size); + ok( !err, "got %d, error %u.\n", err, WSAGetLastError() ); + + value = 0xdeadbeef; + err = getsockopt(dst, SOL_SOCKET, SO_RCVBUF, (char *)&value, &size); + ok( !err, "got %d, error %u.\n", err, WSAGetLastError() ); + if (value >= sizeof(buffer) * 3) + { + value = 1024; + size = sizeof(value); + err = setsockopt(dst, SOL_SOCKET, SO_RCVBUF, (char *)&value, size); + ok( !err, "got %d, error %u.\n", err, WSAGetLastError() ); + value = 0xdeadbeef; + err = getsockopt(dst, SOL_SOCKET, SO_RCVBUF, (char *)&value, &size); + ok( !err, "got %d, error %u.\n", err, WSAGetLastError() ); + ok( value == 1024, "expected 0, got %lu\n", value ); + + err = send(src, buffer, sizeof(buffer), 0); + ok(err == sizeof(buffer), "got %d\n", err); + err = send(src, buffer, sizeof(buffer), 0); + ok(err == sizeof(buffer), "got %d\n", err); + err = send(src, buffer, sizeof(buffer), 0); + ok(err == sizeof(buffer), "got %d\n", err); + + err = sync_recv(dst, buffer, sizeof(buffer), 0); + ok(err == sizeof(buffer), "got %d, error %u\n", err, WSAGetLastError()); + err = sync_recv(dst, buffer, sizeof(buffer), 0); + ok(err == sizeof(buffer), "got %d, error %u\n", err, WSAGetLastError()); + err = sync_recv(dst, buffer, sizeof(buffer), 0); + ok(err == sizeof(buffer), "got %d, error %u\n", err, WSAGetLastError()); + } + else + { + skip("Default SO_RCVBUF %lu is too small, skipping test.\n", value); + } + + closesocket(src); + closesocket(dst); + /* SO_LINGER */ for( i = 0; i < ARRAY_SIZE(linger_testvals);i++) { size = sizeof(lingval); diff --git a/dlls/wtsapi32/tests/wtsapi.c b/dlls/wtsapi32/tests/wtsapi.c index 2023a21e938..2748f63a132 100644 --- a/dlls/wtsapi32/tests/wtsapi.c +++ b/dlls/wtsapi32/tests/wtsapi.c @@ -191,10 +191,25 @@ static void test_WTSQuerySessionInformation(void) { WCHAR *buf1, usernameW[UNLEN + 1], computernameW[MAX_COMPUTERNAME_LENGTH + 1]; char *buf2, username[UNLEN + 1], computername[MAX_COMPUTERNAME_LENGTH + 1]; + WTS_CONNECTSTATE_CLASS *state; DWORD count, tempsize; USHORT *protocol; BOOL ret; + count = 0; + ret = WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, WTSConnectState, (WCHAR **)&state, &count); + ok(ret, "got error %lu\n", GetLastError()); + ok(count == sizeof(*state), "got %lu\n", count); + ok(*state == WTSActive, "got %d.\n", *state); + WTSFreeMemory(state); + + count = 0; + ret = WTSQuerySessionInformationA(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, WTSConnectState, (char **)&state, &count); + ok(ret, "got error %lu\n", GetLastError()); + ok(count == sizeof(*state), "got %lu\n", count); + ok(*state == WTSActive, "got %d.\n", *state); + WTSFreeMemory(state); + SetLastError(0xdeadbeef); count = 0; ret = WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, WTSUserName, NULL, &count); @@ -305,6 +320,46 @@ static void test_WTSQueryUserToken(void) ok(GetLastError()==ERROR_INVALID_PARAMETER, "expected ERROR_INVALID_PARAMETER got: %ld\n", GetLastError()); } +static void test_WTSEnumerateSessions(void) +{ + BOOL console_found = FALSE, services_found = FALSE; + WTS_SESSION_INFOW *info; + WTS_SESSION_INFOA *infoA; + DWORD count, count2; + unsigned int i; + BOOL bret; + + bret = WTSEnumerateSessionsW(WTS_CURRENT_SERVER_HANDLE, 0, 1, &info, &count); + ok(bret, "got error %lu.\n", GetLastError()); + todo_wine_if(count == 1) ok(count >= 2, "got %lu.\n", count); + + bret = WTSEnumerateSessionsA(WTS_CURRENT_SERVER_HANDLE, 0, 1, &infoA, &count2); + ok(bret, "got error %lu.\n", GetLastError()); + ok(count2 == count, "got %lu.\n", count2); + + for (i = 0; i < count; ++i) + { + trace("SessionId %lu, name %s, State %d.\n", info[i].SessionId, debugstr_w(info[i].pWinStationName), info[i].State); + if (!wcscmp(info[i].pWinStationName, L"Console")) + { + console_found = TRUE; + ok(info[i].State == WTSActive, "got State %d.\n", info[i].State); + ok(!strcmp(infoA[i].pWinStationName, "Console"), "got %s.\n", debugstr_a(infoA[i].pWinStationName)); + } + else if (!wcscmp(info[i].pWinStationName, L"Services")) + { + services_found = TRUE; + ok(info[i].State == WTSDisconnected, "got State %d.\n", info[i].State); + ok(!strcmp(infoA[i].pWinStationName, "Services"), "got %s.\n", debugstr_a(infoA[i].pWinStationName)); + } + } + ok(console_found, "Console session not found.\n"); + todo_wine ok(services_found, "Services session not found.\n"); + + WTSFreeMemory(info); + WTSFreeMemory(infoA); +} + START_TEST (wtsapi) { pWTSEnumerateProcessesExW = (void *)GetProcAddress(GetModuleHandleA("wtsapi32"), "WTSEnumerateProcessesExW"); @@ -313,4 +368,5 @@ START_TEST (wtsapi) test_WTSEnumerateProcessesW(); test_WTSQuerySessionInformation(); test_WTSQueryUserToken(); + test_WTSEnumerateSessions(); } diff --git a/dlls/wtsapi32/wtsapi32.c b/dlls/wtsapi32/wtsapi32.c index 7de1b8124ea..f4072e7090b 100644 --- a/dlls/wtsapi32/wtsapi32.c +++ b/dlls/wtsapi32/wtsapi32.c @@ -266,7 +266,6 @@ BOOL WINAPI WTSEnumerateServersW(LPWSTR pDomainName, DWORD Reserved, DWORD Versi return FALSE; } - /************************************************************ * WTSEnumerateEnumerateSessionsExW (WTSAPI32.@) */ @@ -290,35 +289,86 @@ BOOL WINAPI WTSEnumerateSessionsExA(HANDLE server, DWORD *level, DWORD filter, W /************************************************************ * WTSEnumerateEnumerateSessionsA (WTSAPI32.@) */ -BOOL WINAPI WTSEnumerateSessionsA(HANDLE hServer, DWORD Reserved, DWORD Version, - PWTS_SESSION_INFOA* ppSessionInfo, DWORD* pCount) +BOOL WINAPI WTSEnumerateSessionsA(HANDLE server, DWORD reserved, DWORD version, + PWTS_SESSION_INFOA *session_info, DWORD *count) { - static int once; + PWTS_SESSION_INFOW infoW; + DWORD size, offset; + unsigned int i; + int len; - if (!once++) FIXME("Stub %p 0x%08lx 0x%08lx %p %p\n", hServer, Reserved, Version, - ppSessionInfo, pCount); + TRACE("%p 0x%08lx 0x%08lx %p %p.\n", server, reserved, version, session_info, count); - if (!ppSessionInfo || !pCount) return FALSE; + if (!session_info || !count) return FALSE; - *pCount = 0; - *ppSessionInfo = NULL; + if (!WTSEnumerateSessionsW(server, reserved, version, &infoW, count)) return FALSE; + + size = 0; + for (i = 0; i < *count; ++i) + { + if (!(len = WideCharToMultiByte(CP_ACP, 0, infoW[i].pWinStationName, -1, NULL, 0, NULL, NULL))) + { + ERR("WideCharToMultiByte failed.\n"); + WTSFreeMemory(infoW); + return FALSE; + } + size += sizeof(**session_info) + len; + } + + if (!(*session_info = heap_alloc(size))) + { + WTSFreeMemory(infoW); + SetLastError(ERROR_OUTOFMEMORY); + return FALSE; + } + offset = *count * sizeof(**session_info); + for (i = 0; i < *count; ++i) + { + (*session_info)[i].State = infoW[i].State; + (*session_info)[i].SessionId = infoW[i].SessionId; + (*session_info)[i].pWinStationName = (char *)(*session_info) + offset; + len = WideCharToMultiByte(CP_ACP, 0, infoW[i].pWinStationName, -1, (*session_info)[i].pWinStationName, + size - offset, NULL, NULL); + if (!len) + { + ERR("WideCharToMultiByte failed.\n"); + WTSFreeMemory(*session_info); + WTSFreeMemory(infoW); + } + offset += len; + } + + WTSFreeMemory(infoW); return TRUE; } /************************************************************ * WTSEnumerateEnumerateSessionsW (WTSAPI32.@) */ -BOOL WINAPI WTSEnumerateSessionsW(HANDLE hServer, DWORD Reserved, DWORD Version, - PWTS_SESSION_INFOW* ppSessionInfo, DWORD* pCount) +BOOL WINAPI WTSEnumerateSessionsW(HANDLE server, DWORD reserved, DWORD version, + PWTS_SESSION_INFOW *session_info, DWORD *count) { - FIXME("Stub %p 0x%08lx 0x%08lx %p %p\n", hServer, Reserved, Version, - ppSessionInfo, pCount); + static const WCHAR session_name[] = L"Console"; - if (!ppSessionInfo || !pCount) return FALSE; + FIXME("%p 0x%08lx 0x%08lx %p %p semi-stub.\n", server, reserved, version, session_info, count); - *pCount = 0; - *ppSessionInfo = NULL; + if (!session_info || !count) return FALSE; + + if (!(*session_info = heap_alloc(sizeof(**session_info) + sizeof(session_name)))) + { + SetLastError(ERROR_OUTOFMEMORY); + return FALSE; + } + if (!ProcessIdToSessionId( GetCurrentProcessId(), &(*session_info)->SessionId)) + { + WTSFreeMemory(*session_info); + return FALSE; + } + *count = 1; + (*session_info)->State = WTSActive; + (*session_info)->pWinStationName = (WCHAR *)((char *)*session_info + sizeof(**session_info)); + memcpy((*session_info)->pWinStationName, session_name, sizeof(session_name)); return TRUE; } @@ -418,17 +468,8 @@ BOOL WINAPI WTSQuerySessionInformationA(HANDLE server, DWORD session_id, WTS_INF return FALSE; } - if (class == WTSClientProtocolType) - { - USHORT *protocol; - - if (!(protocol = heap_alloc(sizeof(*protocol)))) return FALSE; - FIXME("returning 0 protocol type\n"); - *protocol = 0; - *buffer = (char *)protocol; - *count = sizeof(*protocol); - return TRUE; - } + if (class == WTSClientProtocolType || class == WTSConnectState) + return WTSQuerySessionInformationW(server, session_id, class, (WCHAR **)buffer, count); if (!WTSQuerySessionInformationW(server, session_id, class, &bufferW, count)) return FALSE; @@ -470,6 +511,17 @@ BOOL WINAPI WTSQuerySessionInformationW(HANDLE server, DWORD session_id, WTS_INF return FALSE; } + if (class == WTSConnectState) + { + WTS_CONNECTSTATE_CLASS *state; + + if (!(state = heap_alloc(sizeof(*state)))) return FALSE; + *state = WTSActive; + *buffer = (WCHAR *)state; + *count = sizeof(*state); + return TRUE; + } + if (class == WTSClientProtocolType) { USHORT *protocol; diff --git a/include/cfgmgr32.h b/include/cfgmgr32.h index bff32fe1c08..04f1f80b174 100644 --- a/include/cfgmgr32.h +++ b/include/cfgmgr32.h @@ -246,6 +246,7 @@ CMAPI WORD WINAPI CM_Get_Version(void); CMAPI CONFIGRET WINAPI CM_Locate_DevNodeA(PDEVINST,DEVINSTID_A,ULONG); CMAPI CONFIGRET WINAPI CM_Locate_DevNodeW(PDEVINST,DEVINSTID_W,ULONG); #define CM_Locate_DevNode WINELIB_NAME_AW(CM_Locate_DevNode) +CMAPI DWORD WINAPI CM_MapCrToWin32Err(CONFIGRET,DWORD); CMAPI CONFIGRET WINAPI CM_Open_DevNode_Key(DEVINST dnDevInst, REGSAM access, ULONG ulHardwareProfile, REGDISPOSITION disposition, PHKEY phkDevice, ULONG ulFlags); CMAPI CONFIGRET WINAPI CM_Request_Device_EjectA(DEVINST dev, PPNP_VETO_TYPE type, LPSTR name, ULONG length, ULONG flags); diff --git a/include/immdev.h b/include/immdev.h index 92f2a47c167..4141e9350c0 100644 --- a/include/immdev.h +++ b/include/immdev.h @@ -134,6 +134,13 @@ DWORD WINAPI ImmGetIMCCSize(HIMCC); #define IMMGWL_IMC 0 #define IMMGWL_PRIVATE (sizeof(LONG_PTR)) +#define INIT_STATUSWNDPOS 0x00000001 +#define INIT_CONVERSION 0x00000002 +#define INIT_SENTENCE 0x00000004 +#define INIT_LOGFONT 0x00000008 +#define INIT_COMPFORM 0x00000010 +#define INIT_SOFTKBDPOS 0x00000020 + /* IME Property bits */ #define IME_PROP_END_UNLOAD 0x0001 #define IME_PROP_KBD_CHAR_FIRST 0x0002 diff --git a/include/ntuser.h b/include/ntuser.h index 5295c2c2108..5ba3ae1dfd2 100644 --- a/include/ntuser.h +++ b/include/ntuser.h @@ -22,6 +22,7 @@ #include #include #include +#include #include /* KernelCallbackTable codes, not compatible with Windows */ @@ -285,6 +286,9 @@ struct unpack_dde_message_params #define SPY_RESULT_OK 0x0001 #define SPY_RESULT_DEFWND 0x0002 +/* CreateDesktop wine specific flag */ +#define DF_WINE_CREATE_DESKTOP 0x80000000 + /* NtUserMessageCall codes */ enum { @@ -305,6 +309,7 @@ enum NtUserSpyEnter = 0x0304, NtUserSpyExit = 0x0305, NtUserWinProcResult = 0x0306, + NtUserImeDriverCall = 0x0307, }; /* NtUserThunkedMenuItemInfo codes */ @@ -478,6 +483,7 @@ enum wine_internal_message WM_WINE_KEYBOARD_LL_HOOK, WM_WINE_MOUSE_LL_HOOK, WM_WINE_CLIPCURSOR, + WM_WINE_SETCURSOR, WM_WINE_UPDATEWINDOWSTATE, WM_WINE_FIRST_DRIVER_MSG = 0x80001000, /* range of messages reserved for the USER driver */ WM_WINE_LAST_DRIVER_MSG = 0x80001fff @@ -487,6 +493,27 @@ enum wine_internal_message #define WM_IME_INTERNAL 0x287 #define IME_INTERNAL_ACTIVATE 0x17 #define IME_INTERNAL_DEACTIVATE 0x18 +#define IME_INTERNAL_HKL_ACTIVATE 0x19 +#define IME_INTERNAL_HKL_DEACTIVATE 0x20 + +/* internal WM_IME_NOTIFY wparams, not compatible with Windows */ +#define IMN_WINE_SET_OPEN_STATUS 0x000f +#define IMN_WINE_SET_COMP_STRING 0x0010 + +/* builtin IME driver calls */ +enum wine_ime_call +{ + WINE_IME_PROCESS_KEY, + WINE_IME_TO_ASCII_EX, +}; + +/* NtUserImeDriverCall params */ +struct ime_driver_call_params +{ + HIMC himc; + const BYTE *state; + COMPOSITIONSTRING *compstr; +}; #define WM_SYSTIMER 0x0118 @@ -648,6 +675,7 @@ BOOL WINAPI NtUserAddClipboardFormatListener( HWND hwnd ); UINT WINAPI NtUserAssociateInputContext( HWND hwnd, HIMC ctx, ULONG flags ); BOOL WINAPI NtUserAttachThreadInput( DWORD from, DWORD to, BOOL attach ); HDC WINAPI NtUserBeginPaint( HWND hwnd, PAINTSTRUCT *ps ); +NTSTATUS WINAPI NtUserBuildHimcList( UINT thread_id, UINT count, HIMC *buffer, UINT *size ); NTSTATUS WINAPI NtUserBuildHwndList( HDESK desktop, ULONG unk2, ULONG unk3, ULONG unk4, ULONG thread_id, ULONG count, HWND *buffer, ULONG *size ); ULONG_PTR WINAPI NtUserCallHwnd( HWND hwnd, DWORD code ); @@ -807,6 +835,7 @@ LRESULT WINAPI NtUserMessageCall( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpa BOOL WINAPI NtUserMoveWindow( HWND hwnd, INT x, INT y, INT cx, INT cy, BOOL repaint ); DWORD WINAPI NtUserMsgWaitForMultipleObjectsEx( DWORD count, const HANDLE *handles, DWORD timeout, DWORD mask, DWORD flags ); +void WINAPI NtUserNotifyIMEStatus( HWND hwnd, UINT status ); void WINAPI NtUserNotifyWinEvent( DWORD event, HWND hwnd, LONG object_id, LONG child_id ); HWINSTA WINAPI NtUserOpenWindowStation( OBJECT_ATTRIBUTES *attr, ACCESS_MASK access ); BOOL WINAPI NtUserOpenClipboard( HWND hwnd, ULONG unk ); diff --git a/include/sapi.idl b/include/sapi.idl index cd5d6044bdf..16b8348d73b 100644 --- a/include/sapi.idl +++ b/include/sapi.idl @@ -426,6 +426,9 @@ typedef [hidden] enum SPSTREAMFORMAT SPSF_NUM_FORMATS } SPSTREAMFORMAT; +cpp_quote("EXTERN_C const GUID SPDFID_Text;") +cpp_quote("EXTERN_C const GUID SPDFID_WaveFormatEx;") + typedef unsigned short SPPHONEID; typedef [restricted, hidden] struct SPPHRASEELEMENT @@ -574,6 +577,59 @@ typedef [restricted, hidden] struct SPAUDIOBUFFERINFO ULONG ulMsEventBias; } SPAUDIOBUFFERINFO; +typedef [hidden] enum SPPARTOFSPEECH +{ + SPPS_NotOverriden = -1, + SPPS_Unknown = 0, + SPPS_Noun = 0x1000, + SPPS_Verb = 0x2000, + SPPS_Modifier = 0x3000, + SPPS_Function = 0x4000, + SPPS_Interjection = 0x5000, + SPPS_Noncontent = 0x6000, + SPPS_LMA = 0x7000, + SPPS_SuppressWord = 0xF000 +} SPPARTOFSPEECH; + +typedef [restricted, hidden] struct SPVPITCH +{ + long MiddleAdj; + long RangeAdj; +} SPVPITCH; + +typedef [hidden] enum SPVACTIONS +{ + SPVA_Speak = 0, + SPVA_Silence, + SPVA_Pronounce, + SPVA_Bookmark, + SPVA_SpellOut, + SPVA_Section, + SPVA_ParseUnknownTag +} SPVACTIONS; + +typedef [restricted, hidden] struct SPVCONTEXT +{ + LPCWSTR pCategory; + LPCWSTR pBefore; + LPCWSTR pAfter; +} SPVCONTEXT; + +typedef [restricted, hidden] struct SPVSTATE +{ + SPVACTIONS eAction; + LANGID LangID; + WORD wReserved; + long EmphAdj; + long RateAdj; + ULONG Volume; + SPVPITCH PitchAdj; + ULONG SilenceMSecs; + SPPHONEID *pPhoneIds; + SPPARTOFSPEECH ePartOfSpeech; + SPVCONTEXT Context; +} SPVSTATE; + cpp_quote("#if defined(__GNUC__)") cpp_quote("#define SPCAT_AUDIOOUT (const WCHAR []){ 'H','K','E','Y','_','L','O','C','A','L','_','M','A','C','H','I','N','E','\\\\','S','O','F','T','W','A','R','E','\\\\','M','i','c','r','o','s','o','f','t','\\\\','S','p','e','e','c','h','\\\\','A','u','d','i','o','O','u','t','p','u','t',0 }") diff --git a/include/sapiddk.idl b/include/sapiddk.idl index 670b8c0dce5..8f9abf4e117 100644 --- a/include/sapiddk.idl +++ b/include/sapiddk.idl @@ -49,6 +49,69 @@ interface ISpObjectTokenEnumBuilder : IEnumSpObjectTokens HRESULT Sort([in] LPCWSTR pszTokenIdToListFirst); } +typedef enum SPVSKIPTYPE +{ + SPVST_SENTENCE = (1L << 0) +} SPVSKIPTYPE; + +typedef enum SPVESACTIONS +{ + SPVES_CONTINUE = 0, + SPVES_ABORT = (1L << 0), + SPVES_SKIP = (1L << 1), + SPVES_RATE = (1L << 2), + SPVES_VOLUME = (1L << 3) +} SPVESACTIONS; + +[ + object, + uuid(9880499b-cce9-11d2-b503-00c04f797396), + helpstring("ISpTTSEngineSite"), + pointer_default(unique), + local +] +interface ISpTTSEngineSite : ISpEventSink +{ + DWORD GetActions(); + HRESULT Write([in] const void *pBuff, + [in] ULONG cb, + [out] ULONG *pcbWritten); + HRESULT GetRate([out] long *pRateAdjust); + HRESULT GetVolume([out] USHORT *pusVolume); + HRESULT GetSkipInfo([out] SPVSKIPTYPE *peType, + [out] long *plNumItems); + HRESULT CompleteSkip([in] long lNumSkipped); +}; + +typedef struct SPVTEXTFRAG +{ + struct SPVTEXTFRAG* pNext; + SPVSTATE State; + LPCWSTR pTextStart; + ULONG ulTextLen; + ULONG ulTextSrcOffset; +} SPVTEXTFRAG; + +[ + object, + uuid(a74d7c8e-4cc5-4f2f-a6eb-804dee18500e), + helpstring("ISpTTSEngine"), + pointer_default(unique), + local +] +interface ISpTTSEngine : IUnknown +{ + HRESULT Speak([in] DWORD dwSpeakFlags, + [in] REFGUID rguidFormatId, + [in] const WAVEFORMATEX *pWaveFormatEx, + [in] const SPVTEXTFRAG *pTextFragList, + [in] ISpTTSEngineSite *pOutputSite); + HRESULT GetOutputFormat([in] const GUID *pTargetFmtId, + [in] const WAVEFORMATEX *pTargetWaveFormatEx, + [out] GUID *pOutputFormatId, + [out] WAVEFORMATEX **ppCoMemOutputWaveFormatEx); +}; + [ helpstring("Speech Object DDK Library"), uuid(9903f14c-12ce-4c99-9986-2ee3d7d588a8), diff --git a/include/sperror.h b/include/sperror.h index cd8ed9b948d..0e37ac91c87 100644 --- a/include/sperror.h +++ b/include/sperror.h @@ -47,6 +47,7 @@ #define SPERR_UNINITIALIZED 0x80045001 #define SPERR_ALREADY_INITIALIZED 0x80045002 +#define SPERR_UNSUPPORTED_FORMAT 0x80045003 #define SPERR_INVALID_FLAGS 0x80045004 #define SPERR_DEVICE_BUSY 0x80045006 #define SPERR_DEVICE_NOT_SUPPORTED 0x80045007 diff --git a/include/winbase.h b/include/winbase.h index 9859e22598d..5a2170882a1 100644 --- a/include/winbase.h +++ b/include/winbase.h @@ -2806,6 +2806,7 @@ WINBASEAPI BOOL WINAPI VirtualUnlock(LPVOID,SIZE_T); WINBASEAPI DWORD WINAPI WTSGetActiveConsoleSessionId(void); WINBASEAPI BOOL WINAPI WaitCommEvent(HANDLE,LPDWORD,LPOVERLAPPED); WINBASEAPI BOOL WINAPI WaitForDebugEvent(LPDEBUG_EVENT,DWORD); +WINBASEAPI BOOL WINAPI WaitForDebugEventEx(LPDEBUG_EVENT,DWORD); WINBASEAPI DWORD WINAPI WaitForMultipleObjects(DWORD,const HANDLE*,BOOL,DWORD); WINBASEAPI DWORD WINAPI WaitForMultipleObjectsEx(DWORD,const HANDLE*,BOOL,DWORD,BOOL); WINBASEAPI DWORD WINAPI WaitForSingleObject(HANDLE,DWORD); diff --git a/include/wine/gdi_driver.h b/include/wine/gdi_driver.h index 96bedd7acab..60c3779727b 100644 --- a/include/wine/gdi_driver.h +++ b/include/wine/gdi_driver.h @@ -23,6 +23,7 @@ #include "winternl.h" #include "ntuser.h" +#include "immdev.h" #include "ddk/d3dkmthk.h" #include "wine/list.h" @@ -286,12 +287,16 @@ struct user_driver_funcs INT (*pToUnicodeEx)(UINT,UINT,const BYTE *,LPWSTR,int,UINT,HKL); void (*pUnregisterHotKey)(HWND, UINT, UINT); SHORT (*pVkKeyScanEx)(WCHAR, HKL); + /* IME functions */ + UINT (*pImeProcessKey)(HIMC,UINT,UINT,const BYTE*); + UINT (*pImeToAsciiEx)(UINT,UINT,const BYTE*,COMPOSITIONSTRING*,HIMC); + void (*pNotifyIMEStatus)(HWND,UINT); /* cursor/icon functions */ void (*pDestroyCursorIcon)(HCURSOR); - void (*pSetCursor)(HCURSOR); + void (*pSetCursor)(HWND,HCURSOR); BOOL (*pGetCursorPos)(LPPOINT); BOOL (*pSetCursorPos)(INT,INT); - BOOL (*pClipCursor)(LPCRECT); + BOOL (*pClipCursor)(const RECT*,BOOL); /* clipboard functions */ LRESULT (*pClipboardWindowProc)(HWND,UINT,WPARAM,LPARAM); void (*pUpdateClipboard)(void); @@ -301,7 +306,7 @@ struct user_driver_funcs INT (*pGetDisplayDepth)(LPCWSTR,BOOL); BOOL (*pUpdateDisplayDevices)(const struct gdi_device_manager *,BOOL,void*); /* windowing functions */ - BOOL (*pCreateDesktopWindow)(HWND); + BOOL (*pCreateDesktop)(const WCHAR *,UINT,UINT); BOOL (*pCreateWindow)(HWND); LRESULT (*pDesktopWindowProc)(HWND,UINT,WPARAM,LPARAM); void (*pDestroyWindow)(HWND); @@ -311,6 +316,7 @@ struct user_driver_funcs void (*pReleaseDC)(HWND,HDC); BOOL (*pScrollDC)(HDC,INT,INT,HRGN); void (*pSetCapture)(HWND,UINT); + void (*pSetDesktopWindow)(HWND); void (*pSetFocus)(HWND); void (*pSetLayeredWindowAttributes)(HWND,COLORREF,BYTE,DWORD); void (*pSetParent)(HWND,HWND,HWND); diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h index 472c0ea709d..2c8189b79cf 100644 --- a/include/wine/server_protocol.h +++ b/include/wine/server_protocol.h @@ -5281,8 +5281,6 @@ struct set_cursor_request int x; int y; rectangle_t clip; - unsigned int clip_msg; - char __pad_52[4]; }; struct set_cursor_reply { @@ -5302,6 +5300,7 @@ struct set_cursor_reply #define SET_CURSOR_POS 0x04 #define SET_CURSOR_CLIP 0x08 #define SET_CURSOR_NOCLIP 0x10 +#define SET_CURSOR_FSCLIP 0x20 struct get_cursor_history_request diff --git a/include/wine/wine_common_ver.rc b/include/wine/wine_common_ver.rc index 95ab04666e8..d79062416ed 100644 --- a/include/wine/wine_common_ver.rc +++ b/include/wine/wine_common_ver.rc @@ -115,6 +115,12 @@ never complain. #define WINE_CODEPAGE_HEX WINE_VER_HEXPREFIX(WINE_CODEPAGE) #endif +#ifndef WINE_LANGID +#define WINE_LANGID 0409 /* LANG_ENGLISH/SUBLANG_DEFAULT */ +#endif +#define WINE_LANGID_STR WINE_VER_STRINGIZE(WINE_LANGID) +#define WINE_LANGID_HEX WINE_VER_HEXPREFIX(WINE_LANGID) + VS_VERSION_INFO VERSIONINFO FILEVERSION WINE_FILEVERSION PRODUCTVERSION WINE_PRODUCTVERSION @@ -126,8 +132,7 @@ FILESUBTYPE WINE_FILESUBTYPE { BLOCK "StringFileInfo" { - /* LANG_ENGLISH/SUBLANG_DEFAULT, WINE_CODEPAGE */ - BLOCK "0409" WINE_CODEPAGE_STR + BLOCK WINE_LANGID_STR WINE_CODEPAGE_STR { VALUE "CompanyName", "Microsoft Corporation" /* GameGuard depends on this */ VALUE "FileDescription", WINE_FILEDESCRIPTION_STR @@ -142,7 +147,6 @@ FILESUBTYPE WINE_FILESUBTYPE } BLOCK "VarFileInfo" { - /* LANG_ENGLISH/SUBLANG_DEFAULT, WINE_CODEPAGE */ - VALUE "Translation", 0x0409, WINE_CODEPAGE_HEX + VALUE "Translation", WINE_LANGID_HEX, WINE_CODEPAGE_HEX } } diff --git a/loader/wine.inf.in b/loader/wine.inf.in index 83397bbb9b9..e31c426ca04 100644 --- a/loader/wine.inf.in +++ b/loader/wine.inf.in @@ -420,6 +420,7 @@ HKLM,%CurrentVersionNT%\ProfileList,,16 HKLM,%CurrentVersionNT%\Winlogon,"Shell",,"explorer.exe" ;; App specific heap debug flags HKLM,%CurrentVersionNT%\Image File Execution Options\ChaosCode.exe,GlobalFlag,0x00040002,0x00000020 +HKLM,%CurrentVersionNT%\Image File Execution Options\Crysis2Remastered.exe,GlobalFlag,0x00040002,0x00000020 [CurrentVersionWow64] HKLM,%CurrentVersion%,"ProgramFilesDir (x86)",,"%16426%" @@ -2339,7 +2340,7 @@ system.ini, drivers32,,"msacm.msgsm610=msgsm32.acm" system.ini, drivers32,,"vidc.mrle=msrle32.dll" system.ini, drivers32,,"vidc.msvc=msvidc32.dll" system.ini, drivers32,,"vidc.cvid=iccvid.dll" -system.ini, drivers32,,"; vidc.IV50=ir50_32.dll" +system.ini, drivers32,,"vidc.IV50=ir50_32.dll" system.ini, drivers32,,"; vidc.IV31=ir32_32.dll" system.ini, drivers32,,"; vidc.IV32=ir32_32.dll" diff --git a/po/ar.po b/po/ar.po index b87d6151a2f..5fdad68ed12 100644 --- a/po/ar.po +++ b/po/ar.po @@ -3732,6 +3732,18 @@ msgstr "زائد" msgid "High" msgstr "عالي" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "الفهرس" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "ترميز واين المرئي الأول" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "مقابض اللعب" @@ -4117,11 +4129,11 @@ msgstr "'[object]' ليس عنصر تاريخ" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "واين" diff --git a/po/ast.po b/po/ast.po index 91d7d256182..359de815a1a 100644 --- a/po/ast.po +++ b/po/ast.po @@ -3626,6 +3626,14 @@ msgstr "" msgid "High" msgstr "" +#: dlls/ir50_32/ir50_32.rc:28 +msgid "Indeo5" +msgstr "" + +#: dlls/ir50_32/ir50_32.rc:29 +msgid "Indeo Video Interactive version 5 video codec" +msgstr "" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Joysticks" @@ -3988,11 +3996,11 @@ msgstr "" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/bg.po b/po/bg.po index 47e12a893e8..cac408aef44 100644 --- a/po/bg.po +++ b/po/bg.po @@ -3744,6 +3744,16 @@ msgstr "" msgid "High" msgstr "" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +msgid "Indeo5" +msgstr "&Съдържание" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine MS-RLE видео кодек" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -4115,11 +4125,11 @@ msgstr "" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 #, fuzzy msgid "Wine" diff --git a/po/ca.po b/po/ca.po index 90ee5673f7a..00cb01b7ce2 100644 --- a/po/ca.po +++ b/po/ca.po @@ -3724,6 +3724,18 @@ msgstr "Elevat" msgid "High" msgstr "Alt" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Índex" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Còdec de vídeo Wine Video 1" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Palanques de control" @@ -4089,11 +4101,11 @@ msgstr "'this' no és un objecte de |" msgid "Property cannot have both accessors and a value" msgstr "La propietat no pot tenir ambdós mètodes d'accés i un valor" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "DLL de nucli del Wine" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/cs.po b/po/cs.po index 25a4f1b75e3..b2c53f1d99a 100644 --- a/po/cs.po +++ b/po/cs.po @@ -3681,6 +3681,18 @@ msgstr "Zvýšená" msgid "High" msgstr "Vysoká" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Rejstřík" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Videokodek Wine Video 1" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Pákové ovladače" @@ -4058,11 +4070,11 @@ msgstr "„%s“ není platný název portu" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/da.po b/po/da.po index c7ee34b8ff9..53aa5e9ac72 100644 --- a/po/da.po +++ b/po/da.po @@ -3763,6 +3763,18 @@ msgstr "Øget" msgid "High" msgstr "&Høj" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Indeks" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine Video 1 videokodeks" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -4154,11 +4166,11 @@ msgstr "«[objekt]» er ikke et dato objekt" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/de.po b/po/de.po index 1ed7acd9d05..e970e9175df 100644 --- a/po/de.po +++ b/po/de.po @@ -3714,6 +3714,18 @@ msgstr "Erhöht" msgid "High" msgstr "Hoch" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Index" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine-Video-1-Videocodec" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Joysticks" @@ -4077,11 +4089,11 @@ msgstr "'this' ist kein |-Objekt" msgid "Property cannot have both accessors and a value" msgstr "Eigenschaft kann nicht sowohl Accessoren als auch einen Wert haben" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "Wine-Kernel-DLL" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/el.po b/po/el.po index c5c8307e2cb..f4ca06d96fb 100644 --- a/po/el.po +++ b/po/el.po @@ -3661,6 +3661,14 @@ msgstr "" msgid "High" msgstr "" +#: dlls/ir50_32/ir50_32.rc:28 +msgid "Indeo5" +msgstr "" + +#: dlls/ir50_32/ir50_32.rc:29 +msgid "Indeo Video Interactive version 5 video codec" +msgstr "" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -4017,11 +4025,11 @@ msgstr "" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "" diff --git a/po/en.po b/po/en.po index a9ad0a9cb0a..a5425b52486 100644 --- a/po/en.po +++ b/po/en.po @@ -3707,6 +3707,14 @@ msgstr "Increased" msgid "High" msgstr "High" +#: dlls/ir50_32/ir50_32.rc:28 +msgid "Indeo5" +msgstr "Indeo5" + +#: dlls/ir50_32/ir50_32.rc:29 +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Indeo Video Interactive version 5 video codec" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Joysticks" @@ -4068,11 +4076,11 @@ msgstr "'this' is not a | object" msgid "Property cannot have both accessors and a value" msgstr "Property cannot have both accessors and a value" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "Wine kernel DLL" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/en_US.po b/po/en_US.po index ab15878a97b..0735be69271 100644 --- a/po/en_US.po +++ b/po/en_US.po @@ -3707,6 +3707,14 @@ msgstr "Increased" msgid "High" msgstr "High" +#: dlls/ir50_32/ir50_32.rc:28 +msgid "Indeo5" +msgstr "Indeo5" + +#: dlls/ir50_32/ir50_32.rc:29 +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Indeo Video Interactive version 5 video codec" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Joysticks" @@ -4068,11 +4076,11 @@ msgstr "'this' is not a | object" msgid "Property cannot have both accessors and a value" msgstr "Property cannot have both accessors and a value" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "Wine kernel DLL" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/eo.po b/po/eo.po index 050cd952e48..f2ff3e010a1 100644 --- a/po/eo.po +++ b/po/eo.po @@ -3649,6 +3649,16 @@ msgstr "" msgid "High" msgstr "" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Indekso" + +#: dlls/ir50_32/ir50_32.rc:29 +msgid "Indeo Video Interactive version 5 video codec" +msgstr "" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -4024,11 +4034,11 @@ msgstr "" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/es.po b/po/es.po index c19a7daabc1..4f363add9c4 100644 --- a/po/es.po +++ b/po/es.po @@ -3727,6 +3727,18 @@ msgstr "Aumentada" msgid "High" msgstr "Alta" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Índice" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Códec de vídeo Wine Video 1" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Comando de juego" @@ -4094,11 +4106,11 @@ msgstr "'[this]' no es un objeto Map" msgid "Property cannot have both accessors and a value" msgstr "La propiedad no puede tener tanto descriptores de acceso como un valor" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "DLL de núcle Wine" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/fa.po b/po/fa.po index 40491717d00..2ea2a88163a 100644 --- a/po/fa.po +++ b/po/fa.po @@ -3692,6 +3692,14 @@ msgstr "" msgid "High" msgstr "" +#: dlls/ir50_32/ir50_32.rc:28 +msgid "Indeo5" +msgstr "" + +#: dlls/ir50_32/ir50_32.rc:29 +msgid "Indeo Video Interactive version 5 video codec" +msgstr "" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -4044,11 +4052,11 @@ msgstr "" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "" diff --git a/po/fi.po b/po/fi.po index 51f6dc4736e..dc90507d60e 100644 --- a/po/fi.po +++ b/po/fi.po @@ -3701,6 +3701,18 @@ msgstr "Korotettu" msgid "High" msgstr "Korkea" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Sisällys" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Winen Video 1 -videokoodekki" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Joystickit" @@ -4061,11 +4073,11 @@ msgstr "'this' ei ole |-objekti" msgid "Property cannot have both accessors and a value" msgstr "Ominaisuudella ei voi olla sekä hakufunktiota että arvoa" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "Winen ydin-DLL" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/fr.po b/po/fr.po index b3d2ea080c0..b00b7235c3e 100644 --- a/po/fr.po +++ b/po/fr.po @@ -3732,6 +3732,18 @@ msgstr "Augmentée" msgid "High" msgstr "Haute" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Index" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Codec vidéo Wine Video 1" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Joysticks" @@ -4086,11 +4098,11 @@ msgstr "« this » n'est pas un objet de type Map" msgid "Property cannot have both accessors and a value" msgstr "La propriété ne peut à la fois avoir une valeur et des accesseurs" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "DLL noyau de Wine" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/he.po b/po/he.po index 2e716c65960..58dcefcebd4 100644 --- a/po/he.po +++ b/po/he.po @@ -3727,6 +3727,18 @@ msgstr "מוגברת" msgid "High" msgstr "גבוהה" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "מפתח" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "מקודד הווידאו Video 1 של Wine" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -4108,11 +4120,11 @@ msgstr "'%s' אינו שם תקני לפתחה" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/hi.po b/po/hi.po index 08201ec785b..7663549c7a2 100644 --- a/po/hi.po +++ b/po/hi.po @@ -3625,6 +3625,14 @@ msgstr "" msgid "High" msgstr "" +#: dlls/ir50_32/ir50_32.rc:28 +msgid "Indeo5" +msgstr "" + +#: dlls/ir50_32/ir50_32.rc:29 +msgid "Indeo Video Interactive version 5 video codec" +msgstr "" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -3973,11 +3981,11 @@ msgstr "" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "" diff --git a/po/hr.po b/po/hr.po index cab03b4c47b..f04c70940fa 100644 --- a/po/hr.po +++ b/po/hr.po @@ -3737,6 +3737,18 @@ msgstr "Povećane" msgid "High" msgstr "Visoke" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Indeks" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine Video 1 video codec" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Joystici" @@ -4122,11 +4134,11 @@ msgstr "'[object]' nije vremenski objekt" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/hu.po b/po/hu.po index 6b70faab001..24c0eeecd35 100644 --- a/po/hu.po +++ b/po/hu.po @@ -3779,6 +3779,18 @@ msgstr "Megnövelt" msgid "High" msgstr "Magas" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "&Témakörök" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine Video 1 video kodek" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -4172,11 +4184,11 @@ msgstr "'Az [object]' nem egy date (dátum) objektum" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine súgó" diff --git a/po/it.po b/po/it.po index d2bf050bd81..0449017ac23 100644 --- a/po/it.po +++ b/po/it.po @@ -3787,6 +3787,18 @@ msgstr "Aumentato" msgid "High" msgstr "Alta" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Indice" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Codec Video Wine Video 1" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -4180,11 +4192,11 @@ msgstr "'[oggetto]' non è un oggetto data" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/ja.po b/po/ja.po index ee177c0aaa8..43bf855ddcd 100644 --- a/po/ja.po +++ b/po/ja.po @@ -3699,6 +3699,18 @@ msgstr "中高" msgid "High" msgstr "高" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "索引" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine ビデオ 1 ビデオコーデック" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "ジョイスティック" @@ -4060,11 +4072,11 @@ msgstr "'this' は | オブジェクトではありません" msgid "Property cannot have both accessors and a value" msgstr "プロパティはアクセサーと値の両方になることはできません" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/ko.po b/po/ko.po index fde0ddc1d3b..c1272917c81 100644 --- a/po/ko.po +++ b/po/ko.po @@ -3689,6 +3689,18 @@ msgstr "증가" msgid "High" msgstr "높음" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "인덱스" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine 비디오 1 비디오 코덱" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "조이스틱" @@ -4049,11 +4061,11 @@ msgstr "'this'는 '|' 개체가 아닙니다" msgid "Property cannot have both accessors and a value" msgstr "속성에 접근자와 값을 둘 다 지정할 수는 없습니다" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "Wine 커널 DLL" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/lt.po b/po/lt.po index 2fe2909a0d6..e4f347061d6 100644 --- a/po/lt.po +++ b/po/lt.po @@ -3710,6 +3710,18 @@ msgstr "Padidintos" msgid "High" msgstr "Aukštos" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Indeksas" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "„Wine“ Video 1 vaizdo kodekas" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Vairasvirtės" @@ -4071,11 +4083,11 @@ msgstr "„Šis“ nėra | objektas" msgid "Property cannot have both accessors and a value" msgstr "Savybė negali turėti ir kreipiklių, ir reikšmės" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "Wine branduolio DLL" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/ml.po b/po/ml.po index 29b316c025c..b612d8ef96e 100644 --- a/po/ml.po +++ b/po/ml.po @@ -3627,6 +3627,14 @@ msgstr "" msgid "High" msgstr "" +#: dlls/ir50_32/ir50_32.rc:28 +msgid "Indeo5" +msgstr "" + +#: dlls/ir50_32/ir50_32.rc:29 +msgid "Indeo Video Interactive version 5 video codec" +msgstr "" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -3975,11 +3983,11 @@ msgstr "" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "" diff --git a/po/nb_NO.po b/po/nb_NO.po index ec59d534991..da72e8d86f8 100644 --- a/po/nb_NO.po +++ b/po/nb_NO.po @@ -3715,6 +3715,18 @@ msgstr "Økt" msgid "High" msgstr "Høy" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Innhold" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine Video 1 videokodeks" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Styrespaker" @@ -4092,11 +4104,11 @@ msgstr "'[object]' er ikke et dataobjekt" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "Wine kjerne-DLL" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/nl.po b/po/nl.po index b571b48a26b..b6df43c1006 100644 --- a/po/nl.po +++ b/po/nl.po @@ -3720,6 +3720,18 @@ msgstr "Verhoogd" msgid "High" msgstr "Hoog" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Index" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine Video 1 video codec" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Joysticks" @@ -4081,11 +4093,11 @@ msgstr "'this' is geen | object" msgid "Property cannot have both accessors and a value" msgstr "Eigenschap kan niet zowel accessors als een waarde hebben" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "Wine kernel DLL" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/or.po b/po/or.po index 4efb1620ccb..f380aee4c32 100644 --- a/po/or.po +++ b/po/or.po @@ -3625,6 +3625,14 @@ msgstr "" msgid "High" msgstr "" +#: dlls/ir50_32/ir50_32.rc:28 +msgid "Indeo5" +msgstr "" + +#: dlls/ir50_32/ir50_32.rc:29 +msgid "Indeo Video Interactive version 5 video codec" +msgstr "" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -3973,11 +3981,11 @@ msgstr "" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "" diff --git a/po/pa.po b/po/pa.po index fb970b7c963..02417daffe3 100644 --- a/po/pa.po +++ b/po/pa.po @@ -3625,6 +3625,14 @@ msgstr "" msgid "High" msgstr "" +#: dlls/ir50_32/ir50_32.rc:28 +msgid "Indeo5" +msgstr "" + +#: dlls/ir50_32/ir50_32.rc:29 +msgid "Indeo Video Interactive version 5 video codec" +msgstr "" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -3973,11 +3981,11 @@ msgstr "" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "" diff --git a/po/pl.po b/po/pl.po index 723493afaa2..9689ee339e0 100644 --- a/po/pl.po +++ b/po/pl.po @@ -3726,6 +3726,18 @@ msgstr "Wysoki" msgid "High" msgstr "Najwyższy" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Indeks" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Kodek Wine Video 1" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Joysticki" @@ -4093,11 +4105,11 @@ msgstr "'this' nie jest obiektem Map" msgid "Property cannot have both accessors and a value" msgstr "Własność nie może mieć zarówno akcesorów i wartości" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "DLL jądra Wine" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/pt_BR.po b/po/pt_BR.po index d6821db3689..15079a024e1 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -3722,6 +3722,18 @@ msgstr "Elevada" msgid "High" msgstr "Alta" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Índice" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Codec de vídeo Wine Video 1" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Controles" @@ -4089,11 +4101,11 @@ msgstr "'this' não é um objeto Map" msgid "Property cannot have both accessors and a value" msgstr "Propriedade não pode ter ambos acessores e valor" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "Biblioteca de kernel Wine" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/pt_PT.po b/po/pt_PT.po index 833b8b23ade..55807dedfdd 100644 --- a/po/pt_PT.po +++ b/po/pt_PT.po @@ -3763,6 +3763,18 @@ msgstr "Aumentada" msgid "High" msgstr "Alta" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Índice" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "codec video Wine Video 1" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -4140,11 +4152,11 @@ msgstr "'[object]' não é um objecto de data" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/rm.po b/po/rm.po index 412e3dc6c5e..eb130bb02c2 100644 --- a/po/rm.po +++ b/po/rm.po @@ -3653,6 +3653,15 @@ msgstr "" msgid "High" msgstr "" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +msgid "Indeo5" +msgstr "&Cuntgn�" + +#: dlls/ir50_32/ir50_32.rc:29 +msgid "Indeo Video Interactive version 5 video codec" +msgstr "" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -4002,11 +4011,11 @@ msgstr "" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 #, fuzzy msgid "Wine" diff --git a/po/ro.po b/po/ro.po index 6c82bb5c603..d042fde175b 100644 --- a/po/ro.po +++ b/po/ro.po @@ -3720,6 +3720,18 @@ msgstr "Mărit" msgid "High" msgstr "Înalt" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Index" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Codecul video Wine Video 1" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Joystick-uri" @@ -4095,11 +4107,11 @@ msgstr "„[obiect]” nu este un obiect de tip dată" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/ru.po b/po/ru.po index cb918603b72..483e925f005 100644 --- a/po/ru.po +++ b/po/ru.po @@ -3725,6 +3725,18 @@ msgstr "Повышенный" msgid "High" msgstr "Высокий" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Указатель" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Видео кодек Wine Video 1" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Джойстики" @@ -4096,11 +4108,11 @@ msgstr "«this» не объект типа «Map»" msgid "Property cannot have both accessors and a value" msgstr "Свойство не может одновременно иметь методы для доступа и значение" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "Библиотека ядра Wine" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/si.po b/po/si.po index bb39137afd5..395e7c3aa23 100644 --- a/po/si.po +++ b/po/si.po @@ -3652,6 +3652,18 @@ msgstr "" msgid "High" msgstr "වැඩි" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "දර්ශකය" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine වීඩියෝ 1 වීඩියෝ කොඩෙක්" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "නියාමක යටි" @@ -4025,11 +4037,11 @@ msgstr "'%s' වලංගු තොට නමක් නෙමෙයි." msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "Wine කර්නලයේ DLL" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/sk.po b/po/sk.po index c018db3e215..5a74fd54a99 100644 --- a/po/sk.po +++ b/po/sk.po @@ -3694,6 +3694,16 @@ msgstr "Zvýšené" msgid "High" msgstr "Vysoké" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Obsah" + +#: dlls/ir50_32/ir50_32.rc:29 +msgid "Indeo Video Interactive version 5 video codec" +msgstr "" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -4062,11 +4072,11 @@ msgstr "" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/sl.po b/po/sl.po index 02638b23811..f2a06472681 100644 --- a/po/sl.po +++ b/po/sl.po @@ -3781,6 +3781,18 @@ msgstr "Povečano" msgid "High" msgstr "Visoka" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Kazalo" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine Video 1 video kodek" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -4174,11 +4186,11 @@ msgstr "'[object]' ni predmet datuma" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/sr_RS@cyrillic.po b/po/sr_RS@cyrillic.po index 63e76fa78f5..3f6bf0232b1 100644 --- a/po/sr_RS@cyrillic.po +++ b/po/sr_RS@cyrillic.po @@ -3762,6 +3762,17 @@ msgstr "" msgid "High" msgstr "" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +msgid "Indeo5" +msgstr "&Попис" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine Video 1 видео кодек" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -4150,11 +4161,11 @@ msgstr "„[object]“ није временски објекат" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/sr_RS@latin.po b/po/sr_RS@latin.po index e0977ce9ac3..ea6b3aecae5 100644 --- a/po/sr_RS@latin.po +++ b/po/sr_RS@latin.po @@ -3846,6 +3846,18 @@ msgstr "" msgid "High" msgstr "" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Index" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine Video 1 video kodek" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -4235,11 +4247,11 @@ msgstr "„[object]“ nije vremenski objekat" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/sv.po b/po/sv.po index 5f4397962bf..c4ed81856f5 100644 --- a/po/sv.po +++ b/po/sv.po @@ -3742,6 +3742,18 @@ msgstr "Ökad" msgid "High" msgstr "Hög" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Index" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine Video 1 videokodek" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Joysticks" @@ -4119,11 +4131,11 @@ msgstr "'[object]' är inte ett datumobjekt" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "Wine-kärn-DLL" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/ta.po b/po/ta.po index 2d1c803e1ad..656f7a07b3a 100644 --- a/po/ta.po +++ b/po/ta.po @@ -3589,6 +3589,16 @@ msgstr "" msgid "High" msgstr "" +#: dlls/ir50_32/ir50_32.rc:28 +msgid "Indeo5" +msgstr "" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine வீடியோ 1 வீடியோ கோடெக்" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -3938,11 +3948,11 @@ msgstr "" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "" diff --git a/po/te.po b/po/te.po index c0c7e8116a0..8b69b08e201 100644 --- a/po/te.po +++ b/po/te.po @@ -3625,6 +3625,14 @@ msgstr "" msgid "High" msgstr "" +#: dlls/ir50_32/ir50_32.rc:28 +msgid "Indeo5" +msgstr "" + +#: dlls/ir50_32/ir50_32.rc:29 +msgid "Indeo Video Interactive version 5 video codec" +msgstr "" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -3973,11 +3981,11 @@ msgstr "" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "" diff --git a/po/th.po b/po/th.po index 8f563d508b7..41de65e1d68 100644 --- a/po/th.po +++ b/po/th.po @@ -3680,6 +3680,14 @@ msgstr "" msgid "High" msgstr "" +#: dlls/ir50_32/ir50_32.rc:28 +msgid "Indeo5" +msgstr "" + +#: dlls/ir50_32/ir50_32.rc:29 +msgid "Indeo Video Interactive version 5 video codec" +msgstr "" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -4039,11 +4047,11 @@ msgstr "" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "" diff --git a/po/tr.po b/po/tr.po index 28cee4524cd..0a6e29835f6 100644 --- a/po/tr.po +++ b/po/tr.po @@ -3716,6 +3716,18 @@ msgstr "Arttırılmış" msgid "High" msgstr "Yüksek" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "İçindekiler" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine Video 1 video çözücü" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Oyun Kolları" @@ -4087,11 +4099,11 @@ msgstr "'[object]' bir tarih nesnesi değil" msgid "Property cannot have both accessors and a value" msgstr "Nesnenin erişimcisi ve değeri birden olamaz" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "Wine çekirdek DLL'si" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/uk.po b/po/uk.po index 2d941b5248f..57307a18199 100644 --- a/po/uk.po +++ b/po/uk.po @@ -3713,6 +3713,18 @@ msgstr "Збільшені" msgid "High" msgstr "Високі" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "Вказівник" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Відео кодек Wine Video 1" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "Джойстик" @@ -4089,11 +4101,11 @@ msgstr "'це' не є Map об'єкта" msgid "Property cannot have both accessors and a value" msgstr "Властивість не може одночасно мати доступ і значення" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "Бібліотека ядра Wine" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/wa.po b/po/wa.po index 13d79d4eeca..891cf5fb6e3 100644 --- a/po/wa.po +++ b/po/wa.po @@ -3688,6 +3688,15 @@ msgstr "" msgid "High" msgstr "" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +msgid "Indeo5" +msgstr "Å&dvins" + +#: dlls/ir50_32/ir50_32.rc:29 +msgid "Indeo Video Interactive version 5 video codec" +msgstr "" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -4039,11 +4048,11 @@ msgstr "" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 #, fuzzy msgid "Wine" diff --git a/po/wine.pot b/po/wine.pot index 881b048e33b..5241318e3bf 100644 --- a/po/wine.pot +++ b/po/wine.pot @@ -3580,6 +3580,14 @@ msgstr "" msgid "High" msgstr "" +#: dlls/ir50_32/ir50_32.rc:28 +msgid "Indeo5" +msgstr "" + +#: dlls/ir50_32/ir50_32.rc:29 +msgid "Indeo Video Interactive version 5 video codec" +msgstr "" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "" @@ -3927,11 +3935,11 @@ msgstr "" msgid "Property cannot have both accessors and a value" msgstr "" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "" diff --git a/po/zh_CN.po b/po/zh_CN.po index f226c3b1ced..022dd3369a4 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -3657,6 +3657,18 @@ msgstr "较高" msgid "High" msgstr "高" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "索引" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine Video 1 视频编解码器" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "操纵杆" @@ -4014,11 +4026,11 @@ msgstr "'this' 不是 | 对象" msgid "Property cannot have both accessors and a value" msgstr "属性不能同时包含存取器和值" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "Wine kernel DLL" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/po/zh_TW.po b/po/zh_TW.po index 2a93c59445a..3d888ac2790 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -3663,6 +3663,18 @@ msgstr "稍高" msgid "High" msgstr "高" +#: dlls/ir50_32/ir50_32.rc:28 +#, fuzzy +#| msgid "Index" +msgid "Indeo5" +msgstr "索引" + +#: dlls/ir50_32/ir50_32.rc:29 +#, fuzzy +#| msgid "Wine Video 1 video codec" +msgid "Indeo Video Interactive version 5 video codec" +msgstr "Wine 視訊 1 視訊編碼解碼器" + #: dlls/joy.cpl/joy.rc:37 msgid "Joysticks" msgstr "搖桿" @@ -4022,11 +4034,11 @@ msgstr "'this' 不是一個 | 物件" msgid "Property cannot have both accessors and a value" msgstr "屬性不可同時有存取子和值" -#: include/wine/wine_common_ver.rc:133 +#: include/wine/wine_common_ver.rc:138 msgid "Wine kernel DLL" msgstr "Wine 核心 DLL" -#: include/wine/wine_common_ver.rc:138 dlls/winemac.drv/winemac.rc:32 +#: include/wine/wine_common_ver.rc:143 dlls/winemac.drv/winemac.rc:32 #: programs/wineboot/wineboot.rc:42 programs/winecfg/winecfg.rc:137 msgid "Wine" msgstr "Wine" diff --git a/programs/explorer/desktop.c b/programs/explorer/desktop.c index 14b2cbc2aff..cb17eaeb18e 100644 --- a/programs/explorer/desktop.c +++ b/programs/explorer/desktop.c @@ -42,7 +42,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(explorer); static const WCHAR default_driver[] = {'m','a','c',',','x','1','1',0}; -static BOOL using_root; +static BOOL using_root = TRUE; struct launcher { @@ -770,20 +770,6 @@ static LRESULT WINAPI desktop_wnd_proc( HWND hwnd, UINT message, WPARAM wp, LPAR return desktop_orig_wndproc( hwnd, message, wp, lp ); } -/* create the desktop and the associated driver window, and make it the current desktop */ -static BOOL create_desktop( HMODULE driver, const WCHAR *name, unsigned int width, unsigned int height ) -{ - BOOL ret = FALSE; - BOOL (CDECL *create_desktop_func)(unsigned int, unsigned int); - - if (driver) - { - create_desktop_func = (void *)GetProcAddress( driver, "wine_create_desktop" ); - if (create_desktop_func) ret = create_desktop_func( width, height ); - } - return ret; -} - /* parse the desktop size specification */ static BOOL parse_size( const WCHAR *size, unsigned int *width, unsigned int *height ) { @@ -1114,9 +1100,17 @@ void manage_desktop( WCHAR *arg ) if (name) enable_shell = get_default_enable_shell( name ); + UuidCreate( &guid ); + TRACE( "display guid %s\n", debugstr_guid(&guid) ); + graphics_driver = load_graphics_driver( driver, &guid ); + if (name && width && height) { - if (!(desktop = CreateDesktopW( name, NULL, NULL, 0, DESKTOP_ALL_ACCESS, NULL ))) + DEVMODEW devmode = {.dmPelsWidth = width, .dmPelsHeight = height}; + /* magic: desktop "root" means use the root window */ + if ((using_root = !wcsicmp( name, L"root" ))) desktop = CreateDesktopW( name, NULL, NULL, 0, DESKTOP_ALL_ACCESS, NULL ); + else desktop = CreateDesktopW( name, NULL, &devmode, DF_WINE_CREATE_DESKTOP, DESKTOP_ALL_ACCESS, NULL ); + if (!desktop) { WINE_ERR( "failed to create desktop %s error %ld\n", wine_dbgstr_w(name), GetLastError() ); ExitProcess( 1 ); @@ -1124,10 +1118,6 @@ void manage_desktop( WCHAR *arg ) SetThreadDesktop( desktop ); } - UuidCreate( &guid ); - TRACE( "display guid %s\n", debugstr_guid(&guid) ); - graphics_driver = load_graphics_driver( driver, &guid ); - /* create the desktop window */ hwnd = CreateWindowExW( 0, DESKTOP_CLASS_ATOM, NULL, WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0, 0, 0, 0, 0, 0, 0, &guid ); @@ -1140,7 +1130,6 @@ void manage_desktop( WCHAR *arg ) desktop_orig_wndproc = (WNDPROC)SetWindowLongPtrW( hwnd, GWLP_WNDPROC, (LONG_PTR)desktop_wnd_proc ); - using_root = !desktop || !create_desktop( graphics_driver, name, width, height ); SendMessageW( hwnd, WM_SETICON, ICON_BIG, (LPARAM)LoadIconW( 0, MAKEINTRESOURCEW(OIC_WINLOGO))); if (name) set_desktop_window_title( hwnd, name ); SetWindowPos( hwnd, 0, GetSystemMetrics(SM_XVIRTUALSCREEN), GetSystemMetrics(SM_YVIRTUALSCREEN), diff --git a/server/protocol.def b/server/protocol.def index 9589aacfbbc..cef5a16decb 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -3749,7 +3749,6 @@ struct handle_info int x; /* cursor position */ int y; rectangle_t clip; /* cursor clip rectangle */ - unsigned int clip_msg; /* message to post on cursor clip changes */ @REPLY user_handle_t prev_handle; /* previous handle */ int prev_count; /* previous show count */ @@ -3765,6 +3764,7 @@ struct handle_info #define SET_CURSOR_POS 0x04 #define SET_CURSOR_CLIP 0x08 #define SET_CURSOR_NOCLIP 0x10 +#define SET_CURSOR_FSCLIP 0x20 /* Get the history of the 64 last cursor positions */ @REQ(get_cursor_history) diff --git a/server/queue.c b/server/queue.c index f51263ed692..549d08cbcf5 100644 --- a/server/queue.c +++ b/server/queue.c @@ -34,6 +34,7 @@ #include "wingdi.h" #include "winuser.h" #include "winternl.h" +#include "ntuser.h" #include "handle.h" #include "file.h" @@ -488,8 +489,48 @@ static struct message *alloc_hardware_message( lparam_t info, struct hw_msg_sour return msg; } -static int update_desktop_cursor_pos( struct desktop *desktop, int x, int y ) +static int is_cursor_clipped( struct desktop *desktop ) { + rectangle_t top_rect, clip_rect = desktop->shared->cursor.clip; + get_top_window_rectangle( desktop, &top_rect ); + return !is_rect_equal( &clip_rect, &top_rect ); +} + +static void queue_cursor_message( struct desktop *desktop, user_handle_t win, unsigned int message, + lparam_t wparam, lparam_t lparam ) +{ + static const struct hw_msg_source source = { IMDT_UNAVAILABLE, IMO_SYSTEM }; + struct thread_input *input; + struct message *msg; + + if (!(msg = alloc_hardware_message( 0, source, get_tick_count(), 0 ))) return; + + msg->msg = message; + msg->wparam = wparam; + msg->lparam = lparam; + msg->x = desktop->shared->cursor.x; + msg->y = desktop->shared->cursor.y; + if (!(msg->win = win) && (input = desktop->foreground_input)) msg->win = input->shared->active; + queue_hardware_message( desktop, msg, 1 ); +} + +static int update_desktop_cursor_window( struct desktop *desktop, user_handle_t win ) +{ + int updated = win != desktop->cursor_win; + user_handle_t handle = desktop->cursor_handle; + desktop->cursor_win = win; + if (updated) + { + /* when clipping send the message to the foreground window as well, as some driver have an artificial overlay window */ + if (is_cursor_clipped( desktop )) queue_cursor_message( desktop, 0, WM_WINE_SETCURSOR, win, handle ); + queue_cursor_message( desktop, win, WM_WINE_SETCURSOR, win, handle ); + } + return updated; +} + +static int update_desktop_cursor_pos( struct desktop *desktop, user_handle_t win, int x, int y ) +{ + struct thread_input *input; int updated; unsigned int time = get_tick_count(); @@ -503,9 +544,27 @@ static int update_desktop_cursor_pos( struct desktop *desktop, int x, int y ) desktop->shared->cursor.last_change = time; SHARED_WRITE_END( &desktop->shared->seq ); + if (!win && (input = desktop->foreground_input)) win = input->shared->capture; + if (!win || !is_window_visible( win ) || is_window_transparent( win )) + win = shallow_window_from_point( desktop, x, y ); + if (update_desktop_cursor_window( desktop, win )) updated = 1; + return updated; } +static void update_desktop_cursor_handle( struct desktop *desktop, user_handle_t handle ) +{ + int updated = desktop->cursor_handle != handle; + user_handle_t win = desktop->cursor_win; + desktop->cursor_handle = handle; + if (updated) + { + /* when clipping send the message to the foreground window as well, as some driver have an artificial overlay window */ + if (is_cursor_clipped( desktop )) queue_cursor_message( desktop, 0, WM_WINE_SETCURSOR, win, handle ); + queue_cursor_message( desktop, win, WM_WINE_SETCURSOR, win, handle ); + } +} + /* set the cursor position and queue the corresponding mouse message */ static void set_cursor_pos( struct desktop *desktop, int x, int y ) { @@ -515,7 +574,7 @@ static void set_cursor_pos( struct desktop *desktop, int x, int y ) if ((device = current->process->rawinput_mouse) && (device->flags & RIDEV_NOLEGACY)) { - update_desktop_cursor_pos( desktop, x, y ); + update_desktop_cursor_pos( desktop, 0, x, y ); return; } @@ -538,7 +597,7 @@ static void get_message_defaults( struct msg_queue *queue, int *x, int *y, unsig } /* set the cursor clip rectangle */ -void set_clip_rectangle( struct desktop *desktop, const rectangle_t *rect, int send_clip_msg ) +void set_clip_rectangle( struct desktop *desktop, const rectangle_t *rect, unsigned int flags, int reset ) { rectangle_t top_rect, new_rect; int x, y; @@ -558,21 +617,24 @@ void set_clip_rectangle( struct desktop *desktop, const rectangle_t *rect, int s SHARED_WRITE_BEGIN( &desktop->shared->seq ); desktop->shared->cursor.clip = new_rect; - if (desktop->cursor_clip_msg && send_clip_msg) - post_desktop_message( desktop, desktop->cursor_clip_msg, rect != NULL, 0 ); - /* warp the mouse to be inside the clip rect */ x = max( min( desktop->shared->cursor.x, desktop->shared->cursor.clip.right - 1 ), desktop->shared->cursor.clip.left ); y = max( min( desktop->shared->cursor.y, desktop->shared->cursor.clip.bottom - 1 ), desktop->shared->cursor.clip.top ); if (x != desktop->shared->cursor.x || y != desktop->shared->cursor.y) set_cursor_pos( desktop, x, y ); SHARED_WRITE_END( &desktop->shared->seq ); + + /* request clip cursor rectangle reset to the desktop thread */ + if (reset) post_desktop_message( desktop, WM_WINE_CLIPCURSOR, flags, FALSE ); + + /* notify foreground thread, of reset, or to apply new cursor clipping rect */ + queue_cursor_message( desktop, 0, WM_WINE_CLIPCURSOR, flags, reset ); } /* change the foreground input and reset the cursor clip rect */ static void set_foreground_input( struct desktop *desktop, struct thread_input *input ) { if (desktop->foreground_input == input) return; - set_clip_rectangle( desktop, NULL, 1 ); + set_clip_rectangle( desktop, NULL, SET_CURSOR_NOCLIP, 1 ); desktop->foreground_input = input; SHARED_WRITE_BEGIN( &desktop->shared->seq ); desktop->shared->foreground_tid = input ? input->shared->tid : 0; @@ -643,12 +705,6 @@ static inline void clear_queue_bits( struct msg_queue *queue, unsigned int bits SHARED_WRITE_END( &queue->shared->seq ); } -/* check whether msg is a keyboard message */ -static inline int is_keyboard_msg( struct message *msg ) -{ - return (msg->msg >= WM_KEYFIRST && msg->msg <= WM_KEYLAST); -} - /* check if message is matched by the filter */ static inline int check_msg_filter( unsigned int msg, unsigned int first, unsigned int last ) { @@ -671,13 +727,15 @@ static inline int filter_contains_hw_range( unsigned int first, unsigned int las } /* get the QS_* bit corresponding to a given hardware message */ -static inline int get_hardware_msg_bit( struct message *msg ) -{ - if (msg->msg == WM_INPUT_DEVICE_CHANGE || msg->msg == WM_INPUT) return QS_RAWINPUT; - if (msg->msg == WM_MOUSEMOVE || msg->msg == WM_NCMOUSEMOVE || - msg->msg == WM_POINTERDOWN || msg->msg == WM_POINTERUP || - msg->msg == WM_POINTERUPDATE) return QS_MOUSEMOVE; - if (is_keyboard_msg( msg )) return QS_KEY; +static inline int get_hardware_msg_bit( unsigned int message ) +{ + if (message == WM_INPUT_DEVICE_CHANGE || message == WM_INPUT) return QS_RAWINPUT; + if (message == WM_MOUSEMOVE || message == WM_NCMOUSEMOVE) return QS_MOUSEMOVE; + if (message >= WM_KEYFIRST && message <= WM_KEYLAST) return QS_KEY; + if (message == WM_POINTERDOWN || message == WM_POINTERUP || + message == WM_POINTERUPDATE) return QS_POINTER; + if (message == WM_WINE_CLIPCURSOR) return QS_RAWINPUT; + if (message == WM_WINE_SETCURSOR) return QS_RAWINPUT; return QS_MOUSEBUTTON; } @@ -727,14 +785,12 @@ static int merge_pointer_update_message( struct thread_input *input, const struc return 1; } -/* try to merge a message with the last in the list; return 1 if successful */ -static int merge_message( struct thread_input *input, const struct message *msg ) +/* try to merge a WM_MOUSEMOVE message with the last in the list; return 1 if successful */ +static int merge_mousemove( struct thread_input *input, const struct message *msg ) { struct message *prev; struct list *ptr; - if (msg->msg == WM_POINTERUPDATE) return merge_pointer_update_message( input, msg ); - if (msg->msg != WM_MOUSEMOVE) return 0; for (ptr = list_tail( &input->msg_list ); ptr; ptr = list_prev( &input->msg_list, ptr )) { prev = LIST_ENTRY( ptr, struct message, entry ); @@ -762,6 +818,41 @@ static int merge_message( struct thread_input *input, const struct message *msg return 1; } +/* try to merge a unique message with the last in the list; return 1 if successful */ +static int merge_unique_message( struct thread_input *input, unsigned int message, const struct message *msg ) +{ + struct message *prev; + + LIST_FOR_EACH_ENTRY_REV( prev, &input->msg_list, struct message, entry ) + if (prev->msg == message) break; + if (&prev->entry == &input->msg_list) return 0; + + if (prev->result) return 0; + if (prev->win != msg->win) return 0; + if (prev->type != msg->type) return 0; + + /* now we can merge it */ + prev->wparam = msg->wparam; + prev->lparam = msg->lparam; + prev->x = msg->x; + prev->y = msg->y; + prev->time = msg->time; + list_remove( &prev->entry ); + list_add_tail( &input->msg_list, &prev->entry ); + + return 1; +} + +/* try to merge a message with the messages in the list; return 1 if successful */ +static int merge_message( struct thread_input *input, const struct message *msg ) +{ + if (msg->msg == WM_POINTERUPDATE) return merge_pointer_update_message( input, msg ); + if (msg->msg == WM_MOUSEMOVE) return merge_mousemove( input, msg ); + if (msg->msg == WM_WINE_CLIPCURSOR) return merge_unique_message( input, WM_WINE_CLIPCURSOR, msg ); + if (msg->msg == WM_WINE_SETCURSOR) return merge_unique_message( input, WM_WINE_SETCURSOR, msg ); + return 0; +} + /* free a result structure */ static void free_result( struct message_result *result ) { @@ -1300,12 +1391,13 @@ static void thread_input_dump( struct object *obj, int verbose ) static void thread_input_destroy( struct object *obj ) { struct thread_input *input = (struct thread_input *)obj; + struct desktop *desktop; empty_msg_list( &input->msg_list ); - if (input->desktop) + if ((desktop = input->desktop)) { - if (input->desktop->foreground_input == input) set_foreground_input( input->desktop, NULL ); - release_object( input->desktop ); + if (desktop->foreground_input == input) desktop->foreground_input = NULL; + release_object( desktop ); } release_object( input->shared_mapping ); } @@ -1642,11 +1734,8 @@ static void update_desktop_key_state( struct desktop *desktop, unsigned int msg, } /* update the desktop key state according to a mouse message flags */ -static void update_desktop_mouse_state( struct desktop *desktop, unsigned int flags, - int x, int y, lparam_t wparam ) +static void update_desktop_mouse_state( struct desktop *desktop, unsigned int flags, lparam_t wparam ) { - if (flags & MOUSEEVENTF_MOVE) - update_desktop_cursor_pos( desktop, x, y ); if (flags & MOUSEEVENTF_LEFTDOWN) update_desktop_key_state( desktop, WM_LBUTTONDOWN, wparam ); if (flags & MOUSEEVENTF_LEFTUP) @@ -1679,10 +1768,10 @@ static void release_hardware_message( struct msg_queue *queue, unsigned int hw_i if (&msg->entry == &input->msg_list) return; /* not found */ /* clear the queue bit for that message */ - clr_bit = get_hardware_msg_bit( msg ); + clr_bit = get_hardware_msg_bit( msg->msg ); LIST_FOR_EACH_ENTRY( other, &input->msg_list, struct message, entry ) { - if (other != msg && get_hardware_msg_bit( other ) == clr_bit) + if (other != msg && get_hardware_msg_bit( other->msg ) == clr_bit) { clr_bit = 0; break; @@ -1741,26 +1830,28 @@ static user_handle_t find_hardware_message_window( struct desktop *desktop, stru *thread = NULL; *msg_code = msg->msg; - if (msg->msg == WM_INPUT || msg->msg == WM_INPUT_DEVICE_CHANGE || - msg->msg == WM_POINTERDOWN || msg->msg == WM_POINTERUP || - msg->msg == WM_POINTERUPDATE) + switch (get_hardware_msg_bit( msg->msg )) { + case QS_POINTER: + case QS_RAWINPUT: if (!(win = msg->win) && input) win = input->shared->focus; - } - else if (is_keyboard_msg( msg )) - { + break; + case QS_KEY: if (input && !(win = input->shared->focus)) { win = input->shared->active; if (*msg_code < WM_SYSKEYDOWN) *msg_code += WM_SYSKEYDOWN - WM_KEYDOWN; } - } - else if (!input || !(win = input->shared->capture)) /* mouse message */ - { - if (is_window_visible( msg->win ) && !is_window_transparent( msg->win )) win = msg->win; - else win = shallow_window_from_point( desktop, msg->x, msg->y ); - - *thread = window_thread_from_point( win, msg->x, msg->y ); + break; + case QS_MOUSEMOVE: + case QS_MOUSEBUTTON: + if (!input || !(win = input->shared->capture)) + { + if (is_window_visible( msg->win ) && !is_window_transparent( msg->win )) win = msg->win; + else win = shallow_window_from_point( desktop, msg->x, msg->y ); + *thread = window_thread_from_point( win, msg->x, msg->y ); + } + break; } if (!*thread) @@ -1804,28 +1895,26 @@ static void queue_hardware_message( struct desktop *desktop, struct message *msg last_input_time = get_tick_count(); if (msg->msg != WM_MOUSEMOVE) always_queue = 1; - if (is_keyboard_msg( msg )) + switch (get_hardware_msg_bit( msg->msg )) { + case QS_KEY: if (queue_hotkey_message( desktop, msg )) return; if (desktop->shared->keystate[VK_MENU] & 0x80) msg->lparam |= KF_ALTDOWN << 16; if (msg->wparam == VK_SHIFT || msg->wparam == VK_LSHIFT || msg->wparam == VK_RSHIFT) msg->lparam &= ~(KF_EXTENDED << 16); - } - else if (msg->msg == WM_POINTERDOWN || msg->msg == WM_POINTERUP || msg->msg == WM_POINTERUPDATE) - { + break; + case QS_POINTER: if (IS_POINTER_PRIMARY_WPARAM( msg_data->rawinput.mouse.data )) { prepend_cursor_history( msg->x, msg->y, msg->time, msg_data->info ); - if (update_desktop_cursor_pos( desktop, msg->x, msg->y )) always_queue = 1; - } - } - else if (msg->msg != WM_INPUT && msg->msg != WM_INPUT_DEVICE_CHANGE) - { - if (msg->msg == WM_MOUSEMOVE) - { - prepend_cursor_history( msg->x, msg->y, msg->time, msg_data->info ); - if (update_desktop_cursor_pos( desktop, msg->x, msg->y )) always_queue = 1; + if (update_desktop_cursor_pos( desktop, msg->win, msg->x, msg->y )) always_queue = 1; } + break; + case QS_MOUSEMOVE: + prepend_cursor_history( msg->x, msg->y, msg->time, msg_data->info ); + if (update_desktop_cursor_pos( desktop, msg->win, msg->x, msg->y )) always_queue = 1; + /* fallthrough */ + case QS_MOUSEBUTTON: if (desktop->shared->keystate[VK_LBUTTON] & 0x80) msg->wparam |= MK_LBUTTON; if (desktop->shared->keystate[VK_MBUTTON] & 0x80) msg->wparam |= MK_MBUTTON; if (desktop->shared->keystate[VK_RBUTTON] & 0x80) msg->wparam |= MK_RBUTTON; @@ -1833,6 +1922,7 @@ static void queue_hardware_message( struct desktop *desktop, struct message *msg if (desktop->shared->keystate[VK_CONTROL] & 0x80) msg->wparam |= MK_CONTROL; if (desktop->shared->keystate[VK_XBUTTON1] & 0x80) msg->wparam |= MK_XBUTTON1; if (desktop->shared->keystate[VK_XBUTTON2] & 0x80) msg->wparam |= MK_XBUTTON2; + break; } msg->x = desktop->shared->cursor.x; msg->y = desktop->shared->cursor.y; @@ -1853,15 +1943,12 @@ static void queue_hardware_message( struct desktop *desktop, struct message *msg } input = thread->queue->input; - if (win != desktop->cursor_win) always_queue = 1; - desktop->cursor_win = win; - if (!always_queue || merge_message( input, msg )) free_message( msg ); else { msg->unique_id = 0; /* will be set once we return it to the app */ list_add_tail( &input->msg_list, &msg->entry ); - set_queue_bits( thread->queue, get_hardware_msg_bit(msg) ); + set_queue_bits( thread->queue, get_hardware_msg_bit( msg->msg ) ); } release_object( thread ); } @@ -2032,7 +2119,7 @@ static int queue_mouse_message( struct desktop *desktop, user_handle_t win, cons if ((input->mouse.info & 0xffffff00) == 0xff515700) source.origin = IMDT_TOUCH; - update_desktop_cursor_pos( desktop, desktop->shared->cursor.x, desktop->shared->cursor.y ); /* Update last change time */ + update_desktop_cursor_pos( desktop, desktop->cursor_win, desktop->shared->cursor.x, desktop->shared->cursor.y ); /* Update last change time */ flags = input->mouse.flags; time = input->mouse.time; if (!time) time = desktop->shared->cursor.last_change; @@ -2083,7 +2170,8 @@ static int queue_mouse_message( struct desktop *desktop, user_handle_t win, cons if ((device = current->process->rawinput_mouse) && (device->flags & RIDEV_NOLEGACY)) { - update_desktop_mouse_state( desktop, flags, x, y, input->mouse.data << 16 ); + if (flags & MOUSEEVENTF_MOVE) update_desktop_cursor_pos( desktop, win, x, y ); + update_desktop_mouse_state( desktop, flags, input->mouse.data << 16 ); return 0; } @@ -2394,19 +2482,19 @@ static void queue_custom_hardware_message( struct desktop *desktop, user_handle_ static int check_hw_message_filter( user_handle_t win, unsigned int msg_code, user_handle_t filter_win, unsigned int first, unsigned int last ) { - if (msg_code >= WM_KEYFIRST && msg_code <= WM_KEYLAST) + switch (get_hardware_msg_bit( msg_code )) { + case QS_KEY: /* we can only test the window for a keyboard message since the * dest window for a mouse message depends on hittest */ if (filter_win && win != filter_win && !is_child_window( filter_win, win )) return 0; /* the message code is final for a keyboard message, we can simply check it */ return check_msg_filter( msg_code, first, last ); - } - else /* mouse message */ - { - /* we need to check all possible values that the message can have in the end */ + case QS_MOUSEMOVE: + case QS_MOUSEBUTTON: + /* we need to check all possible values that the message can have in the end */ if (check_msg_filter( msg_code, first, last )) return 1; if (msg_code == WM_MOUSEWHEEL) return 0; /* no other possible value for this one */ @@ -2421,6 +2509,9 @@ static int check_hw_message_filter( user_handle_t win, unsigned int msg_code, if (check_msg_filter( msg_code + (WM_NCLBUTTONDBLCLK - WM_LBUTTONDOWN), first, last )) return 1; } return 0; + + default: + return check_msg_filter( msg_code, first, last ); } } @@ -2475,7 +2566,7 @@ static int get_hardware_message( struct thread *thread, unsigned int hw_id, user if (win_thread->queue->input == input) { /* wake the other thread */ - set_queue_bits( win_thread->queue, get_hardware_msg_bit(msg) ); + set_queue_bits( win_thread->queue, get_hardware_msg_bit( msg->msg ) ); got_one = 1; } else @@ -2494,7 +2585,7 @@ static int get_hardware_message( struct thread *thread, unsigned int hw_id, user * match the filter we skip it */ if (got_one || !check_hw_message_filter( win, msg_code, filter_win, first, last )) { - clear_bits &= ~get_hardware_msg_bit( msg ); + clear_bits &= ~get_hardware_msg_bit( msg->msg ); continue; } @@ -2518,9 +2609,7 @@ static int get_hardware_message( struct thread *thread, unsigned int hw_id, user data->hw_id = msg->unique_id; set_reply_data( msg->data, msg->data_size ); - if ((msg->msg == WM_INPUT || msg->msg == WM_INPUT_DEVICE_CHANGE || - msg->msg == WM_POINTERDOWN || msg->msg == WM_POINTERUP || - msg->msg == WM_POINTERUPDATE) && (flags & PM_REMOVE)) + if ((get_hardware_msg_bit( msg->msg ) & (QS_POINTER | QS_RAWINPUT)) && (flags & PM_REMOVE)) release_hardware_message( current->queue, data->hw_id ); return 1; } @@ -3684,14 +3773,16 @@ DECL_HANDLER(set_cursor) { struct msg_queue *queue = get_current_queue(); struct thread_input *input; + struct desktop *desktop; if (!queue) return; input = queue->input; + desktop = input->desktop; reply->prev_handle = input->shared->cursor; reply->prev_count = input->shared->cursor_count; - reply->prev_x = input->desktop->shared->cursor.x; - reply->prev_y = input->desktop->shared->cursor.y; + reply->prev_x = desktop->shared->cursor.x; + reply->prev_y = desktop->shared->cursor.y; if ((req->flags & SET_CURSOR_HANDLE) && req->handle && !get_user_object( req->handle, USER_CLIENT )) @@ -3711,25 +3802,20 @@ DECL_HANDLER(set_cursor) input->shared->cursor_count += req->show_count; } SHARED_WRITE_END( &input->shared->seq ); - if (req->flags & SET_CURSOR_POS) - { - set_cursor_pos( input->desktop, req->x, req->y ); - } - if (req->flags & (SET_CURSOR_CLIP | SET_CURSOR_NOCLIP)) - { - struct desktop *desktop = input->desktop; - - /* only the desktop owner can set the message */ - if (req->clip_msg && get_top_window_owner(desktop) == current->process) - desktop->cursor_clip_msg = req->clip_msg; + if (req->flags & SET_CURSOR_POS) set_cursor_pos( desktop, req->x, req->y ); + if (req->flags & SET_CURSOR_CLIP) set_clip_rectangle( desktop, &req->clip, req->flags, 0 ); + if (req->flags & SET_CURSOR_NOCLIP) set_clip_rectangle( desktop, NULL, SET_CURSOR_NOCLIP, 0 ); - set_clip_rectangle( desktop, (req->flags & SET_CURSOR_NOCLIP) ? NULL : &req->clip, 0 ); + if (req->flags & (SET_CURSOR_HANDLE | SET_CURSOR_COUNT)) + { + if (input->shared->cursor_count < 0) update_desktop_cursor_handle( desktop, 0 ); + else update_desktop_cursor_handle( desktop, input->shared->cursor ); } - reply->new_x = input->desktop->shared->cursor.x; - reply->new_y = input->desktop->shared->cursor.y; - reply->new_clip = input->desktop->shared->cursor.clip; - reply->last_change = input->desktop->shared->cursor.last_change; + reply->new_x = desktop->shared->cursor.x; + reply->new_y = desktop->shared->cursor.y; + reply->new_clip = desktop->shared->cursor.clip; + reply->last_change = desktop->shared->cursor.last_change; } /* Get the history of the 64 last cursor positions */ diff --git a/server/request.h b/server/request.h index 089af79e199..3443ee93c17 100644 --- a/server/request.h +++ b/server/request.h @@ -2209,8 +2209,7 @@ C_ASSERT( FIELD_OFFSET(struct set_cursor_request, show_count) == 20 ); C_ASSERT( FIELD_OFFSET(struct set_cursor_request, x) == 24 ); C_ASSERT( FIELD_OFFSET(struct set_cursor_request, y) == 28 ); C_ASSERT( FIELD_OFFSET(struct set_cursor_request, clip) == 32 ); -C_ASSERT( FIELD_OFFSET(struct set_cursor_request, clip_msg) == 48 ); -C_ASSERT( sizeof(struct set_cursor_request) == 56 ); +C_ASSERT( sizeof(struct set_cursor_request) == 48 ); C_ASSERT( FIELD_OFFSET(struct set_cursor_reply, prev_handle) == 8 ); C_ASSERT( FIELD_OFFSET(struct set_cursor_reply, prev_count) == 12 ); C_ASSERT( FIELD_OFFSET(struct set_cursor_reply, prev_x) == 16 ); diff --git a/server/sock.c b/server/sock.c index 16769fc2b4b..62b9ceca0a2 100644 --- a/server/sock.c +++ b/server/sock.c @@ -239,6 +239,7 @@ struct sock struct poll_req *main_poll; /* main poll */ union win_sockaddr addr; /* socket name */ int addr_len; /* socket name length */ + unsigned int default_rcvbuf; /* initial advisory recv buffer size */ unsigned int rcvbuf; /* advisory recv buffer size */ unsigned int sndbuf; /* advisory send buffer size */ unsigned int rcvtimeo; /* receive timeout in ms */ @@ -1729,6 +1730,7 @@ static struct sock *create_socket(void) sock->reset = 0; sock->reuseaddr = 0; sock->exclusiveaddruse = 0; + sock->default_rcvbuf = 0; sock->rcvbuf = 0; sock->sndbuf = 0; sock->rcvtimeo = 0; @@ -1912,7 +1914,7 @@ static int init_socket( struct sock *sock, int family, int type, int protocol ) len = sizeof(value); if (!getsockopt( sockfd, SOL_SOCKET, SO_RCVBUF, &value, &len )) - sock->rcvbuf = value; + sock->rcvbuf = sock->default_rcvbuf = value; len = sizeof(value); if (!getsockopt( sockfd, SOL_SOCKET, SO_SNDBUF, &value, &len )) @@ -2007,6 +2009,7 @@ static struct sock *accept_socket( struct sock *sock ) acceptsock->reuseaddr = sock->reuseaddr; acceptsock->exclusiveaddruse = sock->exclusiveaddruse; acceptsock->sndbuf = sock->sndbuf; + acceptsock->default_rcvbuf = sock->default_rcvbuf; acceptsock->rcvbuf = sock->rcvbuf; acceptsock->sndtimeo = sock->sndtimeo; acceptsock->rcvtimeo = sock->rcvtimeo; @@ -3086,7 +3089,7 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) case IOCTL_AFD_WINE_SET_SO_RCVBUF: { - DWORD rcvbuf; + DWORD rcvbuf, set_rcvbuf; if (get_req_data_size() < sizeof(rcvbuf)) { @@ -3094,8 +3097,9 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) return; } rcvbuf = *(DWORD *)get_req_data(); + set_rcvbuf = max( rcvbuf, sock->default_rcvbuf ); - if (!setsockopt( unix_fd, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf, sizeof(rcvbuf) )) + if (!setsockopt( unix_fd, SOL_SOCKET, SO_RCVBUF, (char *)&set_rcvbuf, sizeof(set_rcvbuf) )) sock->rcvbuf = rcvbuf; else set_error( sock_get_ntstatus( errno ) ); diff --git a/server/trace.c b/server/trace.c index b749c54a900..a360b4706ce 100644 --- a/server/trace.c +++ b/server/trace.c @@ -4398,7 +4398,6 @@ static void dump_set_cursor_request( const struct set_cursor_request *req ) fprintf( stderr, ", x=%d", req->x ); fprintf( stderr, ", y=%d", req->y ); dump_rectangle( ", clip=", &req->clip ); - fprintf( stderr, ", clip_msg=%08x", req->clip_msg ); } static void dump_set_cursor_reply( const struct set_cursor_reply *req ) diff --git a/server/user.h b/server/user.h index deebd92ee6a..d9e4c023e29 100644 --- a/server/user.h +++ b/server/user.h @@ -67,9 +67,8 @@ struct desktop struct list touches; /* list of active touches */ struct thread_input *foreground_input; /* thread input of foreground thread */ unsigned int users; /* processes and threads using this desktop */ - unsigned char keystate[256]; /* asynchronous key state */ - unsigned int cursor_clip_msg; /* message to post for cursor clip changes */ - user_handle_t cursor_win; /* window that contains the cursor */ + user_handle_t cursor_win; /* window that contains the cursor */ + user_handle_t cursor_handle; /* last set cursor handle */ struct object *shared_mapping; /* desktop shared memory mapping */ volatile struct desktop_shared_memory *shared; /* desktop shared memory ptr */ unsigned int last_press_alt:1; /* last key press was Alt (used to determine msg on Alt release) */ @@ -106,6 +105,8 @@ extern void queue_cleanup_window( struct thread *thread, user_handle_t win ); extern int init_thread_queue( struct thread *thread ); extern int attach_thread_input( struct thread *thread_from, struct thread *thread_to ); extern void detach_thread_input( struct thread *thread_from ); +extern void set_clip_rectangle( struct desktop *desktop, const rectangle_t *rect, + unsigned int flags, int reset ); extern void post_message( user_handle_t win, unsigned int message, lparam_t wparam, lparam_t lparam ); extern void send_notify_message( user_handle_t win, unsigned int message, @@ -117,7 +118,6 @@ extern void post_win_event( struct thread *thread, unsigned int event, user_handle_t handle ); extern void free_hotkeys( struct desktop *desktop, user_handle_t window ); extern void free_touches( struct desktop *desktop, user_handle_t window ); -extern void set_clip_rectangle( struct desktop *desktop, const rectangle_t *rect, int send_clip_msg ); /* region functions */ diff --git a/server/window.c b/server/window.c index 302aa2e6071..7220fd65e53 100644 --- a/server/window.c +++ b/server/window.c @@ -1831,7 +1831,7 @@ static void set_window_pos( struct window *win, struct window *previous, } /* reset cursor clip rectangle when the desktop changes size */ - if (win == win->desktop->top_window) set_clip_rectangle( win->desktop, NULL, 0 ); + if (win == win->desktop->top_window) set_clip_rectangle( win->desktop, NULL, SET_CURSOR_NOCLIP, 1 ); /* if the window is not visible, everything is easy */ if (!visible) return; diff --git a/server/winstation.c b/server/winstation.c index 53613592a82..d9617b5509e 100644 --- a/server/winstation.c +++ b/server/winstation.c @@ -30,6 +30,7 @@ #include "winbase.h" #include "winuser.h" #include "winternl.h" +#include "ntuser.h" #include "object.h" #include "handle.h" @@ -260,7 +261,6 @@ static struct desktop *create_desktop( const struct unicode_str *name, unsigned desktop->close_timeout_val = 0; desktop->foreground_input = NULL; desktop->users = 0; - desktop->cursor_clip_msg = 0; desktop->cursor_win = 0; desktop->last_press_alt = 0; list_add_tail( &winstation->desktops, &desktop->entry ); @@ -272,7 +272,11 @@ static struct desktop *create_desktop( const struct unicode_str *name, unsigned return NULL; } } - else clear_error(); + else + { + desktop->flags |= (flags & DF_WINE_CREATE_DESKTOP); + clear_error(); + } } return desktop; } diff --git a/tools/makedep.c b/tools/makedep.c index 3dd58428c99..6b02b154e45 100644 --- a/tools/makedep.c +++ b/tools/makedep.c @@ -143,6 +143,7 @@ static struct strarray subdirs; static struct strarray delay_import_libs; static struct strarray top_install[NB_INSTALL_RULES]; static const char *root_src_dir; +static const char *root_obj_dir; static const char *tools_dir; static const char *tools_ext; static const char *exe_ext; @@ -192,15 +193,18 @@ struct makefile const char *obj_dir; const char *parent_dir; const char *module; + const char *module_x64; const char *testdll; const char *extlib; const char *sharedlib; const char *staticlib; const char *importlib; const char *unixlib; + int use_msvcrt; int data_only; int is_win16; int is_exe; + int has_cxx; int disabled[MAX_ARCHS]; /* values generated at output time */ @@ -597,17 +601,6 @@ static int is_multiarch( unsigned int arch ) } -/******************************************************************* - * is_using_msvcrt - * - * Check if the files of a makefile use msvcrt by default. - */ -static int is_using_msvcrt( struct makefile *make ) -{ - return make->module || make->testdll; -} - - /******************************************************************* * arch_module_name */ @@ -654,6 +647,16 @@ static char *root_src_dir_path( const char *path ) } +/******************************************************************* + * root_obj_dir_path + */ +static char *root_obj_dir_path( const char *path ) +{ + if (!root_obj_dir) return (char *)path; + return concat_paths( root_obj_dir, path ); +} + + /******************************************************************* * tools_dir_path */ @@ -866,7 +869,7 @@ static struct incl_file *add_generated_source( struct makefile *make, const char file->basename = xstrdup( filename ? filename : name ); file->filename = obj_dir_path( make, file->basename ); file->file->flags = FLAG_GENERATED; - file->use_msvcrt = is_using_msvcrt( make ); + file->use_msvcrt = make->use_msvcrt; list_add_tail( &make->sources, &file->entry ); if (make == include_makefile) { @@ -1185,6 +1188,7 @@ static const struct } parse_functions[] = { { ".c", parse_c_file }, + { ".cpp", parse_c_file }, { ".h", parse_c_file }, { ".inl", parse_c_file }, { ".l", parse_c_file }, @@ -1443,9 +1447,9 @@ static struct file *open_include_file( const struct makefile *make, struct incl_ if ((file = open_local_file( make, pFile->name, &pFile->filename ))) return file; /* check for global importlib (module dependency) */ - if (pFile->type == INCL_IMPORTLIB && find_importlib_module( pFile->name )) + if (pFile->type == INCL_IMPORTLIB) { - pFile->filename = pFile->name; + if (find_importlib_module( pFile->name )) pFile->filename = pFile->name; return NULL; } @@ -1498,7 +1502,7 @@ static struct file *open_include_file( const struct makefile *make, struct incl_ return file; } - if (make->extlib) return NULL; /* ignore missing files in external libs */ + if (make->extlib || make->has_cxx) return NULL; /* ignore missing files in external libs */ fprintf( stderr, "%s:%d: error: ", pFile->included_by->file->name, pFile->included_line ); perror( pFile->name ); @@ -1551,6 +1555,13 @@ static void parse_file( struct makefile *make, struct incl_file *source, int src { struct file *file = src ? open_src_file( make, source ) : open_include_file( make, source ); + if (strendswith( source->name, ".cpp" )) + { + /* ignore missing headers or unsupported includes in .cpp files */ + source->is_external = 1; + make->has_cxx = 1; + } + if (!file) return; source->file = file; @@ -1615,7 +1626,7 @@ static struct incl_file *add_src_file( struct makefile *make, const char *name ) memset( file, 0, sizeof(*file) ); file->name = xstrdup(name); - file->use_msvcrt = is_using_msvcrt( make ); + file->use_msvcrt = make->use_msvcrt; file->is_external = !!make->extlib; list_add_tail( &make->sources, &file->entry ); if (make == include_makefile) @@ -1813,12 +1824,13 @@ static void add_generated_sources( struct makefile *make ) unsigned int i, arch; struct incl_file *source, *next, *file, *dlldata = NULL; struct strarray objs = get_expanded_make_var_array( make, "EXTRA_OBJS" ); + int multiarch = archs.count > 1 && make->use_msvcrt; LIST_FOR_EACH_ENTRY_SAFE( source, next, &make->sources, struct incl_file, entry ) { for (arch = 0; arch < archs.count; arch++) { - if (!is_multiarch( arch )) continue; + if (!arch != !multiarch) continue; if (source->file->flags & FLAG_IDL_CLIENT) { file = add_generated_source( make, replace_extension( source->name, ".idl", "_c.c" ), NULL, arch ); @@ -1922,7 +1934,7 @@ static void add_generated_sources( struct makefile *make ) { for (arch = 0; arch < archs.count; arch++) { - if (!is_multiarch( arch )) continue; + if (!arch != !multiarch) continue; file = add_generated_source( make, "testlist.o", "testlist.c", arch ); add_dependency( file->file, "wine/test.h", INCL_NORMAL ); add_all_includes( make, file, file->file ); @@ -2176,6 +2188,7 @@ static int is_crt_module( const char *file ) */ static const char *get_default_crt( const struct makefile *make ) { + if (!make->use_msvcrt) return NULL; if (make->module && is_crt_module( make->module )) return NULL; /* don't add crt import to crt dlls */ return !make->testdll && (!make->staticlib || make->extlib) ? "ucrtbase" : "msvcrt"; } @@ -2365,7 +2378,6 @@ static struct strarray get_source_defines( struct makefile *make, struct incl_fi strarray_add( &ret, strmake( "-I%s", root_src_dir_path( "include/msvcrt" ))); for (i = 0; i < make->include_paths.count; i++) strarray_add( &ret, strmake( "-I%s", make->include_paths.str[i] )); - strarray_add( &ret, get_crt_define( make )); } strarray_addall( &ret, make->define_args ); strarray_addall( &ret, get_expanded_file_local_var( make, obj, "EXTRADEFS" )); @@ -2416,19 +2428,19 @@ static const char *cmd_prefix( const char *cmd ) /******************************************************************* * output_winegcc_command */ -static void output_winegcc_command( struct makefile *make, unsigned int arch ) +static void output_winegcc_command( struct makefile *make, unsigned int arch, int is_cxx ) { - output( "\t%s%s -o $@", cmd_prefix( "CCLD" ), tools_path( make, "winegcc" )); - output_filename( "--wine-objdir ." ); + const char *tool = tools_path( make, "winegcc" ); + if (is_cxx) strcpy( strrchr( tool, 'w' ), "wineg++" ); + output( "\t%s%s -o $@", cmd_prefix( "CCLD" ), tool ); + output_filename( strmake( "--wine-objdir %s", root_obj_dir_path( "." ) ) ); if (tools_dir) { output_filename( "--winebuild" ); output_filename( tools_path( make, "winebuild" )); } output_filenames( target_flags[arch] ); - if (arch) return; - output_filename( "-mno-cygwin" ); - output_filenames( lddll_flags ); + if (!arch) output_filenames( lddll_flags ); } @@ -2850,6 +2862,7 @@ static void output_source_idl( struct makefile *make, struct incl_file *source, if (!(source->file->flags & idl_outputs[i].flag)) continue; for (arch = 0; arch < archs.count; arch++) { + if (!make->use_msvcrt) continue; if (!is_multiarch( arch )) continue; if (make->disabled[arch]) continue; dest = strmake( "%s%s%s", arch_dirs[arch], obj, idl_outputs[i].ext ); @@ -3111,7 +3124,7 @@ static void output_source_spec( struct makefile *make, struct incl_file *source, output_filename( tools_path( make, "winebuild" )); output_filename( tools_path( make, "winegcc" )); output( "\n" ); - output_winegcc_command( make, arch ); + output_winegcc_command( make, arch, 0 ); output_filename( "-s" ); output_filenames( dll_flags ); if (arch) output_filenames( get_expanded_arch_var_array( make, "EXTRADLLFLAGS", arch )); @@ -3134,20 +3147,22 @@ static void output_source_one_arch( struct makefile *make, struct incl_file *sou struct strarray defines, struct strarray *targets, unsigned int arch, int is_dll_src ) { + const int is_cxx = strendswith( source->name, ".cpp" ); const char *obj_name; if (make->disabled[arch] && !(source->file->flags & FLAG_C_IMPLIB)) return; + make->has_cxx |= is_cxx; if (arch) { if (source->file->flags & FLAG_C_UNIX) return; - if (!is_using_msvcrt( make ) && !make->staticlib && !(source->file->flags & FLAG_C_IMPLIB)) return; + if (!make->use_msvcrt && !make->staticlib && !(source->file->flags & FLAG_C_IMPLIB)) return; } else if (source->file->flags & FLAG_C_UNIX) { if (!*dll_ext) return; } - else if (archs.count > 1 && is_using_msvcrt( make ) && + else if (archs.count > 1 && make->use_msvcrt && !(source->file->flags & FLAG_C_IMPLIB) && (!make->staticlib || make->extlib)) return; @@ -3164,10 +3179,11 @@ static void output_source_one_arch( struct makefile *make, struct incl_file *sou strarray_add( &make->clean_files, obj_name ); output( "%s: %s\n", obj_dir_path( make, obj_name ), source->filename ); - output( "\t%s%s -c -o $@ %s", cmd_prefix( "CC" ), arch_make_variable( "CC", arch ), source->filename ); + if (is_cxx) output( "\t%s%s -c -o $@ %s", cmd_prefix( "CXX" ), arch_make_variable( "CXX", arch ), source->filename ); + else output( "\t%s%s -c -o $@ %s", cmd_prefix( "CC" ), arch_make_variable( "CC", arch ), source->filename ); output_filenames( defines ); if (!source->use_msvcrt) output_filenames( make->unix_cflags ); - output_filenames( make->extlib ? extra_cflags_extlib[arch] : extra_cflags[arch] ); + output_filenames( make->extlib || is_cxx ? extra_cflags_extlib[arch] : extra_cflags[arch] ); if (!arch) { if (make->sharedlib || (source->file->flags & FLAG_C_UNIX)) @@ -3188,7 +3204,8 @@ static void output_source_one_arch( struct makefile *make, struct incl_file *sou } output_filenames( cpp_flags ); - output_filename( arch_make_variable( "CFLAGS", arch )); + if (is_cxx) output_filename( arch_make_variable( "CXXFLAGS", arch )); + else output_filename( arch_make_variable( "CFLAGS", arch )); output( "\n" ); if (make->testdll && !is_dll_src && strendswith( source->name, ".c" ) && @@ -3275,14 +3292,17 @@ static const struct static void output_fake_module( struct makefile *make ) { unsigned int arch = 0; /* fake modules are always native */ - const char *spec_file = NULL, *name = strmake( "%s%s", arch_pe_dirs[arch], make->module ); + const char *spec_file = NULL, *name; + const char *module = make->module; if (make->disabled[arch]) return; - if (!make->is_exe) spec_file = src_dir_path( make, replace_extension( make->module, ".dll", ".spec" )); + if (make->module_x64 && !strcmp( archs.str[arch], "x86_64" )) module = make->module_x64; + if (!make->is_exe) spec_file = src_dir_path( make, replace_extension( module, ".dll", ".spec" )); + name = strmake( "%s%s", arch_pe_dirs[arch], module ); strarray_add( &make->all_targets[arch], name ); - add_install_rule( make, make->module, arch, name, strmake( "d$(dlldir)/%s", name )); + add_install_rule( make, module, arch, name, strmake( "d$(dlldir)/%s", name )); output( "%s:", obj_dir_path( make, name )); if (spec_file) output_filename( spec_file ); @@ -3290,7 +3310,7 @@ static void output_fake_module( struct makefile *make ) output_filename( tools_path( make, "winebuild" )); output_filename( tools_path( make, "winegcc" )); output( "\n" ); - output_winegcc_command( make, arch ); + output_winegcc_command( make, arch, 0 ); output_filename( "-Wb,--fake-module" ); if (spec_file) { @@ -3312,18 +3332,19 @@ static void output_module( struct makefile *make, unsigned int arch ) struct strarray all_libs = empty_strarray; struct strarray dep_libs = empty_strarray; struct strarray imports = make->imports; - const char *module_name; + const char *module_name, *module = make->module; const char *debug_file; - char *spec_file = NULL; + char *tool, *spec_file = NULL; unsigned int i; if (make->disabled[arch]) return; - if (!make->is_exe) spec_file = src_dir_path( make, replace_extension( make->module, ".dll", ".spec" )); + if (make->module_x64 && !strcmp( archs.str[arch], "x86_64" )) module = make->module_x64; + if (!make->is_exe) spec_file = src_dir_path( make, replace_extension( module, ".dll", ".spec" )); if (!make->data_only) { - module_name = arch_module_name( make->module, arch ); + module_name = arch_module_name( module, arch ); if (!strarray_exists( &make->extradllflags, "-nodefaultlibs" )) default_imports = get_default_imports( make, imports ); @@ -3332,6 +3353,12 @@ static void output_module( struct makefile *make, unsigned int arch ) strarray_addall( &all_libs, add_import_libs( make, &dep_libs, default_imports, IMPORT_TYPE_DEFAULT, arch ) ); if (!arch) strarray_addall( &all_libs, libs ); + if (!make->use_msvcrt) + { + strarray_addall( &all_libs, get_expanded_make_var_array( make, "UNIX_LIBS" )); + strarray_addall( &all_libs, libs ); + } + if (delay_load_flags[arch]) { for (i = 0; i < make->delayimports.count; i++) @@ -3341,15 +3368,15 @@ static void output_module( struct makefile *make, unsigned int arch ) } } } - else module_name = strmake( "%s%s", arch_pe_dirs[arch], make->module ); + else module_name = strmake( "%s%s", arch_pe_dirs[arch], module ); strarray_add( &make->all_targets[arch], module_name ); if (make->data_only) - add_install_rule( make, make->module, arch, module_name, - strmake( "d$(dlldir)/%s%s", arch_pe_dirs[arch], make->module )); + add_install_rule( make, module, arch, module_name, + strmake( "d$(dlldir)/%s%s", arch_pe_dirs[arch], module )); else - add_install_rule( make, make->module, arch, module_name, - strmake( "%c%s%s%s", '0' + arch, arch_install_dirs[arch], make->module, + add_install_rule( make, module, arch, module_name, + strmake( "%c%s%s%s", '0' + arch, arch_install_dirs[arch], module, arch ? "" : dll_ext )); output( "%s:", obj_dir_path( make, module_name )); @@ -3358,9 +3385,15 @@ static void output_module( struct makefile *make, unsigned int arch ) output_filenames_obj_dir( make, make->res_files[arch] ); output_filenames( dep_libs ); output_filename( tools_path( make, "winebuild" )); - output_filename( tools_path( make, "winegcc" )); + tool = tools_path( make, "winegcc" ); + output_filename( tool ); + if (make->has_cxx) + { + strcpy( strrchr( tool, 'w' ), "wineg++" ); + output_filename( tool ); + } output( "\n" ); - output_winegcc_command( make, arch ); + output_winegcc_command( make, arch, make->has_cxx ); if (arch) output_filename( "-Wl,--wine-builtin" ); if (spec_file) { @@ -3527,7 +3560,7 @@ static void output_test_module( struct makefile *make, unsigned int arch ) strarray_add( &make->all_targets[arch], testmodule ); strarray_add( &make->clean_files, stripped ); output( "%s:\n", obj_dir_path( make, testmodule )); - output_winegcc_command( make, arch ); + output_winegcc_command( make, arch, 0 ); output_filenames( make->extradllflags ); output_filenames_obj_dir( make, make->object_files[arch] ); output_filenames_obj_dir( make, make->res_files[arch] ); @@ -3537,7 +3570,7 @@ static void output_test_module( struct makefile *make, unsigned int arch ) output_filename( arch_make_variable( "LDFLAGS", arch )); output( "\n" ); output( "%s:\n", obj_dir_path( make, stripped )); - output_winegcc_command( make, arch ); + output_winegcc_command( make, arch, 0 ); output_filename( "-s" ); output_filename( strmake( "-Wb,-F,%s_test.exe", basemodule )); output_filenames( make->extradllflags ); @@ -3568,7 +3601,7 @@ static void output_test_module( struct makefile *make, unsigned int arch ) output( ": %s", obj_dir_path( make, testmodule )); if (parent) { - char *parent_module = arch_module_name( make->testdll, arch ); + char *parent_module = arch_module_name( make->testdll, parent->use_msvcrt ? arch : 0 ); output_filename( obj_dir_path( parent, parent_module )); if (parent->unixlib) output_filename( obj_dir_path( parent, parent->unixlib )); } @@ -3818,7 +3851,12 @@ static void output_sources( struct makefile *make ) } else if (make->module) { - for (arch = 0; arch < archs.count; arch++) if (is_multiarch( arch )) output_module( make, arch ); + if (!make->use_msvcrt) output_module( make, 0 ); + else + { + for (arch = 0; arch < archs.count; arch++) + if (is_multiarch( arch )) output_module( make, arch ); + } if (make->unixlib) output_unix_lib( make ); if (make->importlib) for (arch = 0; arch < archs.count; arch++) output_import_lib( make, arch ); if (make->is_exe && !make->is_win16 && *dll_ext && strendswith( make->module, ".exe" )) @@ -4208,6 +4246,7 @@ static void load_sources( struct makefile *make ) { "SOURCES", "C_SRCS", + "CXX_SRCS", "OBJC_SRCS", "RC_SRCS", "MC_SRCS", @@ -4233,6 +4272,7 @@ static void load_sources( struct makefile *make ) make->parent_dir = get_expanded_make_variable( make, "PARENTSRC" ); make->module = get_expanded_make_variable( make, "MODULE" ); + make->module_x64 = get_expanded_make_variable( make, "MODULE_x64" ); make->testdll = get_expanded_make_variable( make, "TESTDLL" ); make->sharedlib = get_expanded_make_variable( make, "SHAREDLIB" ); make->staticlib = get_expanded_make_variable( make, "STATICLIB" ); @@ -4260,9 +4300,13 @@ static void load_sources( struct makefile *make ) } make->is_win16 = strarray_exists( &make->extradllflags, "-m16" ); make->data_only = strarray_exists( &make->extradllflags, "-Wb,--data-only" ); + make->use_msvcrt = (make->module || make->testdll || make->is_win16) && + !strarray_exists( &make->extradllflags, "-mcygwin" ); make->is_exe = strarray_exists( &make->extradllflags, "-mconsole" ) || strarray_exists( &make->extradllflags, "-mwindows" ); + if (make->use_msvcrt) strarray_add_uniq( &make->extradllflags, "-mno-cygwin" ); + if (make->module) { /* add default install rules if nothing was specified */ @@ -4272,6 +4316,7 @@ static void load_sources( struct makefile *make ) if (make->importlib) strarray_add( &make->install[INSTALL_DEV], make->importlib ); if (make->staticlib) strarray_add( &make->install[INSTALL_DEV], make->staticlib ); else strarray_add( &make->install[INSTALL_LIB], make->module ); + if (make->module_x64) strarray_add( &make->install[INSTALL_LIB], make->module_x64 ); } } @@ -4305,7 +4350,7 @@ static void load_sources( struct makefile *make ) strarray_add( &make->include_args, strmake( "-I%s", make->src_dir )); if (make->parent_dir) strarray_add( &make->include_args, strmake( "-I%s", src_dir_path( make, make->parent_dir ))); - strarray_add( &make->include_args, "-Iinclude" ); + strarray_add( &make->include_args, strmake( "-I%s", root_obj_dir_path( "include" ) ) ); if (root_src_dir) strarray_add( &make->include_args, strmake( "-I%s", root_src_dir_path( "include" ))); list_init( &make->sources ); @@ -4319,6 +4364,8 @@ static void load_sources( struct makefile *make ) add_generated_sources( make ); + if (make->use_msvcrt) strarray_add( &make->define_args, get_crt_define( make )); + LIST_FOR_EACH_ENTRY( file, &make->includes, struct incl_file, entry ) parse_file( make, file, 0 ); LIST_FOR_EACH_ENTRY( file, &make->sources, struct incl_file, entry ) get_dependencies( file, file ); @@ -4385,7 +4432,7 @@ static int parse_option( const char *opt ) int main( int argc, char *argv[] ) { const char *makeflags = getenv( "MAKEFLAGS" ); - const char *target; + const char *target, *tmp; unsigned int i, j, arch; if (makeflags) parse_makeflags( makeflags ); @@ -4441,6 +4488,7 @@ int main( int argc, char *argv[] ) top_install[i] = get_expanded_make_var_array( top_makefile, strmake( "TOP_%s", install_variables[i] )); root_src_dir = get_expanded_make_variable( top_makefile, "srcdir" ); + root_obj_dir = get_expanded_make_variable( top_makefile, "objdir" ); tools_dir = get_expanded_make_variable( top_makefile, "toolsdir" ); tools_ext = get_expanded_make_variable( top_makefile, "toolsext" ); exe_ext = get_expanded_make_variable( top_makefile, "EXEEXT" ); @@ -4501,7 +4549,14 @@ int main( int argc, char *argv[] ) subdirs = get_expanded_make_var_array( top_makefile, "SUBDIRS" ); submakes = xmalloc( subdirs.count * sizeof(*submakes) ); - for (i = 0; i < subdirs.count; i++) submakes[i] = parse_makefile( subdirs.str[i] ); + for (i = 0; i < subdirs.count; i++) + { + submakes[i] = parse_makefile( subdirs.str[i] ); + if (*(tmp = subdirs.str[i]) == '/') tmp = strmake( "dlls%s", strrchr( tmp, '/' ) ); + submakes[i]->obj_dir = subdirs.str[i] = tmp; + } + + if (!include_makefile) include_makefile = parse_makefile( root_src_dir_path( "include" ) ); load_sources( top_makefile ); load_sources( include_makefile );