wine/dlls/win32u/imm.c

638 lines
19 KiB
C

/*
* Input Context implementation
*
* Copyright 1998 Patrik Stridvall
* Copyright 2002, 2003, 2007 CodeWeavers, Aric Stewart
* Copyright 2022 Jacek Caban 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 <pthread.h>
#include "ntstatus.h"
#define WIN32_NO_STATUS
#include "win32u_private.h"
#include "ntuser_private.h"
#include "immdev.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(imm);
struct ime_update
{
struct list entry;
WORD vkey;
WORD scan;
DWORD cursor_pos;
WCHAR *comp_str;
WCHAR *result_str;
WCHAR buffer[];
};
struct imc
{
struct user_object obj;
DWORD thread_id;
UINT_PTR client_ptr;
};
struct imm_thread_data
{
struct list entry;
DWORD thread_id;
HWND default_hwnd;
BOOL disable_ime;
UINT window_cnt;
WORD ime_process_scan; /* scan code of the key being processed */
WORD ime_process_vkey; /* vkey of the key being processed */
struct ime_update *update; /* result of ImeProcessKey */
};
static struct list thread_data_list = LIST_INIT( thread_data_list );
static pthread_mutex_t imm_mutex = PTHREAD_MUTEX_INITIALIZER;
static struct list ime_updates = LIST_INIT( ime_updates );
static BOOL disable_ime;
static struct imc *get_imc_ptr( HIMC handle )
{
struct imc *imc = get_user_handle_ptr( handle, NTUSER_OBJ_IMC );
if (imc && imc != OBJ_OTHER_PROCESS) return imc;
WARN( "invalid handle %p\n", handle );
RtlSetLastWin32Error( ERROR_INVALID_HANDLE );
return NULL;
}
static void release_imc_ptr( struct imc *imc )
{
release_user_handle_ptr( imc );
}
/******************************************************************************
* NtUserCreateInputContext (win32u.@)
*/
HIMC WINAPI NtUserCreateInputContext( UINT_PTR client_ptr )
{
struct imc *imc;
HIMC handle;
if (!(imc = malloc( sizeof(*imc) ))) return 0;
imc->client_ptr = client_ptr;
imc->thread_id = GetCurrentThreadId();
if (!(handle = alloc_user_handle( &imc->obj, NTUSER_OBJ_IMC )))
{
free( imc );
return 0;
}
TRACE( "%lx returning %p\n", (long)client_ptr, handle );
return handle;
}
/******************************************************************************
* NtUserDestroyInputContext (win32u.@)
*/
BOOL WINAPI NtUserDestroyInputContext( HIMC handle )
{
struct imc *imc;
TRACE( "%p\n", handle );
if (!(imc = free_user_handle( handle, NTUSER_OBJ_IMC ))) return FALSE;
if (imc == OBJ_OTHER_PROCESS)
{
FIXME( "other process handle %p\n", handle );
return FALSE;
}
free( imc );
return TRUE;
}
/******************************************************************************
* NtUserUpdateInputContext (win32u.@)
*/
BOOL WINAPI NtUserUpdateInputContext( HIMC handle, UINT attr, UINT_PTR value )
{
struct imc *imc;
BOOL ret = TRUE;
TRACE( "%p %u %lx\n", handle, attr, (long)value );
if (!(imc = get_imc_ptr( handle ))) return FALSE;
switch (attr)
{
case NtUserInputContextClientPtr:
imc->client_ptr = value;
break;
default:
FIXME( "unknown attr %u\n", attr );
ret = FALSE;
};
release_imc_ptr( imc );
return ret;
}
/******************************************************************************
* NtUserQueryInputContext (win32u.@)
*/
UINT_PTR WINAPI NtUserQueryInputContext( HIMC handle, UINT attr )
{
struct imc *imc;
UINT_PTR ret;
if (!(imc = get_imc_ptr( handle ))) return FALSE;
switch (attr)
{
case NtUserInputContextClientPtr:
ret = imc->client_ptr;
break;
case NtUserInputContextThreadId:
ret = imc->thread_id;
break;
default:
FIXME( "unknown attr %u\n", attr );
ret = 0;
};
release_imc_ptr( imc );
return ret;
}
/******************************************************************************
* NtUserAssociateInputContext (win32u.@)
*/
UINT WINAPI NtUserAssociateInputContext( HWND hwnd, HIMC ctx, ULONG flags )
{
WND *win;
UINT ret = AICR_OK;
TRACE( "%p %p %x\n", hwnd, ctx, (int)flags );
switch (flags)
{
case 0:
case IACE_IGNORENOCONTEXT:
case IACE_DEFAULT:
break;
default:
FIXME( "unknown flags 0x%x\n", (int)flags );
return AICR_FAILED;
}
if (flags == IACE_DEFAULT)
{
if (!(ctx = get_default_input_context())) return AICR_FAILED;
}
else if (ctx)
{
if (NtUserQueryInputContext( ctx, NtUserInputContextThreadId ) != GetCurrentThreadId())
return AICR_FAILED;
}
if (!(win = get_win_ptr( hwnd )) || win == WND_OTHER_PROCESS || win == WND_DESKTOP)
return AICR_FAILED;
if (ctx && win->tid != GetCurrentThreadId()) ret = AICR_FAILED;
else if (flags != IACE_IGNORENOCONTEXT || win->imc)
{
if (win->imc != ctx && get_focus() == hwnd) ret = AICR_FOCUS_CHANGED;
win->imc = ctx;
}
release_win_ptr( win );
return ret;
}
HIMC get_default_input_context(void)
{
struct ntuser_thread_info *thread_info = NtUserGetThreadInfo();
if (!thread_info->default_imc)
thread_info->default_imc = HandleToUlong( NtUserCreateInputContext( 0 ));
return UlongToHandle( thread_info->default_imc );
}
HIMC get_window_input_context( HWND hwnd )
{
WND *win;
HIMC ret;
if (!(win = get_win_ptr( hwnd )) || win == WND_OTHER_PROCESS || win == WND_DESKTOP)
{
RtlSetLastWin32Error( ERROR_INVALID_WINDOW_HANDLE );
return 0;
}
ret = win->imc;
release_win_ptr( win );
return ret;
}
static HWND detach_default_window( struct imm_thread_data *thread_data )
{
HWND hwnd = thread_data->default_hwnd;
thread_data->default_hwnd = NULL;
thread_data->window_cnt = 0;
return hwnd;
}
static struct imm_thread_data *get_imm_thread_data(void)
{
struct user_thread_info *thread_info = get_user_thread_info();
if (!thread_info->imm_thread_data)
{
struct imm_thread_data *data;
if (!(data = calloc( 1, sizeof( *data )))) return NULL;
data->thread_id = GetCurrentThreadId();
pthread_mutex_lock( &imm_mutex );
list_add_tail( &thread_data_list, &data->entry );
pthread_mutex_unlock( &imm_mutex );
thread_info->imm_thread_data = data;
}
return thread_info->imm_thread_data;
}
BOOL register_imm_window( HWND hwnd )
{
struct imm_thread_data *thread_data;
TRACE( "(%p)\n", hwnd );
if (disable_ime || !needs_ime_window( hwnd ))
return FALSE;
thread_data = get_imm_thread_data();
if (!thread_data || thread_data->disable_ime)
return FALSE;
TRACE( "window_cnt=%u, default_hwnd=%p\n", thread_data->window_cnt + 1, thread_data->default_hwnd );
/* Create default IME window */
if (!thread_data->window_cnt++)
{
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 );
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 );
}
return TRUE;
}
void unregister_imm_window( HWND hwnd )
{
struct imm_thread_data *thread_data = get_user_thread_info()->imm_thread_data;
if (!thread_data) return;
if (thread_data->default_hwnd == hwnd)
{
detach_default_window( thread_data );
return;
}
if (!(win_set_flags( hwnd, 0, WIN_HAS_IME_WIN ) & WIN_HAS_IME_WIN)) return;
/* destroy default IME window */
TRACE( "unregister IME window for %p\n", hwnd );
if (!--thread_data->window_cnt)
{
HWND destroy_hwnd = detach_default_window( thread_data );
if (destroy_hwnd) NtUserDestroyWindow( destroy_hwnd );
}
}
/***********************************************************************
* NtUserDisableThreadIme (win32u.@)
*/
BOOL WINAPI NtUserDisableThreadIme( DWORD thread_id )
{
struct imm_thread_data *thread_data;
if (thread_id == -1)
{
disable_ime = TRUE;
pthread_mutex_lock( &imm_mutex );
LIST_FOR_EACH_ENTRY( thread_data, &thread_data_list, struct imm_thread_data, entry )
{
if (thread_data->thread_id == GetCurrentThreadId()) continue;
if (!thread_data->default_hwnd) continue;
NtUserMessageCall( thread_data->default_hwnd, WM_WINE_DESTROYWINDOW, 0, 0,
0, NtUserSendNotifyMessage, FALSE );
}
pthread_mutex_unlock( &imm_mutex );
}
else if (!thread_id || thread_id == GetCurrentThreadId())
{
if (!(thread_data = get_imm_thread_data())) return FALSE;
thread_data->disable_ime = TRUE;
}
else return FALSE;
if ((thread_data = get_user_thread_info()->imm_thread_data))
{
HWND destroy_hwnd = detach_default_window( thread_data );
NtUserDestroyWindow( destroy_hwnd );
}
return TRUE;
}
HWND get_default_ime_window( HWND hwnd )
{
struct imm_thread_data *thread_data;
HWND ret = 0;
if (hwnd)
{
DWORD thread_id;
if (!(thread_id = get_window_thread( hwnd, NULL ))) return 0;
pthread_mutex_lock( &imm_mutex );
LIST_FOR_EACH_ENTRY( thread_data, &thread_data_list, struct imm_thread_data, entry )
{
if (thread_data->thread_id != thread_id) continue;
ret = thread_data->default_hwnd;
break;
}
pthread_mutex_unlock( &imm_mutex );
}
else if ((thread_data = get_user_thread_info()->imm_thread_data))
{
ret = thread_data->default_hwnd;
}
TRACE( "default for %p is %p\n", hwnd, ret );
return ret;
}
void cleanup_imm_thread(void)
{
struct user_thread_info *thread_info = get_user_thread_info();
if (thread_info->imm_thread_data)
{
pthread_mutex_lock( &imm_mutex );
list_remove( &thread_info->imm_thread_data->entry );
pthread_mutex_unlock( &imm_mutex );
free( thread_info->imm_thread_data );
thread_info->imm_thread_data = NULL;
}
NtUserDestroyInputContext( UlongToHandle( thread_info->client_info.default_imc ));
}
/*****************************************************************************
* 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;
}
static void post_ime_update( HWND hwnd, UINT cursor_pos, WCHAR *comp_str, WCHAR *result_str )
{
static UINT ime_update_count;
struct imm_thread_data *data = get_imm_thread_data();
UINT id = -1, comp_len, result_len;
struct ime_update *update;
TRACE( "hwnd %p, cursor_pos %u, comp_str %s, result_str %s\n", hwnd, cursor_pos,
debugstr_w(comp_str), debugstr_w(result_str) );
comp_len = comp_str ? wcslen( comp_str ) + 1 : 0;
result_len = result_str ? wcslen( result_str ) + 1 : 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;
if (!(update->vkey = data->ime_process_vkey))
{
pthread_mutex_lock( &imm_mutex );
id = update->scan = ++ime_update_count;
update->vkey = VK_PROCESSKEY;
list_add_tail( &ime_updates, &update->entry );
pthread_mutex_unlock( &imm_mutex );
NtUserPostMessage( hwnd, WM_IME_NOTIFY, IMN_WINE_SET_COMP_STRING, id );
}
else
{
update->scan = data->ime_process_scan;
free( data->update );
data->update = update;
}
}
static struct ime_update *find_ime_update( WORD vkey, WORD scan )
{
struct ime_update *update;
LIST_FOR_EACH_ENTRY( update, &ime_updates, struct ime_update, entry )
if (update->vkey == vkey && update->scan == scan) return update;
return NULL;
}
UINT ime_to_tascii_ex( 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( &imm_mutex );
if (!(update = find_ime_update( vkey, lparam )))
{
pthread_mutex_unlock( &imm_mutex );
return STATUS_NOT_FOUND;
}
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;
pthread_mutex_unlock( &imm_mutex );
return STATUS_BUFFER_TOO_SMALL;
}
list_remove( &update->entry );
pthread_mutex_unlock( &imm_mutex );
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 );
return 0;
}
LRESULT ime_driver_call( HWND hwnd, enum wine_ime_call call, WPARAM wparam, LPARAM lparam,
struct ime_driver_call_params *params )
{
LRESULT res;
switch (call)
{
case WINE_IME_PROCESS_KEY:
{
struct imm_thread_data *data = get_imm_thread_data();
data->ime_process_scan = HIWORD(lparam) & 0x1ff;
data->ime_process_vkey = LOWORD(wparam);
res = user_driver->pImeProcessKey( params->himc, wparam, lparam, params->state );
data->ime_process_vkey = data->ime_process_scan = 0;
if (data->update)
{
pthread_mutex_lock( &imm_mutex );
list_add_tail( &ime_updates, &data->update->entry );
pthread_mutex_unlock( &imm_mutex );
data->update = NULL;
res = TRUE;
}
TRACE( "processing scan %#x, vkey %#x -> %u\n", LOWORD(wparam), HIWORD(lparam) & 0x1ff, (UINT)res );
return res;
}
case WINE_IME_TO_ASCII_EX:
return ime_to_tascii_ex( wparam, lparam, params->state, params->compstr, params->himc );
case WINE_IME_POST_UPDATE:
post_ime_update( hwnd, wparam, (WCHAR *)lparam, (WCHAR *)params );
return 0;
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 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 };
void *ret_ptr;
ULONG ret_len;
return !KeUserModeCallback( NtUserImmProcessKey, &params, sizeof(params), &ret_ptr, &ret_len );
}
BOOL WINAPI 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 };
void *ret_ptr;
ULONG ret_len;
return !KeUserModeCallback( NtUserImmTranslateMessage, &params, sizeof(params), &ret_ptr, &ret_len );
}