|
| 1 | +# Develop Native GUI Application |
| 2 | + |
| 3 | +Develop native GUI application for webOS. |
| 4 | + |
| 5 | +Native system apps in webOS Uses Qt, but Qt version varies between system versions. |
| 6 | + |
| 7 | +For maximum compatibility, this tutorial shows how to build GUI application with SDL2. |
| 8 | + |
| 9 | +This project is written in C11, and it can run through webOS 1 to webOS 6 without any issue. |
| 10 | + |
| 11 | +## Config CMake Project |
| 12 | + |
| 13 | +Start with project declaration. |
| 14 | + |
| 15 | +```cmake |
| 16 | +cmake_minimum_required (VERSION 3.8) |
| 17 | +
|
| 18 | +# This is a C/C++ project |
| 19 | +project ("myapp" VERSION 1.0 LANGUAGES C CXX) |
| 20 | +
|
| 21 | +# Use `pkg-config` to link needed libraries. |
| 22 | +find_package(PkgConfig REQUIRED) |
| 23 | +
|
| 24 | +# Use SDL2 for window creation and event handling. |
| 25 | +pkg_check_modules(SDL2 REQUIRED sdl2) |
| 26 | +
|
| 27 | +# Add source to this project's executable. |
| 28 | +add_executable (myapp "src/myapp.c") |
| 29 | +
|
| 30 | +# Link SDL2 |
| 31 | +target_include_directories("myapp" SYSTEM PRIVATE ${SDL2_INCLUDE_DIRS}) |
| 32 | +target_link_libraries("myapp" PRIVATE ${SDL2_LIBRARIES}) |
| 33 | +``` |
| 34 | + |
| 35 | +## Create an empty app with SDL2 and LVGL |
| 36 | + |
| 37 | +Add `lv_conf.h`. `#defines` below are important to have, for other settings you can set based on your use case. |
| 38 | + |
| 39 | +```c |
| 40 | +/* Use 32-bit color depth */ |
| 41 | +#define LV_COLOR_DEPTH 32 |
| 42 | + |
| 43 | +/* Use stdlib to allocate/free memory */ |
| 44 | +#define LV_MEM_CUSTOM 1 |
| 45 | +#define LV_MEM_CUSTOM_INCLUDE <string.h> /*Header for the dynamic memory function*/ |
| 46 | +#define LV_MEM_CUSTOM_ALLOC malloc |
| 47 | +#define LV_MEM_CUSTOM_FREE free |
| 48 | +#define LV_MEM_CUSTOM_REALLOC realloc |
| 49 | + |
| 50 | +/* Use the standard `memcpy` and `memset` instead of LVGL's own functions. */ |
| 51 | +#define LV_MEMCPY_MEMSET_STD 1 |
| 52 | + |
| 53 | +/* Let LVGL call SDL_GetTicks automatically so we can skip creating a separate timer thread. */ |
| 54 | +#define LV_TICK_CUSTOM 1 |
| 55 | +#define LV_TICK_CUSTOM_INCLUDE <SDL.h> |
| 56 | +#define LV_TICK_CUSTOM_SYS_TIME_EXPR (SDL_GetTicks()) |
| 57 | + |
| 58 | +#define LV_DRAW_COMPLEX 1 |
| 59 | +#define LV_SHADOW_CACHE_SIZE 0 |
| 60 | +#define LV_IMG_CACHE_DEF_SIZE 0 |
| 61 | + |
| 62 | +/* Use SDL draw backend */ |
| 63 | +#define LV_USE_GPU_SDL 1 |
| 64 | +#define LV_GPU_SDL_INCLUDE_PATH <SDL.h> |
| 65 | +``` |
| 66 | +
|
| 67 | +Edit `src/myapp.c`. |
| 68 | +
|
| 69 | +```c |
| 70 | +static void process_events(); |
| 71 | +
|
| 72 | +static lv_disp_t *display_init(SDL_Window *window); |
| 73 | +
|
| 74 | +static void flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *src); |
| 75 | +
|
| 76 | +static bool running = true; |
| 77 | +
|
| 78 | +int main(int argc, char *argv[]) { |
| 79 | + SDL_Init(SDL_INIT_VIDEO); |
| 80 | + lv_init(); |
| 81 | +
|
| 82 | + int w = 800, h = 480; |
| 83 | + SDL_DisplayMode mode; |
| 84 | + SDL_GetDisplayMode(0, 0, &mode); |
| 85 | + /* Get display size. Fallback to 1920x1080 if failed. */ |
| 86 | + if (mode.w > 0 && mode.h > 0) { |
| 87 | + w = mode.w; |
| 88 | + h = mode.h; |
| 89 | + } |
| 90 | + /* Caveat: Don't use SDL_WINDOW_FULLSCREEN_DESKTOP on webOS. On older platforms it's not supported. */ |
| 91 | + SDL_Window *window = SDL_CreateWindow("myapp", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, |
| 92 | + SDL_WINDOW_FULLSCREEN | SDL_WINDOW_ALLOW_HIGHDPI); |
| 93 | + lv_disp_t *disp = display_init(window); |
| 94 | + lv_disp_set_default(disp); |
| 95 | +
|
| 96 | + lv_obj_t *label = lv_label_create(lv_scr_act()); |
| 97 | + lv_obj_set_pos(label, LV_DPX(10), LV_DPX(10)); |
| 98 | + lv_label_set_text(label, "Hello, world!"); |
| 99 | +
|
| 100 | + while (running) { |
| 101 | + process_events(); |
| 102 | + lv_task_handler(); |
| 103 | + SDL_Delay(1); |
| 104 | + } |
| 105 | + SDL_DestroyWindow(window); |
| 106 | + SDL_Quit(); |
| 107 | + return 0; |
| 108 | +} |
| 109 | +``` |
| 110 | + |
| 111 | +Implement functions declared above |
| 112 | + |
| 113 | +```c |
| 114 | + |
| 115 | +static void process_events() { |
| 116 | + SDL_Event event; |
| 117 | + while (SDL_PollEvent(&event)) { |
| 118 | + switch (event.type) { |
| 119 | + case SDL_QUIT: |
| 120 | + running = false; |
| 121 | + break; |
| 122 | + default: |
| 123 | + break; |
| 124 | + } |
| 125 | + } |
| 126 | +} |
| 127 | + |
| 128 | +static lv_disp_t *display_init(SDL_Window *window) { |
| 129 | + int width, height; |
| 130 | + SDL_GetWindowSize(window, &width, &height); |
| 131 | + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); |
| 132 | + SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); |
| 133 | + lv_disp_draw_buf_t *draw_buf = malloc(sizeof(lv_disp_draw_buf_t)); |
| 134 | + SDL_Texture *texture = lv_draw_sdl_create_screen_texture(renderer, width, height); |
| 135 | + lv_disp_draw_buf_init(draw_buf, texture, NULL, width * height); |
| 136 | + lv_disp_drv_t *driver = malloc(sizeof(lv_disp_drv_t)); |
| 137 | + lv_disp_drv_init(driver); |
| 138 | + |
| 139 | + lv_draw_sdl_drv_param_t *param = lv_mem_alloc(sizeof(lv_draw_sdl_drv_param_t)); |
| 140 | + param->renderer = renderer; |
| 141 | + driver->user_data = param; |
| 142 | + driver->draw_buf = draw_buf; |
| 143 | + driver->flush_cb = flush_cb; |
| 144 | + driver->hor_res = width; |
| 145 | + driver->ver_res = height; |
| 146 | + SDL_SetRenderTarget(renderer, texture); |
| 147 | + lv_disp_t *disp = lv_disp_drv_register(driver); |
| 148 | + return disp; |
| 149 | +} |
| 150 | + |
| 151 | +static void flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *src) { |
| 152 | + LV_UNUSED(src); |
| 153 | + if (area->x2 < 0 || area->y2 < 0 || |
| 154 | + area->x1 > disp_drv->hor_res - 1 || area->y1 > disp_drv->ver_res - 1) { |
| 155 | + lv_disp_flush_ready(disp_drv); |
| 156 | + return; |
| 157 | + } |
| 158 | + |
| 159 | + if (lv_disp_flush_is_last(disp_drv)) { |
| 160 | + lv_draw_sdl_drv_param_t *param = disp_drv->user_data; |
| 161 | + SDL_Renderer *renderer = param->renderer; |
| 162 | + SDL_Texture *texture = disp_drv->draw_buf->buf1; |
| 163 | + SDL_SetRenderTarget(renderer, NULL); |
| 164 | + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); |
| 165 | + SDL_RenderClear(renderer); |
| 166 | + SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); |
| 167 | + SDL_RenderCopy(renderer, texture, NULL, NULL); |
| 168 | + SDL_RenderPresent(renderer); |
| 169 | + SDL_SetRenderTarget(renderer, texture); |
| 170 | + } |
| 171 | + lv_disp_flush_ready(disp_drv); |
| 172 | +} |
| 173 | +``` |
| 174 | +
|
| 175 | +Now if you build the project, you should be able to get a binary. If you build the project with system toolchain, you'll |
| 176 | +be able to run it, and see "Hello, world!" on top left corner. |
| 177 | +
|
| 178 | +Next, we configure CMake to generate IPK for installing on TV. |
| 179 | +
|
| 180 | +## Create Installation Package |
| 181 | +
|
| 182 | +### Create `appinfo.json` |
| 183 | +
|
| 184 | +```json |
| 185 | +{ |
| 186 | + "id": "com.mycompany.myapp", |
| 187 | + "type": "native", |
| 188 | + "main": "myapp", |
| 189 | + "icon": "icon.png", |
| 190 | + "title": "Native App", |
| 191 | + "version": "0.0.1" |
| 192 | +} |
| 193 | +``` |
| 194 | + |
| 195 | +### Config CPack |
| 196 | + |
| 197 | +Append to `CMakeLists.txt`. |
| 198 | + |
| 199 | +```cmake |
| 200 | +install(TARGETS myapp RUNTIME DESTINATION .) |
| 201 | +install(FILES appinfo.json icon.png DESTINATION .) |
| 202 | +
|
| 203 | +set(CPACK_GENERATOR "External") |
| 204 | +set(CPACK_EXTERNAL_PACKAGE_SCRIPT "${CMAKE_SOURCE_DIR}/AresPackage.cmake") |
| 205 | +set(CPACK_EXTERNAL_ENABLE_STAGING TRUE) |
| 206 | +set(CPACK_MONOLITHIC_INSTALL TRUE) |
| 207 | +
|
| 208 | +include(CPack) |
| 209 | +``` |
| 210 | + |
| 211 | +Create `AresPackage.cmake` |
| 212 | + |
| 213 | +```cmake |
| 214 | +execute_process(COMMAND ares-package "${CPACK_TEMPORARY_DIRECTORY}") |
| 215 | +``` |
| 216 | + |
| 217 | +### Input Handling |
| 218 | + |
| 219 | +#### Magic Remote (Mouse) |
| 220 | + |
| 221 | +#### Remote Keys (Keyboard) |
| 222 | + |
| 223 | +#### Bonus: Gamepad |
| 224 | + |
| 225 | +### Shipping with Dependencies |
| 226 | + |
| 227 | +## Tips |
| 228 | + |
| 229 | +### Remote GDB Debugging |
| 230 | + |
| 231 | +### Cross-Platform Program for Easier Debugging |
| 232 | + |
| 233 | +## Compatibility |
| 234 | + |
| 235 | +### SDL Version Support Chart |
0 commit comments