From cab1336d8ed1abf7318e73452c721f9efe4e69af Mon Sep 17 00:00:00 2001 From: "Kenji Mouri (Qi Lu)" Date: Sun, 14 Jan 2024 23:35:35 +0800 Subject: [PATCH] feat(drivers): add Windows backend for LVGL v9 (#5313) --- Kconfig | 4 + docs/integration/driver/index.rst | 1 + docs/integration/driver/windows.rst | 105 ++++ lv_conf_template.h | 2 + lvgl.h | 3 + src/dev/windows/lv_windows_context.c | 669 ++++++++++++++++++++++++ src/dev/windows/lv_windows_context.h | 123 +++++ src/dev/windows/lv_windows_display.c | 170 ++++++ src/dev/windows/lv_windows_display.h | 127 +++++ src/dev/windows/lv_windows_input.c | 749 +++++++++++++++++++++++++++ src/dev/windows/lv_windows_input.h | 83 +++ src/lv_conf_internal.h | 8 + src/lv_init.c | 7 + 13 files changed, 2051 insertions(+) create mode 100644 docs/integration/driver/windows.rst create mode 100644 src/dev/windows/lv_windows_context.c create mode 100644 src/dev/windows/lv_windows_context.h create mode 100644 src/dev/windows/lv_windows_display.c create mode 100644 src/dev/windows/lv_windows_display.h create mode 100644 src/dev/windows/lv_windows_input.c create mode 100644 src/dev/windows/lv_windows_input.h diff --git a/Kconfig b/Kconfig index 7c06e6372..0011a7b5f 100644 --- a/Kconfig +++ b/Kconfig @@ -1489,6 +1489,10 @@ menu "LVGL configuration" config LV_USE_ILI9341 bool "Use ILI9341 LCD driver" default n + + config LV_USE_WINDOWS + bool "Use LVGL Windows backend" + default n endmenu menu "Examples" diff --git a/docs/integration/driver/index.rst b/docs/integration/driver/index.rst index 7f3d9e85e..b69a5b78c 100644 --- a/docs/integration/driver/index.rst +++ b/docs/integration/driver/index.rst @@ -8,3 +8,4 @@ Drivers display/index touchpad/index X11 + windows diff --git a/docs/integration/driver/windows.rst b/docs/integration/driver/windows.rst new file mode 100644 index 000000000..7dc5016e4 --- /dev/null +++ b/docs/integration/driver/windows.rst @@ -0,0 +1,105 @@ +============================= +Windows Display/Inputs driver +============================= + +Overview +------------- + +The **Windows** display/input `driver `__ offers support for simulating the LVGL display and keyboard/mouse inputs in a Windows Win32 window. + +The main purpose for this driver is for testing/debugging the LVGL application in a **Windows** simulation window via **simulator mode**, or developing a standard **Windows** desktop application with LVGL via **application mode**. + +Here are the **similarity** for simulator mode and application mode. + +- Support LVGL pointer, keypad and encoder devices integration. +- Support Windows touch input. +- Support Windows input method integration input. +- Support Per-monitor DPI Aware (both V1 and V2). + +Here are the **differences** for simulator mode and application mode. + +Simulator Mode +^^^^^^^^^^^^^^ + +- Designed for LVGL simulation scenario. +- Keep the LVGL display resolution all time for trying best to simulate UI layout which will see in their production devices. +- When Windows DPI scaling setting is changed, Windows backend will stretch the display content. + +Application Mode +^^^^^^^^^^^^^^^^ + +- Designed for Windows desktop application development scenario. +- Have the Window resizing support and LVGL display resolution will be changed. +- When Windows DPI scaling setting is changed, the LVGL display DPI value will also be changed. + +Prerequisites +------------- + +The minimum Windows OS version this driver supported is Windows Server 2003, or Windows XP with East Asian language support installed because the input method integration support need that. + +Configure Windows driver +-------------------- + +Enable the Windows driver support in lv_conf.h, by cmake compiler define or by KConfig + +.. code:: c + + #define LV_USE_WINDOWS 1 + +Usage +----- + +.. code:: c + + #include + #include "lvgl/lvgl.h" + #include "lvgl/examples/lv_examples.h" + #include "lvgl/demos/lv_demos.h" + + int main() + { + lv_init(); + + int32_t zoom_level = 100; + bool allow_dpi_override = false; + bool simulator_mode = false; + lv_display_t* display = lv_windows_create_display( + L"LVGL Display Window", + 800, + 480, + zoom_level, + allow_dpi_override, + simulator_mode); + if (!display) + { + return -1; + } + + lv_indev_t* pointer_device = lv_windows_acquire_pointer_indev(display); + if (!pointer_device) + { + return -1; + } + + lv_indev_t* keypad_device = lv_windows_acquire_keypad_indev(display); + if (!keypad_device) + { + return -1; + } + + lv_indev_t* encoder_device = lv_windows_acquire_encoder_indev(display); + if (!encoder_device) + { + return -1; + } + + lv_demo_widgets(); + + while (1) + { + uint32_t time_till_next = lv_timer_handler(); + Sleep(time_till_next); + } + + return 0; + } diff --git a/lv_conf_template.h b/lv_conf_template.h index 465ea7aa0..f3fda03aa 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -893,6 +893,8 @@ #define LV_USE_GENERIC_MIPI (LV_USE_ST7735 | LV_USE_ST7789 | LV_USE_ST7796 | LV_USE_ILI9341) +/* LVGL Windows backend */ +#define LV_USE_WINDOWS 0 /*================== * EXAMPLES diff --git a/lvgl.h b/lvgl.h index 89d397b47..e62e4b321 100644 --- a/lvgl.h +++ b/lvgl.h @@ -133,6 +133,9 @@ extern "C" { #include "src/dev/evdev/lv_evdev.h" +#include "src/dev/windows/lv_windows_input.h" +#include "src/dev/windows/lv_windows_display.h" + #include "src/core/lv_global.h" /********************* * DEFINES diff --git a/src/dev/windows/lv_windows_context.c b/src/dev/windows/lv_windows_context.c new file mode 100644 index 000000000..9c37230a3 --- /dev/null +++ b/src/dev/windows/lv_windows_context.c @@ -0,0 +1,669 @@ +/** + * @file lv_windows_context.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_windows_context.h" +#if LV_USE_WINDOWS + +#include "lv_windows_display.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static uint32_t lv_windows_tick_count_callback(void); + +static void lv_windows_check_display_existence_timer_callback( + lv_timer_t * timer); + +static LRESULT CALLBACK lv_windows_window_message_callback( + HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam); + +bool lv_windows_pointer_device_window_message_handler( + HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam, + LRESULT * plResult); + +bool lv_windows_keypad_device_window_message_handler( + HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam, + LRESULT * plResult); + +bool lv_windows_encoder_device_window_message_handler( + HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam, + LRESULT * plResult); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_windows_platform_init(void) +{ + lv_tick_set_cb(lv_windows_tick_count_callback); + + lv_timer_create( + lv_windows_check_display_existence_timer_callback, + 200, + NULL); + + // Try to ensure the default group exists. + { + lv_group_t * default_group = lv_group_get_default(); + if(!default_group) { + default_group = lv_group_create(); + if(default_group) { + lv_group_set_default(default_group); + } + } + } + + WNDCLASSEXW window_class; + lv_memzero(&window_class, sizeof(WNDCLASSEXW)); + window_class.cbSize = sizeof(WNDCLASSEXW); + window_class.style = 0; + window_class.lpfnWndProc = lv_windows_window_message_callback; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = NULL; + window_class.hIcon = NULL; + window_class.hCursor = LoadCursorW(NULL, IDC_ARROW); + window_class.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + window_class.lpszMenuName = NULL; + window_class.lpszClassName = L"LVGL.Window"; + window_class.hIconSm = NULL; + LV_ASSERT_NULL(RegisterClassExW(&window_class)); +} + +lv_windows_window_context_t * lv_windows_get_window_context( + HWND window_handle) +{ + return (lv_windows_window_context_t *)( + GetPropW(window_handle, L"LVGL.Window.Context")); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static uint32_t lv_windows_tick_count_callback(void) +{ + return GetTickCount(); +} + +static void lv_windows_check_display_existence_timer_callback( + lv_timer_t * timer) +{ + if(!lv_display_get_next(NULL)) { + // Don't use lv_deinit() due to it will cause exception when parallel + // rendering is enabled. + exit(0); + } +} + +static HDC lv_windows_create_frame_buffer( + HWND window_handle, + LONG width, + LONG height, + UINT32 ** pixel_buffer, + SIZE_T * pixel_buffer_size) +{ + HDC frame_buffer_dc_handle = NULL; + + LV_ASSERT_NULL(pixel_buffer); + LV_ASSERT_NULL(pixel_buffer_size); + + HDC window_dc_handle = GetDC(window_handle); + if(window_dc_handle) { + frame_buffer_dc_handle = CreateCompatibleDC(window_dc_handle); + ReleaseDC(window_handle, window_dc_handle); + } + + if(frame_buffer_dc_handle) { +#if (LV_COLOR_DEPTH == 32) || (LV_COLOR_DEPTH == 24) + BITMAPINFO bitmap_info = { 0 }; +#elif (LV_COLOR_DEPTH == 16) + typedef struct _BITMAPINFO_16BPP { + BITMAPINFOHEADER bmiHeader; + DWORD bmiColorMask[3]; + } BITMAPINFO_16BPP, * PBITMAPINFO_16BPP; + + BITMAPINFO_16BPP bitmap_info = { 0 }; +#else +#error [lv_windows] Unsupported LV_COLOR_DEPTH. +#endif + + bitmap_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bitmap_info.bmiHeader.biWidth = width; + bitmap_info.bmiHeader.biHeight = -height; + bitmap_info.bmiHeader.biPlanes = 1; + bitmap_info.bmiHeader.biBitCount = lv_color_format_get_bpp( + LV_COLOR_FORMAT_NATIVE); +#if (LV_COLOR_DEPTH == 32) || (LV_COLOR_DEPTH == 24) + bitmap_info.bmiHeader.biCompression = BI_RGB; +#elif (LV_COLOR_DEPTH == 16) + bitmap_info.bmiHeader.biCompression = BI_BITFIELDS; + bitmap_info.bmiColorMask[0] = 0xF800; + bitmap_info.bmiColorMask[1] = 0x07E0; + bitmap_info.bmiColorMask[2] = 0x001F; +#else +#error [lv_windows] Unsupported LV_COLOR_DEPTH. +#endif + + HBITMAP hBitmap = CreateDIBSection( + frame_buffer_dc_handle, + (PBITMAPINFO)(&bitmap_info), + DIB_RGB_COLORS, + (void **)pixel_buffer, + NULL, + 0); + if(hBitmap) { + *pixel_buffer_size = width * height; + *pixel_buffer_size *= lv_color_format_get_size( + LV_COLOR_FORMAT_NATIVE); + + DeleteObject(SelectObject(frame_buffer_dc_handle, hBitmap)); + DeleteObject(hBitmap); + } + else { + DeleteDC(frame_buffer_dc_handle); + frame_buffer_dc_handle = NULL; + } + } + + return frame_buffer_dc_handle; +} + +static void lv_windows_display_timer_callback(lv_timer_t * timer) +{ + lv_windows_window_context_t * context = lv_timer_get_user_data(timer); + LV_ASSERT_NULL(context); + + if(!context->display_resolution_changed) { + return; + } + + lv_display_set_resolution( + context->display_device_object, + context->requested_display_resolution.x, + context->requested_display_resolution.y); + + int32_t hor_res = lv_display_get_horizontal_resolution( + context->display_device_object); + int32_t ver_res = lv_display_get_vertical_resolution( + context->display_device_object); + + HWND window_handle = lv_windows_get_display_window_handle( + context->display_device_object); + if(window_handle) { + if(context->display_framebuffer_context_handle) { + context->display_framebuffer_base = NULL; + context->display_framebuffer_size = 0; + DeleteDC(context->display_framebuffer_context_handle); + context->display_framebuffer_context_handle = NULL; + } + + context->display_framebuffer_context_handle = + lv_windows_create_frame_buffer( + window_handle, + hor_res, + ver_res, + &context->display_framebuffer_base, + &context->display_framebuffer_size); + if(context->display_framebuffer_context_handle) { + lv_display_set_buffers( + context->display_device_object, + context->display_framebuffer_base, + NULL, + context->display_framebuffer_size, + LV_DISPLAY_RENDER_MODE_DIRECT); + } + } + + context->display_resolution_changed = false; + context->requested_display_resolution.x = 0; + context->requested_display_resolution.y = 0; +} + +static void lv_windows_display_driver_flush_callback( + lv_display_t * display, + const lv_area_t * area, + uint8_t * px_map) +{ + HWND window_handle = lv_windows_get_display_window_handle(display); + if(!window_handle) { + lv_display_flush_ready(display); + return; + } + + lv_windows_window_context_t * context = lv_windows_get_window_context( + window_handle); + if(!context) { + lv_display_flush_ready(display); + return; + } + + if(lv_display_flush_is_last(display)) { +#if (LV_COLOR_DEPTH == 32) || \ + (LV_COLOR_DEPTH == 24) || \ + (LV_COLOR_DEPTH == 16) + UNREFERENCED_PARAMETER(px_map); +#else +#error [lv_windows] Unsupported LV_COLOR_DEPTH. +#endif + + HDC hdc = GetDC(window_handle); + if(hdc) { + SetStretchBltMode(hdc, HALFTONE); + + RECT client_rect; + GetClientRect(window_handle, &client_rect); + + int32_t width = lv_windows_zoom_to_logical( + client_rect.right - client_rect.left, + context->zoom_level); + int32_t height = lv_windows_zoom_to_logical( + client_rect.bottom - client_rect.top, + context->zoom_level); + if(context->simulator_mode) { + width = lv_windows_dpi_to_logical(width, context->window_dpi); + height = lv_windows_dpi_to_logical(height, context->window_dpi); + } + + StretchBlt( + hdc, + client_rect.left, + client_rect.top, + client_rect.right - client_rect.left, + client_rect.bottom - client_rect.top, + context->display_framebuffer_context_handle, + 0, + 0, + width, + height, + SRCCOPY); + + ReleaseDC(window_handle, hdc); + } + } + + lv_display_flush_ready(display); +} + +static UINT lv_windows_get_dpi_for_window(HWND window_handle) +{ + UINT result = (UINT)(-1); + + HMODULE module_handle = LoadLibraryW(L"SHCore.dll"); + if(module_handle) { + typedef enum MONITOR_DPI_TYPE_PRIVATE { + MDT_EFFECTIVE_DPI = 0, + MDT_ANGULAR_DPI = 1, + MDT_RAW_DPI = 2, + MDT_DEFAULT = MDT_EFFECTIVE_DPI + } MONITOR_DPI_TYPE_PRIVATE; + + typedef HRESULT(WINAPI * function_type)( + HMONITOR, MONITOR_DPI_TYPE_PRIVATE, UINT *, UINT *); + + function_type function = (function_type)( + GetProcAddress(module_handle, "GetDpiForMonitor")); + if(function) { + HMONITOR MonitorHandle = MonitorFromWindow( + window_handle, + MONITOR_DEFAULTTONEAREST); + + UINT dpiX = 0; + UINT dpiY = 0; + if(SUCCEEDED(function( + MonitorHandle, + MDT_EFFECTIVE_DPI, + &dpiX, + &dpiY))) { + result = dpiX; + } + } + + FreeLibrary(module_handle); + } + + if(result == (UINT)(-1)) { + HDC hWindowDC = GetDC(window_handle); + if(hWindowDC) { + result = GetDeviceCaps(hWindowDC, LOGPIXELSX); + ReleaseDC(window_handle, hWindowDC); + } + } + + if(result == (UINT)(-1)) { + result = USER_DEFAULT_SCREEN_DPI; + } + + return result; +} + +static BOOL lv_windows_register_touch_window( + HWND window_handle, + ULONG flags) +{ + HMODULE module_handle = GetModuleHandleW(L"user32.dll"); + if(!module_handle) { + return FALSE; + } + + typedef BOOL(WINAPI * function_type)(HWND, ULONG); + + function_type function = (function_type)( + GetProcAddress(module_handle, "RegisterTouchWindow")); + if(!function) { + return FALSE; + } + + return function(window_handle, flags); +} + +static BOOL lv_windows_enable_child_window_dpi_message( + HWND WindowHandle) +{ + // The private Per-Monitor DPI Awareness support extension is Windows 10 + // only. We don't need the private Per-Monitor DPI Awareness support + // extension if the Per-Monitor (V2) DPI Awareness exists. + OSVERSIONINFOEXW os_version_info_ex = { 0 }; + os_version_info_ex.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW); + os_version_info_ex.dwMajorVersion = 10; + os_version_info_ex.dwMinorVersion = 0; + os_version_info_ex.dwBuildNumber = 14986; + if(!VerifyVersionInfoW( + &os_version_info_ex, + VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER, + VerSetConditionMask( + VerSetConditionMask( + VerSetConditionMask( + 0, + VER_MAJORVERSION, + VER_GREATER_EQUAL), + VER_MINORVERSION, + VER_GREATER_EQUAL), + VER_BUILDNUMBER, + VER_LESS))) { + return FALSE; + } + + HMODULE module_handle = GetModuleHandleW(L"user32.dll"); + if(!module_handle) { + return FALSE; + } + + typedef BOOL(WINAPI * function_type)(HWND, BOOL); + + function_type function = (function_type)( + GetProcAddress(module_handle, "EnableChildWindowDpiMessage")); + if(!function) { + return FALSE; + } + + return function(WindowHandle, TRUE); +} + +static LRESULT CALLBACK lv_windows_window_message_callback( + HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam) +{ + switch(uMsg) { + case WM_CREATE: { + // Note: Return -1 directly because WM_DESTROY message will be sent + // when destroy the window automatically. We free the resource when + // processing the WM_DESTROY message of this window. + + lv_windows_create_display_data_t * data = + (lv_windows_create_display_data_t *)( + ((LPCREATESTRUCTW)(lParam))->lpCreateParams); + if(!data) { + return -1; + } + + lv_windows_window_context_t * context = + (lv_windows_window_context_t *)(HeapAlloc( + GetProcessHeap(), + HEAP_ZERO_MEMORY, + sizeof(lv_windows_window_context_t))); + if(!context) { + return -1; + } + + if(!SetPropW(hWnd, L"LVGL.Window.Context", (HANDLE)(context))) { + return -1; + } + + context->window_dpi = lv_windows_get_dpi_for_window(hWnd); + context->zoom_level = data->zoom_level; + context->allow_dpi_override = data->allow_dpi_override; + context->simulator_mode = data->simulator_mode; + + context->display_timer_object = lv_timer_create( + lv_windows_display_timer_callback, + LV_DEF_REFR_PERIOD, + context); + + context->display_resolution_changed = false; + context->requested_display_resolution.x = 0; + context->requested_display_resolution.y = 0; + + context->display_device_object = lv_display_create(0, 0); + if(!context->display_device_object) { + return -1; + } + RECT request_content_size; + GetWindowRect(hWnd, &request_content_size); + lv_display_set_resolution( + context->display_device_object, + request_content_size.right - request_content_size.left, + request_content_size.bottom - request_content_size.top); + lv_display_set_flush_cb( + context->display_device_object, + lv_windows_display_driver_flush_callback); + lv_display_set_driver_data( + context->display_device_object, + hWnd); + if(!context->allow_dpi_override) { + lv_display_set_dpi( + context->display_device_object, + context->window_dpi); + } + + if(context->simulator_mode) { + context->display_resolution_changed = true; + context->requested_display_resolution.x = + lv_display_get_horizontal_resolution( + context->display_device_object); + context->requested_display_resolution.y = + lv_display_get_vertical_resolution( + context->display_device_object); + } + + lv_windows_register_touch_window(hWnd, 0); + + lv_windows_enable_child_window_dpi_message(hWnd); + + break; + } + case WM_SIZE: { + if(wParam != SIZE_MINIMIZED) { + lv_windows_window_context_t * context = (lv_windows_window_context_t *)( + lv_windows_get_window_context(hWnd)); + if(context) { + if(!context->simulator_mode) { + context->display_resolution_changed = true; + context->requested_display_resolution.x = LOWORD(lParam); + context->requested_display_resolution.y = HIWORD(lParam); + } + else { + int32_t window_width = lv_windows_dpi_to_physical( + lv_windows_zoom_to_physical( + lv_display_get_horizontal_resolution( + context->display_device_object), + context->zoom_level), + context->window_dpi); + int32_t window_height = lv_windows_dpi_to_physical( + lv_windows_zoom_to_physical( + lv_display_get_vertical_resolution( + context->display_device_object), + context->zoom_level), + context->window_dpi); + + RECT window_rect; + GetWindowRect(hWnd, &window_rect); + + RECT client_rect; + GetClientRect(hWnd, &client_rect); + + int32_t original_window_width = + window_rect.right - window_rect.left; + int32_t original_window_height = + window_rect.bottom - window_rect.top; + + int32_t original_client_width = + client_rect.right - client_rect.left; + int32_t original_client_height = + client_rect.bottom - client_rect.top; + + int32_t reserved_width = + original_window_width - original_client_width; + int32_t reserved_height = + original_window_height - original_client_height; + + SetWindowPos( + hWnd, + NULL, + 0, + 0, + reserved_width + window_width, + reserved_height + window_height, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE); + } + } + } + break; + } + case WM_DPICHANGED: { + lv_windows_window_context_t * context = (lv_windows_window_context_t *)( + lv_windows_get_window_context(hWnd)); + if(context) { + context->window_dpi = HIWORD(wParam); + + if(!context->allow_dpi_override) { + lv_display_set_dpi( + context->display_device_object, + context->window_dpi); + } + + LPRECT suggested_rect = (LPRECT)lParam; + + SetWindowPos( + hWnd, + NULL, + suggested_rect->left, + suggested_rect->top, + suggested_rect->right, + suggested_rect->bottom, + SWP_NOZORDER | SWP_NOACTIVATE); + } + + break; + } + case WM_ERASEBKGND: { + return TRUE; + } + case WM_DESTROY: { + lv_windows_window_context_t * context = (lv_windows_window_context_t *)( + RemovePropW(hWnd, L"LVGL.Window.Context")); + if(context) { + lv_display_t * display_device_object = + context->display_device_object; + context->display_device_object = NULL; + lv_display_delete(display_device_object); + DeleteDC(context->display_framebuffer_context_handle); + + lv_timer_delete(context->display_timer_object); + + HeapFree(GetProcessHeap(), 0, context); + } + + PostQuitMessage(0); + + break; + } + default: { + lv_windows_window_context_t * context = (lv_windows_window_context_t *)( + lv_windows_get_window_context(hWnd)); + if(context) { + LRESULT lResult = 0; + if(context->pointer.indev && + lv_windows_pointer_device_window_message_handler( + hWnd, + uMsg, + wParam, + lParam, + &lResult)) { + return lResult; + } + else if(context->keypad.indev && + lv_windows_keypad_device_window_message_handler( + hWnd, + uMsg, + wParam, + lParam, + &lResult)) { + return lResult; + } + else if(context->encoder.indev && + lv_windows_encoder_device_window_message_handler( + hWnd, + uMsg, + wParam, + lParam, + &lResult)) { + return lResult; + } + } + + return DefWindowProcW(hWnd, uMsg, wParam, lParam); + } + } + + return 0; +} + +#endif // LV_USE_WINDOWS diff --git a/src/dev/windows/lv_windows_context.h b/src/dev/windows/lv_windows_context.h new file mode 100644 index 000000000..20e72af37 --- /dev/null +++ b/src/dev/windows/lv_windows_context.h @@ -0,0 +1,123 @@ +/** + * @file lv_windows_context.h + * + */ + +#ifndef LV_WINDOWS_CONTEXT_H +#define LV_WINDOWS_CONTEXT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "../../display/lv_display.h" +#include "../../indev/lv_indev.h" + +#if LV_USE_WINDOWS + +#include + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct _lv_windows_pointer_context_t { + lv_indev_state_t state; + lv_point_t point; + lv_indev_t * indev; +} lv_windows_pointer_context_t; + +typedef struct _lv_windows_keypad_queue_item_t { + uint32_t key; + lv_indev_state_t state; +} lv_windows_keypad_queue_item_t; + +typedef struct _lv_windows_keypad_context_t { + CRITICAL_SECTION mutex; + lv_ll_t queue; + uint16_t utf16_high_surrogate; + uint16_t utf16_low_surrogate; + lv_indev_t * indev; +} lv_windows_keypad_context_t; + +typedef struct _lv_windows_encoder_context_t { + lv_indev_state_t state; + int16_t enc_diff; + lv_indev_t * indev; +} lv_windows_encoder_context_t; + +typedef struct _lv_windows_window_context_t { + lv_display_t * display_device_object; + lv_timer_t * display_timer_object; + + int32_t window_dpi; + int32_t zoom_level; + bool allow_dpi_override; + bool simulator_mode; + bool display_resolution_changed; + lv_point_t requested_display_resolution; + + HDC display_framebuffer_context_handle; + uint32_t * display_framebuffer_base; + size_t display_framebuffer_size; + + lv_windows_pointer_context_t pointer; + lv_windows_keypad_context_t keypad; + lv_windows_encoder_context_t encoder; + +} lv_windows_window_context_t; + +typedef struct _lv_windows_create_display_data_t { + const wchar_t * title; + int32_t hor_res; + int32_t ver_res; + int32_t zoom_level; + bool allow_dpi_override; + bool simulator_mode; + HANDLE mutex; + lv_display_t * display; +} lv_windows_create_display_data_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * @brief Initialize the LVGL Windows backend. + * @remark This is a private API which is used for LVGL Windows backend + * implementation. LVGL users shouldn't use that because the + * LVGL has already used it in lv_init. +*/ +void lv_windows_platform_init(void); + +/** + * @brief Get the window context from specific LVGL display window. + * @param window_handle The window handle of specific LVGL display window. + * @return The window context from specific LVGL display window. + * @remark This is a private API which is used for LVGL Windows backend + * implementation. LVGL users shouldn't use that because the + * maintainer doesn't promise the application binary interface + * compatibility for this API. +*/ +lv_windows_window_context_t * lv_windows_get_window_context( + HWND window_handle); + +/********************** + * MACROS + **********************/ + +#endif // LV_USE_WINDOWS + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_WINDOWS_CONTEXT_H*/ diff --git a/src/dev/windows/lv_windows_display.c b/src/dev/windows/lv_windows_display.c new file mode 100644 index 000000000..b21e8642e --- /dev/null +++ b/src/dev/windows/lv_windows_display.c @@ -0,0 +1,170 @@ +/** + * @file lv_windows_display.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_windows_display.h" +#if LV_USE_WINDOWS + +#include "lv_windows_context.h" + +#include + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static unsigned int __stdcall lv_windows_display_thread_entrypoint( + void * parameter); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_display_t * lv_windows_create_display( + const wchar_t * title, + int32_t hor_res, + int32_t ver_res, + int32_t zoom_level, + bool allow_dpi_override, + bool simulator_mode) +{ + lv_windows_create_display_data_t data; + + lv_memzero(&data, sizeof(lv_windows_create_display_data_t)); + data.title = title; + data.hor_res = hor_res; + data.ver_res = ver_res; + data.zoom_level = zoom_level; + data.allow_dpi_override = allow_dpi_override; + data.simulator_mode = simulator_mode; + data.mutex = CreateEventExW(NULL, NULL, 0, EVENT_ALL_ACCESS); + data.display = NULL; + if(!data.mutex) { + return NULL; + } + + HANDLE thread = (HANDLE)_beginthreadex( + NULL, + 0, + lv_windows_display_thread_entrypoint, + &data, + 0, + NULL); + LV_ASSERT_NULL(thread); + + WaitForSingleObjectEx(data.mutex, INFINITE, FALSE); + + if(thread) { + CloseHandle(thread); + } + + if(data.mutex) { + CloseHandle(data.mutex); + } + + return data.display; +} + +HWND lv_windows_get_display_window_handle(lv_display_t * display) +{ + return (HWND)lv_display_get_driver_data(display); +} + +int32_t lv_windows_zoom_to_logical(int32_t physical, int32_t zoom_level) +{ + return MulDiv(physical, LV_WINDOWS_ZOOM_BASE_LEVEL, zoom_level); +} + +int32_t lv_windows_zoom_to_physical(int32_t logical, int32_t zoom_level) +{ + return MulDiv(logical, zoom_level, LV_WINDOWS_ZOOM_BASE_LEVEL); +} + +int32_t lv_windows_dpi_to_logical(int32_t physical, int32_t dpi) +{ + return MulDiv(physical, USER_DEFAULT_SCREEN_DPI, dpi); +} + +int32_t lv_windows_dpi_to_physical(int32_t logical, int32_t dpi) +{ + return MulDiv(logical, dpi, USER_DEFAULT_SCREEN_DPI); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static unsigned int __stdcall lv_windows_display_thread_entrypoint( + void * parameter) +{ + lv_windows_create_display_data_t * data = parameter; + LV_ASSERT_NULL(data); + + DWORD window_style = WS_OVERLAPPEDWINDOW; + if(data->simulator_mode) { + window_style &= ~(WS_SIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME); + } + + HWND window_handle = CreateWindowExW( + WS_EX_CLIENTEDGE, + L"LVGL.Window", + data->title, + window_style, + CW_USEDEFAULT, + 0, + data->hor_res, + data->ver_res, + NULL, + NULL, + NULL, + data); + if(!window_handle) { + return 0; + } + + lv_windows_window_context_t * context = lv_windows_get_window_context( + window_handle); + if(!context) { + return 0; + } + + data->display = context->display_device_object; + + ShowWindow(window_handle, SW_SHOW); + UpdateWindow(window_handle); + + LV_ASSERT_NULL(SetEvent(data->mutex)); + + data = NULL; + + MSG message; + while(GetMessageW(&message, NULL, 0, 0)) { + TranslateMessage(&message); + DispatchMessageW(&message); + } + + return 0; +} + +#endif // LV_USE_WINDOWS diff --git a/src/dev/windows/lv_windows_display.h b/src/dev/windows/lv_windows_display.h new file mode 100644 index 000000000..b9c6f8e94 --- /dev/null +++ b/src/dev/windows/lv_windows_display.h @@ -0,0 +1,127 @@ +/** + * @file lv_windows_display.h + * + */ + +#ifndef LV_WINDOWS_DISPLAY_H +#define LV_WINDOWS_DISPLAY_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "../../display/lv_display.h" +#include "../../indev/lv_indev.h" + +#if LV_USE_WINDOWS + +#include + +/********************* + * DEFINES + *********************/ + +#define LV_WINDOWS_ZOOM_BASE_LEVEL 100 + +#ifndef USER_DEFAULT_SCREEN_DPI +#define USER_DEFAULT_SCREEN_DPI 96 +#endif + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * @brief Create a LVGL display object. + * @param title The window title of LVGL display. + * @param hor_res The horizontal resolution value of LVGL display. + * @param ver_res The vertical resolution value of LVGL display. + * @param zoom_level The zoom level value. Base value is 100 a.k.a 100%. + * @param allow_dpi_override Allow DPI override if true, or follow the + * Windows DPI scaling setting dynamically. + * @param simulator_mode Create simulator mode display if true, or create + * application mode display. + * @return The created LVGL display object. +*/ +lv_display_t * lv_windows_create_display( + const wchar_t * title, + int32_t hor_res, + int32_t ver_res, + int32_t zoom_level, + bool allow_dpi_override, + bool simulator_mode); + +/** + * @brief Get the window handle from specific LVGL display object. + * @param display The specific LVGL display object. + * @return The window handle from specific LVGL display object. +*/ +HWND lv_windows_get_display_window_handle(lv_display_t * display); + +/** + * @brief Get logical pixel value from physical pixel value taken account + * with zoom level. + * @param physical The physical pixel value taken account with zoom level. + * @param zoom_level The zoom level value. Base value is 100 a.k.a 100%. + * @return The logical pixel value. + * @remark It uses the same calculation style as Windows OS implementation. + * It will be useful for integrate LVGL Windows backend to other + * Windows applications. +*/ +int32_t lv_windows_zoom_to_logical(int32_t physical, int32_t zoom_level); + +/** + * @brief Get physical pixel value taken account with zoom level from + * logical pixel value. + * @param logical The logical pixel value. + * @param zoom_level The zoom level value. Base value is 100 a.k.a 100%. + * @return The physical pixel value taken account with zoom level. + * @remark It uses the same calculation style as Windows OS implementation. + * It will be useful for integrate LVGL Windows backend to other + * Windows applications. +*/ +int32_t lv_windows_zoom_to_physical(int32_t logical, int32_t zoom_level); + +/** + * @brief Get logical pixel value from physical pixel value taken account + * with DPI scaling. + * @param physical The physical pixel value taken account with DPI scaling. + * @param dpi The DPI scaling value. Base value is USER_DEFAULT_SCREEN_DPI. + * @return The logical pixel value. + * @remark It uses the same calculation style as Windows OS implementation. + * It will be useful for integrate LVGL Windows backend to other + * Windows applications. +*/ +int32_t lv_windows_dpi_to_logical(int32_t physical, int32_t dpi); + +/** + * @brief Get physical pixel value taken account with DPI scaling from + * logical pixel value. + * @param logical The logical pixel value. + * @param dpi The DPI scaling value. Base value is USER_DEFAULT_SCREEN_DPI. + * @return The physical pixel value taken account with DPI scaling. + * @remark It uses the same calculation style as Windows OS implementation. + * It will be useful for integrate LVGL Windows backend to other + * Windows applications. +*/ +int32_t lv_windows_dpi_to_physical(int32_t logical, int32_t dpi); + +/********************** + * MACROS + **********************/ + +#endif // LV_USE_WINDOWS + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_WINDOWS_DISPLAY_H*/ diff --git a/src/dev/windows/lv_windows_input.c b/src/dev/windows/lv_windows_input.c new file mode 100644 index 000000000..b92a8ca6d --- /dev/null +++ b/src/dev/windows/lv_windows_input.c @@ -0,0 +1,749 @@ +/** + * @file lv_windows_input.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_windows_input.h" +#if LV_USE_WINDOWS + +#include "lv_windows_context.h" +#include "lv_windows_display.h" + +#include + +#if _MSC_VER >= 1200 + #pragma comment(lib, "Imm32.lib") +#endif + +#include "../../widgets/textarea/lv_textarea.h" +#include "../../widgets/keyboard/lv_keyboard.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void lv_windows_pointer_driver_read_callback( + lv_indev_t * indev, + lv_indev_data_t * data); + +static void lv_windows_release_pointer_device_event_callback(lv_event_t * e); + +static void lv_windows_keypad_driver_read_callback( + lv_indev_t * indev, + lv_indev_data_t * data); + +static void lv_windows_release_keypad_device_event_callback(lv_event_t * e); + +static void lv_windows_encoder_driver_read_callback( + lv_indev_t * indev, + lv_indev_data_t * data); + +static void lv_windows_release_encoder_device_event_callback(lv_event_t * e); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +HWND lv_windows_get_indev_window_handle(lv_indev_t * indev) +{ + return lv_windows_get_display_window_handle(lv_indev_get_display(indev)); +} + +lv_indev_t * lv_windows_acquire_pointer_indev(lv_display_t * display) +{ + HWND window_handle = lv_windows_get_display_window_handle(display); + if(!window_handle) { + return NULL; + } + + lv_windows_window_context_t * context = lv_windows_get_window_context( + window_handle); + if(!context) { + return NULL; + } + + if(!context->pointer.indev) { + context->pointer.state = LV_INDEV_STATE_RELEASED; + context->pointer.point.x = 0; + context->pointer.point.y = 0; + + context->pointer.indev = lv_indev_create(); + if(context->pointer.indev) { + lv_indev_set_type( + context->pointer.indev, + LV_INDEV_TYPE_POINTER); + lv_indev_set_read_cb( + context->pointer.indev, + lv_windows_pointer_driver_read_callback); + lv_indev_set_display( + context->pointer.indev, + context->display_device_object); + lv_indev_add_event_cb( + context->pointer.indev, + lv_windows_release_pointer_device_event_callback, + LV_EVENT_DELETE, + context->pointer.indev); + lv_indev_set_group( + context->pointer.indev, + lv_group_get_default()); + } + } + + return context->pointer.indev; +} + +lv_indev_t * lv_windows_acquire_keypad_indev(lv_display_t * display) +{ + HWND window_handle = lv_windows_get_display_window_handle(display); + if(!window_handle) { + return NULL; + } + + lv_windows_window_context_t * context = lv_windows_get_window_context( + window_handle); + if(!context) { + return NULL; + } + + if(!context->keypad.indev) { + InitializeCriticalSection(&context->keypad.mutex); + _lv_ll_init( + &context->keypad.queue, + sizeof(lv_windows_keypad_queue_item_t)); + context->keypad.utf16_high_surrogate = 0; + context->keypad.utf16_low_surrogate = 0; + + context->keypad.indev = lv_indev_create(); + if(context->keypad.indev) { + lv_indev_set_type( + context->keypad.indev, + LV_INDEV_TYPE_KEYPAD); + lv_indev_set_read_cb( + context->keypad.indev, + lv_windows_keypad_driver_read_callback); + lv_indev_set_display( + context->keypad.indev, + context->display_device_object); + lv_indev_add_event_cb( + context->keypad.indev, + lv_windows_release_keypad_device_event_callback, + LV_EVENT_DELETE, + context->keypad.indev); + lv_indev_set_group( + context->keypad.indev, + lv_group_get_default()); + } + } + + return context->keypad.indev; +} + +lv_indev_t * lv_windows_acquire_encoder_indev(lv_display_t * display) +{ + HWND window_handle = lv_windows_get_display_window_handle(display); + if(!window_handle) { + return NULL; + } + + lv_windows_window_context_t * context = lv_windows_get_window_context( + window_handle); + if(!context) { + return NULL; + } + + if(!context->encoder.indev) { + context->encoder.state = LV_INDEV_STATE_RELEASED; + context->encoder.enc_diff = 0; + + context->encoder.indev = lv_indev_create(); + if(context->encoder.indev) { + lv_indev_set_type( + context->encoder.indev, + LV_INDEV_TYPE_ENCODER); + lv_indev_set_read_cb( + context->encoder.indev, + lv_windows_encoder_driver_read_callback); + lv_indev_set_display( + context->encoder.indev, + context->display_device_object); + lv_indev_add_event_cb( + context->encoder.indev, + lv_windows_release_encoder_device_event_callback, + LV_EVENT_DELETE, + context->encoder.indev); + lv_indev_set_group( + context->encoder.indev, + lv_group_get_default()); + } + } + + return context->encoder.indev; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_windows_pointer_driver_read_callback( + lv_indev_t * indev, + lv_indev_data_t * data) +{ + lv_windows_window_context_t * context = lv_windows_get_window_context( + lv_windows_get_indev_window_handle(indev)); + if(!context) { + return; + } + + data->state = context->pointer.state; + data->point = context->pointer.point; +} + +static void lv_windows_release_pointer_device_event_callback(lv_event_t * e) +{ + lv_indev_t * indev = (lv_indev_t *)lv_event_get_user_data(e); + if(!indev) { + return; + } + + HWND window_handle = lv_windows_get_indev_window_handle(indev); + if(!window_handle) { + return; + } + + lv_windows_window_context_t * context = lv_windows_get_window_context( + window_handle); + if(!context) { + return; + } + + context->pointer.state = LV_INDEV_STATE_RELEASED; + context->pointer.point.x = 0; + context->pointer.point.y = 0; + + context->pointer.indev = NULL; +} + +static BOOL lv_windows_get_touch_input_info( + HTOUCHINPUT touch_input_handle, + UINT input_count, + PTOUCHINPUT inputs, + int item_size) +{ + HMODULE module_handle = GetModuleHandleW(L"user32.dll"); + if(!module_handle) { + return FALSE; + } + + typedef BOOL(WINAPI * function_type)(HTOUCHINPUT, UINT, PTOUCHINPUT, int); + + function_type function = (function_type)( + GetProcAddress(module_handle, "GetTouchInputInfo")); + if(!function) { + return FALSE; + } + + return function(touch_input_handle, input_count, inputs, item_size); +} + +static BOOL lv_windows_close_touch_input_handle( + HTOUCHINPUT touch_input_handle) +{ + HMODULE module_handle = GetModuleHandleW(L"user32.dll"); + if(!module_handle) { + return FALSE; + } + + typedef BOOL(WINAPI * function_type)(HTOUCHINPUT); + + function_type function = (function_type)( + GetProcAddress(module_handle, "CloseTouchInputHandle")); + if(!function) { + return FALSE; + } + + return function(touch_input_handle); +} + +bool lv_windows_pointer_device_window_message_handler( + HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam, + LRESULT * plResult) +{ + switch(uMsg) { + case WM_MOUSEMOVE: { + lv_windows_window_context_t * context = (lv_windows_window_context_t *)( + lv_windows_get_window_context(hWnd)); + if(context) { + int32_t hor_res = lv_display_get_horizontal_resolution( + context->display_device_object); + int32_t ver_res = lv_display_get_vertical_resolution( + context->display_device_object); + + context->pointer.point.x = lv_windows_zoom_to_logical( + GET_X_LPARAM(lParam), + context->zoom_level); + context->pointer.point.y = lv_windows_zoom_to_logical( + GET_Y_LPARAM(lParam), + context->zoom_level); + if(context->simulator_mode) { + context->pointer.point.x = lv_windows_dpi_to_logical( + context->pointer.point.x, + context->window_dpi); + context->pointer.point.y = lv_windows_dpi_to_logical( + context->pointer.point.y, + context->window_dpi); + } + if(context->pointer.point.x < 0) { + context->pointer.point.x = 0; + } + if(context->pointer.point.x > hor_res - 1) { + context->pointer.point.x = hor_res - 1; + } + if(context->pointer.point.y < 0) { + context->pointer.point.y = 0; + } + if(context->pointer.point.y > ver_res - 1) { + context->pointer.point.y = ver_res - 1; + } + } + + break; + } + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: { + lv_windows_window_context_t * context = (lv_windows_window_context_t *)( + lv_windows_get_window_context(hWnd)); + if(context) { + context->pointer.state = ( + uMsg == WM_LBUTTONDOWN + ? LV_INDEV_STATE_PRESSED + : LV_INDEV_STATE_RELEASED); + } + + break; + } + case WM_TOUCH: { + lv_windows_window_context_t * context = (lv_windows_window_context_t *)( + lv_windows_get_window_context(hWnd)); + if(context) { + UINT input_count = LOWORD(wParam); + HTOUCHINPUT touch_input_handle = (HTOUCHINPUT)(lParam); + + PTOUCHINPUT inputs = malloc(input_count * sizeof(TOUCHINPUT)); + if(inputs) { + if(lv_windows_get_touch_input_info( + touch_input_handle, + input_count, + inputs, + sizeof(TOUCHINPUT))) { + for(UINT i = 0; i < input_count; ++i) { + POINT Point; + Point.x = TOUCH_COORD_TO_PIXEL(inputs[i].x); + Point.y = TOUCH_COORD_TO_PIXEL(inputs[i].y); + if(!ScreenToClient(hWnd, &Point)) { + continue; + } + + context->pointer.point.x = lv_windows_zoom_to_logical( + Point.x, + context->zoom_level); + context->pointer.point.y = lv_windows_zoom_to_logical( + Point.y, + context->zoom_level); + if(context->simulator_mode) { + context->pointer.point.x = lv_windows_dpi_to_logical( + context->pointer.point.x, + context->window_dpi); + context->pointer.point.y = lv_windows_dpi_to_logical( + context->pointer.point.y, + context->window_dpi); + } + + DWORD MousePressedMask = + TOUCHEVENTF_MOVE | TOUCHEVENTF_DOWN; + + context->pointer.state = ( + inputs[i].dwFlags & MousePressedMask + ? LV_INDEV_STATE_PRESSED + : LV_INDEV_STATE_RELEASED); + } + } + + free(inputs); + } + + lv_windows_close_touch_input_handle(touch_input_handle); + } + + break; + } + default: + // Not Handled + return false; + } + + // Handled + *plResult = 0; + return true; +} + +static void lv_windows_keypad_driver_read_callback( + lv_indev_t * indev, + lv_indev_data_t * data) +{ + lv_windows_window_context_t * context = lv_windows_get_window_context( + lv_windows_get_indev_window_handle(indev)); + if(!context) { + return; + } + + EnterCriticalSection(&context->keypad.mutex); + + lv_windows_keypad_queue_item_t * current = (lv_windows_keypad_queue_item_t *)( + _lv_ll_get_head(&context->keypad.queue)); + if(current) { + data->key = current->key; + data->state = current->state; + + _lv_ll_remove(&context->keypad.queue, current); + lv_free(current); + + data->continue_reading = true; + } + + LeaveCriticalSection(&context->keypad.mutex); +} + +static void lv_windows_release_keypad_device_event_callback(lv_event_t * e) +{ + lv_indev_t * indev = (lv_indev_t *)lv_event_get_user_data(e); + if(!indev) { + return; + } + + HWND window_handle = lv_windows_get_indev_window_handle(indev); + if(!window_handle) { + return; + } + + lv_windows_window_context_t * context = lv_windows_get_window_context( + window_handle); + if(!context) { + return; + } + + DeleteCriticalSection(&context->keypad.mutex); + _lv_ll_clear(&context->keypad.queue); + context->keypad.utf16_high_surrogate = 0; + context->keypad.utf16_low_surrogate = 0; + + context->keypad.indev = NULL; +} + +static void lv_windows_push_key_to_keyboard_queue( + lv_windows_window_context_t * context, + uint32_t key, + lv_indev_state_t state) +{ + lv_windows_keypad_queue_item_t * current = (lv_windows_keypad_queue_item_t *)( + _lv_ll_ins_tail(&context->keypad.queue)); + if(current) { + current->key = key; + current->state = state; + } +} + +bool lv_windows_keypad_device_window_message_handler( + HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam, + LRESULT * plResult) +{ + switch(uMsg) { + case WM_KEYDOWN: + case WM_KEYUP: { + lv_windows_window_context_t * context = (lv_windows_window_context_t *)( + lv_windows_get_window_context(hWnd)); + if(context) { + EnterCriticalSection(&context->keypad.mutex); + + bool skip_translation = false; + uint32_t translated_key = 0; + + switch(wParam) { + case VK_UP: + translated_key = LV_KEY_UP; + break; + case VK_DOWN: + translated_key = LV_KEY_DOWN; + break; + case VK_LEFT: + translated_key = LV_KEY_LEFT; + break; + case VK_RIGHT: + translated_key = LV_KEY_RIGHT; + break; + case VK_ESCAPE: + translated_key = LV_KEY_ESC; + break; + case VK_DELETE: + translated_key = LV_KEY_DEL; + break; + case VK_BACK: + translated_key = LV_KEY_BACKSPACE; + break; + case VK_RETURN: + translated_key = LV_KEY_ENTER; + break; + case VK_TAB: + case VK_NEXT: + translated_key = LV_KEY_NEXT; + break; + case VK_PRIOR: + translated_key = LV_KEY_PREV; + break; + case VK_HOME: + translated_key = LV_KEY_HOME; + break; + case VK_END: + translated_key = LV_KEY_END; + break; + default: + skip_translation = true; + break; + } + + if(!skip_translation) { + lv_windows_push_key_to_keyboard_queue( + context, + translated_key, + ((uMsg == WM_KEYUP) + ? LV_INDEV_STATE_RELEASED + : LV_INDEV_STATE_PRESSED)); + } + + LeaveCriticalSection(&context->keypad.mutex); + } + + break; + } + case WM_CHAR: { + lv_windows_window_context_t * context = (lv_windows_window_context_t *)( + lv_windows_get_window_context(hWnd)); + if(context) { + EnterCriticalSection(&context->keypad.mutex); + + uint16_t raw_code_point = (uint16_t)(wParam); + + if(raw_code_point >= 0x20 && raw_code_point != 0x7F) { + if(IS_HIGH_SURROGATE(raw_code_point)) { + context->keypad.utf16_high_surrogate = raw_code_point; + } + else if(IS_LOW_SURROGATE(raw_code_point)) { + context->keypad.utf16_low_surrogate = raw_code_point; + } + + uint32_t code_point = raw_code_point; + + if(context->keypad.utf16_high_surrogate && + context->keypad.utf16_low_surrogate) { + uint16_t high_surrogate = + context->keypad.utf16_high_surrogate; + uint16_t low_surrogate = + context->keypad.utf16_low_surrogate; + + code_point = (low_surrogate & 0x03FF); + code_point += (((high_surrogate & 0x03FF) + 0x40) << 10); + + context->keypad.utf16_high_surrogate = 0; + context->keypad.utf16_low_surrogate = 0; + } + + uint32_t lvgl_code_point = + _lv_text_unicode_to_encoded(code_point); + + lv_windows_push_key_to_keyboard_queue( + context, + lvgl_code_point, + LV_INDEV_STATE_PRESSED); + lv_windows_push_key_to_keyboard_queue( + context, + lvgl_code_point, + LV_INDEV_STATE_RELEASED); + } + + LeaveCriticalSection(&context->keypad.mutex); + } + + break; + } + case WM_IME_SETCONTEXT: { + if(wParam == TRUE) { + HIMC input_method_context_handle = ImmGetContext(hWnd); + if(input_method_context_handle) { + ImmAssociateContext(hWnd, input_method_context_handle); + ImmReleaseContext(hWnd, input_method_context_handle); + } + } + + *plResult = DefWindowProcW(hWnd, uMsg, wParam, wParam); + break; + } + case WM_IME_STARTCOMPOSITION: { + HIMC input_method_context_handle = ImmGetContext(hWnd); + if(input_method_context_handle) { + lv_obj_t * textarea_object = NULL; + lv_obj_t * focused_object = lv_group_get_focused( + lv_group_get_default()); + if(focused_object) { + const lv_obj_class_t * object_class = lv_obj_get_class( + focused_object); + + if(object_class == &lv_textarea_class) { + textarea_object = focused_object; + } + else if(object_class == &lv_keyboard_class) { + textarea_object = lv_keyboard_get_textarea(focused_object); + } + } + + COMPOSITIONFORM composition_form; + composition_form.dwStyle = CFS_POINT; + composition_form.ptCurrentPos.x = 0; + composition_form.ptCurrentPos.y = 0; + + if(textarea_object) { + lv_textarea_t * textarea = (lv_textarea_t *)(textarea_object); + lv_obj_t * label_object = lv_textarea_get_label(textarea_object); + + composition_form.ptCurrentPos.x = + label_object->coords.x1 + textarea->cursor.area.x1; + composition_form.ptCurrentPos.y = + label_object->coords.y1 + textarea->cursor.area.y1; + } + + ImmSetCompositionWindow( + input_method_context_handle, + &composition_form); + ImmReleaseContext( + hWnd, + input_method_context_handle); + } + + *plResult = DefWindowProcW(hWnd, uMsg, wParam, wParam); + break; + } + default: + // Not Handled + return false; + } + + // Handled + *plResult = 0; + return true; +} + +static void lv_windows_encoder_driver_read_callback( + lv_indev_t * indev, + lv_indev_data_t * data) +{ + lv_windows_window_context_t * context = lv_windows_get_window_context( + lv_windows_get_indev_window_handle(indev)); + if(!context) { + return; + } + + data->state = context->encoder.state; + data->enc_diff = context->encoder.enc_diff; + context->encoder.enc_diff = 0; +} + +static void lv_windows_release_encoder_device_event_callback(lv_event_t * e) +{ + lv_indev_t * indev = (lv_indev_t *)lv_event_get_user_data(e); + if(!indev) { + return; + } + + HWND window_handle = lv_windows_get_indev_window_handle(indev); + if(!window_handle) { + return; + } + + lv_windows_window_context_t * context = lv_windows_get_window_context( + window_handle); + if(!context) { + return; + } + + context->encoder.state = LV_INDEV_STATE_RELEASED; + context->encoder.enc_diff = 0; + + context->encoder.indev = NULL; +} + +bool lv_windows_encoder_device_window_message_handler( + HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam, + LRESULT * plResult) +{ + switch(uMsg) { + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: { + lv_windows_window_context_t * context = (lv_windows_window_context_t *)( + lv_windows_get_window_context(hWnd)); + if(context) { + context->encoder.state = ( + uMsg == WM_MBUTTONDOWN + ? LV_INDEV_STATE_PRESSED + : LV_INDEV_STATE_RELEASED); + } + + break; + } + case WM_MOUSEWHEEL: { + lv_windows_window_context_t * context = (lv_windows_window_context_t *)( + lv_windows_get_window_context(hWnd)); + if(context) { + context->encoder.enc_diff = + -(GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA); + } + + break; + } + default: + // Not Handled + return false; + } + + // Handled + *plResult = 0; + return true; +} + +#endif // LV_USE_WINDOWS diff --git a/src/dev/windows/lv_windows_input.h b/src/dev/windows/lv_windows_input.h new file mode 100644 index 000000000..892014ea3 --- /dev/null +++ b/src/dev/windows/lv_windows_input.h @@ -0,0 +1,83 @@ +/** + * @file lv_windows_input.h + * + */ + +#ifndef LV_WINDOWS_INPUT_H +#define LV_WINDOWS_INPUT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "../../display/lv_display.h" +#include "../../indev/lv_indev.h" + +#if LV_USE_WINDOWS + +#include + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * @brief Get the window handle from specific LVGL input device object. + * @param indev The specific LVGL input device object. + * @return The window handle from specific LVGL input device object. +*/ +HWND lv_windows_get_indev_window_handle(lv_indev_t * indev); + +/** + * @brief Open a LVGL pointer input device object for the specific LVGL + * display object, or create it if the LVGL pointer input device + * object is not created or removed before. + * @param display The specific LVGL display object. + * @return The LVGL pointer input device object for the specific LVGL + * display object. +*/ +lv_indev_t * lv_windows_acquire_pointer_indev(lv_display_t * display); + +/** + * @brief Open a LVGL keypad input device object for the specific LVGL + * display object, or create it if the LVGL keypad input device + * object is not created or removed before. + * @param display The specific LVGL display object. + * @return The LVGL keypad input device object for the specific LVGL + * display object. +*/ +lv_indev_t * lv_windows_acquire_keypad_indev(lv_display_t * display); + +/** + * @brief Open a LVGL encoder input device object for the specific LVGL + * display object, or create it if the LVGL encoder input device + * object is not created or removed before. + * @param display The specific LVGL display object. + * @return The LVGL encoder input device object for the specific LVGL + * display object. +*/ +lv_indev_t * lv_windows_acquire_encoder_indev(lv_display_t * display); + +/********************** + * MACROS + **********************/ + +#endif // LV_USE_WINDOWS + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_WINDOWS_INPUT_H*/ diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h index 3d3df001b..c470db640 100644 --- a/src/lv_conf_internal.h +++ b/src/lv_conf_internal.h @@ -2937,6 +2937,14 @@ #endif #endif +/* LVGL Windows backend */ +#ifndef LV_USE_WINDOWS + #ifdef CONFIG_LV_USE_WINDOWS + #define LV_USE_WINDOWS CONFIG_LV_USE_WINDOWS + #else + #define LV_USE_WINDOWS 0 + #endif +#endif /*================== * EXAMPLES diff --git a/src/lv_init.c b/src/lv_init.c index f906b1fdc..613547829 100644 --- a/src/lv_init.c +++ b/src/lv_init.c @@ -39,6 +39,9 @@ #if LV_USE_DRAW_VG_LITE #include "draw/vg_lite/lv_draw_vg_lite.h" #endif +#if LV_USE_WINDOWS + #include "src/dev/windows/lv_windows_context.h" +#endif /********************* * DEFINES @@ -185,6 +188,10 @@ void lv_init(void) lv_draw_sdl_init(); #endif +#if LV_USE_WINDOWS + lv_windows_platform_init(); +#endif + _lv_obj_style_init(); /*Initialize the screen refresh system*/