lvgl / lv_drivers

TFT and touch pad drivers for LVGL embedded GUI library
https://docs.lvgl.io/master/porting/index.html
MIT License
306 stars 314 forks source link

Add the multiple display support for win32drv. #259

Closed MouriNaruto closed 1 year ago

MouriNaruto commented 1 year ago

image

Here is the screenshot for 16 displays with different LVGL demos.

@kisvegabor @wrgallo

Fix #255 and #256.

The implementation have almost been rewritten and provide some new APIs. But it still keeps the compatibility with the old usage.

Here is the snippet of the new usage.

#include <Windows.h>

#include "resource.h"

#if _MSC_VER >= 1200
 // Disable compilation warnings.
#pragma warning(push)
// nonstandard extension used : bit field types other than int
#pragma warning(disable:4214)
// 'conversion' conversion from 'type1' to 'type2', possible loss of data
#pragma warning(disable:4244)
#endif

#include "lvgl/lvgl.h"
#include "lvgl/examples/lv_examples.h"
#include "lvgl/demos/lv_demos.h"
#include "lv_drivers/win32drv/win32drv.h"

#if _MSC_VER >= 1200
// Restore compilation warnings.
#pragma warning(pop)
#endif

#include <stdio.h>
#include <process.h>

HANDLE g_window_mutex = NULL;
bool g_initialization_status = false;

#define LVGL_SIMULATOR_MAXIMUM_DISPLAYS 16
static HWND g_display_window_handles[LVGL_SIMULATOR_MAXIMUM_DISPLAYS];

unsigned int __stdcall lv_win32_window_thread_entrypoint(
    void* raw_parameter)
{
    size_t display_id = *(size_t*)(raw_parameter);

    HINSTANCE instance_handle = GetModuleHandleW(NULL);
    HICON icon_handle = LoadIconW(GetModuleHandleW(NULL), MAKEINTRESOURCE(IDI_LVGL));
    lv_coord_t hor_res = 800;
    lv_coord_t ver_res = 450;

    wchar_t WindowTitle[256];
    memset(WindowTitle, 0, sizeof(WindowTitle));
    _snwprintf(
        WindowTitle,
        256,
        L"LVGL Simulator for Windows Desktop (Display %d)",
        display_id);

    g_display_window_handles[display_id] = lv_win32_create_display_window(
        window_title,
        hor_res,
        ver_res,
        instance_handle,
        icon_handle,
        show_window_mode);
    if (!g_display_window_handles[display_id])
    {
        return 0;
    }

    g_initialization_status = true;

    SetEvent(g_window_mutex);

    MSG message;
    while (GetMessageW(&message, NULL, 0, 0))
    {
        TranslateMessage(&message);
        DispatchMessageW(&message);
    }

    lv_win32_quit_signal = true;

    return 0;
}

bool dual_display_mode_initialization()
{
    if (!lv_win32_init_window_class())
    {
        return false;
    }

    for (size_t i = 0; i < LVGL_SIMULATOR_MAXIMUM_DISPLAYS; ++i)
    {
        g_initialization_status = false;

        g_window_mutex = CreateEventExW(NULL, NULL, 0, EVENT_ALL_ACCESS);

        _beginthreadex(
            NULL,
            0,
            lv_win32_window_thread_entrypoint,
            &i,
            0,
            NULL);

        WaitForSingleObjectEx(g_window_mutex, INFINITE, FALSE);

        CloseHandle(g_window_mutex);

        if (!g_initialization_status)
        {
            return false;
        }
    }

    lv_win32_window_context_t* context = (lv_win32_window_context_t*)(
        lv_win32_get_window_context(g_display_window_handles[0]));
    if (context)
    {
        lv_win32_pointer_device_object = context->mouse_device_object;
        lv_win32_keypad_device_object = context->keyboard_device_object;
        lv_win32_encoder_device_object = context->mousewheel_device_object;
    }

    lv_win32_add_all_input_devices_to_group(NULL);

    return true;
}

int main()
{
    lv_init();

    if (!dual_display_mode_initialization())
    {
        return -1;
    }
    else
    {
        for (size_t i = 0; i < LVGL_SIMULATOR_MAXIMUM_DISPLAYS; ++i)
        {
            lv_win32_window_context_t* context = (lv_win32_window_context_t*)(
                lv_win32_get_window_context(g_display_window_handles[i]));
            if (context)
            {
                lv_disp_set_default(context->display_device_object);
                switch (i)
                {
                case 0:
                    lv_demo_widgets();
                    break;
                case 1:
                    lv_demo_benchmark();
                    break;
                case 2:
                    lv_example_style_1();
                    break;
                case 3:
                    lv_example_get_started_1();
                    break;
                case 4:
                    lv_example_anim_1();
                    break;
                case 5:
                    lv_example_style_2();
                    break;
                case 6:
                    lv_example_get_started_2();
                    break;
                case 7:
                    lv_example_anim_2();
                    break;
                case 8:
                    lv_example_style_3();
                    break;
                case 9:
                    lv_example_get_started_3();
                    break;
                case 10:
                    lv_example_anim_3();
                    break;
                case 11:
                    lv_example_style_4();
                    break;
                case 12:
                    lv_example_style_5();
                    break;
                case 13:
                    lv_example_style_6();
                    break;
                case 14:
                    lv_example_imgfont_1();
                    break;
                case 15:
                    lv_example_style_7();
                    break;
                default:
                    break;
                }
            }
        }
    }

    while (!lv_win32_quit_signal)
    {
        lv_task_handler();
        Sleep(1);
    }

    return 0;
}

Kenji Mouri

kisvegabor commented 1 year ago

Very nice! In the arch/drivers branch it works like this:

  lv_disp_t * disp = lv_sdl_window_create(w, h);

  lv_indev_t * mouse = lv_sdl_mouse_create();
  lv_indev_set_disp(mouse, disp);

  lv_indev_t * mousewheel = lv_sdl_mousewheel_create();
  lv_indev_set_disp(mousewheel, disp);

  lv_indev_t * keyboard = lv_sdl_keyboard_create();
  lv_indev_set_disp(keyboard, disp);

I think it fits well into your current API.

Let me now if I can merge it.

MouriNaruto commented 1 year ago

@kisvegabor

I think it's OK to merge with the new driver model to the arch/drivers branch. If you don't mind waiting for one or two weeks, I want to contribute the implementation with the new driver model to the arch/drivers branch directly because I think my implementation can be optimized with the new driver model, or you can merge them at first and I commit for some improvements via the further PRs if needed.

Note1: I have read the implementation of the new driver model and I think I can try something about unifying the implementations from windows simulator and windows port.

Note2: I don't know how to test with the new driver model.

I also hope this PR can be merged because it can enable current users using the multiple display support.

Kenji Mouri

MouriNaruto commented 1 year ago

@kisvegabor

If you want to merge this driver to new driver model by yourself directly. I hope the driver name should be win32.

Kenji Mouri

wrgallo-navitas commented 1 year ago

image

Here is the screenshot for 16 displays with different LVGL demos.

@kisvegabor @wrgallo

Fix #255 and #256.

The implementation have almost been rewritten and provide some new APIs. But it still keeps the compatibility with the old usage.

Here is the snippet of the new usage.

#include <Windows.h>

#include "resource.h"

#if _MSC_VER >= 1200
 // Disable compilation warnings.
#pragma warning(push)
// nonstandard extension used : bit field types other than int
#pragma warning(disable:4214)
// 'conversion' conversion from 'type1' to 'type2', possible loss of data
#pragma warning(disable:4244)
#endif

#include "lvgl/lvgl.h"
#include "lvgl/examples/lv_examples.h"
#include "lvgl/demos/lv_demos.h"
#include "lv_drivers/win32drv/win32drv.h"

#if _MSC_VER >= 1200
// Restore compilation warnings.
#pragma warning(pop)
#endif

#include <stdio.h>
#include <process.h>

HANDLE g_window_mutex = NULL;
bool g_initialization_status = false;

#define LVGL_SIMULATOR_MAXIMUM_DISPLAYS 16
static HWND g_display_window_handles[LVGL_SIMULATOR_MAXIMUM_DISPLAYS];

unsigned int __stdcall lv_win32_window_thread_entrypoint(
    void* raw_parameter)
{
    size_t display_id = *(size_t*)(raw_parameter);

    HINSTANCE instance_handle = GetModuleHandleW(NULL);
    HICON icon_handle = LoadIconW(GetModuleHandleW(NULL), MAKEINTRESOURCE(IDI_LVGL));
    lv_coord_t hor_res = 800;
    lv_coord_t ver_res = 450;

    wchar_t WindowTitle[256];
    memset(WindowTitle, 0, sizeof(WindowTitle));
    _snwprintf(
        WindowTitle,
        256,
        L"LVGL Simulator for Windows Desktop (Display %d)",
        display_id);

    g_display_window_handles[display_id] = CreateWindowExW(
        WS_EX_CLIENTEDGE,
        LVGL_SIMULATOR_WINDOW_CLASS,
        WindowTitle,
        WS_OVERLAPPEDWINDOW & ~(WS_SIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME),
        CW_USEDEFAULT,
        0,
        hor_res,
        ver_res,
        NULL,
        NULL,
        instance_handle,
        NULL);
    if (!g_display_window_handles[display_id])
    {
        return 0;
    }

    SendMessageW(
        g_display_window_handles[display_id],
        WM_SETICON,
        TRUE,
        (LPARAM)icon_handle);
    SendMessageW(
        g_display_window_handles[display_id],
        WM_SETICON,
        FALSE,
        (LPARAM)icon_handle);

    ShowWindow(g_display_window_handles[display_id], SW_SHOW);
    UpdateWindow(g_display_window_handles[display_id]);

    g_initialization_status = true;

    SetEvent(g_window_mutex);

    MSG message;
    while (GetMessageW(&message, NULL, 0, 0))
    {
        TranslateMessage(&message);
        DispatchMessageW(&message);
    }

    lv_win32_quit_signal = true;

    return 0;
}

bool dual_display_mode_initialization()
{
    if (!lv_win32_init_window_class())
    {
        return false;
    }

    for (size_t i = 0; i < LVGL_SIMULATOR_MAXIMUM_DISPLAYS; ++i)
    {
        g_initialization_status = false;

        g_window_mutex = CreateEventExW(NULL, NULL, 0, EVENT_ALL_ACCESS);

        _beginthreadex(
            NULL,
            0,
            lv_win32_window_thread_entrypoint,
            &i,
            0,
            NULL);

        WaitForSingleObjectEx(g_window_mutex, INFINITE, FALSE);

        CloseHandle(g_window_mutex);

        if (!g_initialization_status)
        {
            return false;
        }
    }

    lv_win32_window_context_t* context = (lv_win32_window_context_t*)(
        lv_win32_get_window_context(g_display_window_handles[0]));
    if (context)
    {
        lv_win32_pointer_device_object = context->mouse_device_object;
        lv_win32_keypad_device_object = context->keyboard_device_object;
        lv_win32_encoder_device_object = context->mousewheel_device_object;
    }

    lv_win32_add_all_input_devices_to_group(NULL);

    return true;
}

int main()
{
    lv_init();

    if (!dual_display_mode_initialization())
    {
        return -1;
    }
    else
    {
        for (size_t i = 0; i < LVGL_SIMULATOR_MAXIMUM_DISPLAYS; ++i)
        {
            lv_win32_window_context_t* context = (lv_win32_window_context_t*)(
                lv_win32_get_window_context(g_display_window_handles[i]));
            if (context)
            {
                lv_disp_set_default(context->display_device_object);
                switch (i)
                {
                case 0:
                    lv_demo_widgets();
                    break;
                case 1:
                    lv_demo_benchmark();
                    break;
                case 2:
                    lv_example_style_1();
                    break;
                case 3:
                    lv_example_get_started_1();
                    break;
                case 4:
                    lv_example_anim_1();
                    break;
                case 5:
                    lv_example_style_2();
                    break;
                case 6:
                    lv_example_get_started_2();
                    break;
                case 7:
                    lv_example_anim_2();
                    break;
                case 8:
                    lv_example_style_3();
                    break;
                case 9:
                    lv_example_get_started_3();
                    break;
                case 10:
                    lv_example_anim_3();
                    break;
                case 11:
                    lv_example_style_4();
                    break;
                case 12:
                    lv_example_style_5();
                    break;
                case 13:
                    lv_example_style_6();
                    break;
                case 14:
                    lv_example_imgfont_1();
                    break;
                case 15:
                    lv_example_style_7();
                    break;
                default:
                    break;
                }
            }
        }
    }

    while (!lv_win32_quit_signal)
    {
        lv_task_handler();
        Sleep(1);
    }

    return 0;
}

Kenji Mouri

It would be necessary to create other lv_win32_window_thread_entrypoint implementation on the main to be able to use multiple display and change the window title?

I know my suggested implementation had a lot of room for improvement, but the option to use a simple function to create and setup the display window would be really useful, if the older function would be kept for compatibility we could create a new one.

/**
 * @brief Create a display on WIN32
 * @param[in] hor_res Horizontal Resolution
 * @param[in] ver_res Vertical Resolution
 * @param[in] screen_title Title of the windows screen or NULL to use default
 * @param[in] icon_handle Icon for the windows screen
 * @return pointer to the new display or NULL if failed
*/
EXTERN_C lv_disp_t* lv_win32_create_disp(
    lv_coord_t hor_res,
    lv_coord_t ver_res,
    const wchar_t* screen_title,
    HICON icon_handle );
MouriNaruto commented 1 year ago

@wrgallo-navitas

It would be necessary to create other lv_win32_window_thread_entrypoint implementation on the main to be able to use multiple display and change the window title?

Of course, it's not necessary. But I use that example because I want to show flexibility. You can make all windows and LVGL scheduler in the main thread. I use multithread for making offload tasks from the main thread to improve the performance.

I know my https://github.com/lvgl/lv_drivers/pull/257 had a lot of room for improvement, but the option to use a simple function to create and setup the display window would be really useful, if the older function would be kept for compatibility we could create a new one.

I will follow your suggestion to provide a simple function to create a display instance.

Kenji Mouri

MouriNaruto commented 1 year ago

@wrgallo-navitas

I have add a new API to simplify them.

@kisvegabor

Ping for progress.

Kenji Mouri

wrgallo-navitas commented 1 year ago

Great work, just a question:

Why not always use GetModuleHandleW(NULL) for instance_handle and SW_SHOW for show_window_mode?

EXTERN_C HWND lv_win32_create_display_window(
    const wchar_t* window_title,
    lv_coord_t hor_res,
    lv_coord_t ver_res,
    HINSTANCE instance_handle, // Always use GetModuleHandleW(NULL) ?
    HICON icon_handle,
    int show_window_mode // why not fix with SW_SHOW ?
);

@wrgallo-navitas

I have add a new API to simplify them.

@kisvegabor

Ping for progress.

Kenji Mouri

MouriNaruto commented 1 year ago

@wrgallo-navitas

I have considered more. (For example, somebody doesn't want to show some simulator display windows for doing something headless so providing show_window_mode is useful. Or they want to use win32drv instance in a dll so providing instance_handle is useful for them to manage the resources.)

Kenji Mouri

kisvegabor commented 1 year ago

Sorry, somehow I missed your previous comment. I merge it now.

We are updating the MicroPython binding for arch/drivers. So I hope it will be merged soon too. Until that feel free to send PRs to arch/drivers. It'd be great to see how it work with various platforms and concepts.

wrgallo commented 1 year ago

@MouriNaruto I have just tested the commit and had troubles making it work.

This works:

int main()
{
    lv_init();

    HICON icoLvgl = LoadIconW(GetModuleHandleW(NULL), MAKEINTRESOURCE(IDI_LVGL));
    lv_win32_init(GetModuleHandleW(NULL), SW_SHOW, 800, 480, icoLvgl);

    lv_demo_widgets();
    while (!lv_win32_quit_signal) {
        lv_task_handler();
        Sleep(1);
    }

    return 0;
}

But this does not:

int main()
{
    lv_init();

    HICON icoLvgl = LoadIconW(GetModuleHandleW(NULL), MAKEINTRESOURCE(IDI_LVGL));
    // lv_win32_init_window_class(); // Removing this comment, still does not make it work!
    lv_win32_create_display_window(L"My title", 800, 480, GetModuleHandleW(NULL), icoLvgl, SW_SHOW);

    lv_demo_widgets();
    while (!lv_win32_quit_signal) {
        lv_task_handler();
        Sleep(1);
    }

    return 0;
}

Since it is not intuitive, could you please guide me on how to use lv_win32_create_display_window?