| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* GATE PROJECT LICENSE: | ||
| 2 | +----------------------------------------------------------------------------+ | ||
| 3 | | Copyright(c) 2018-2025, Stefan Meislinger <sm@opengate.at> | | ||
| 4 | | All rights reserved. | | ||
| 5 | | | | ||
| 6 | | Redistribution and use in source and binary forms, with or without | | ||
| 7 | | modification, are permitted provided that the following conditions are met:| | ||
| 8 | | | | ||
| 9 | | 1. Redistributions of source code must retain the above copyright notice, | | ||
| 10 | | this list of conditions and the following disclaimer. | | ||
| 11 | | 2. Redistributions in binary form must reproduce the above copyright | | ||
| 12 | | notice, this list of conditions and the following disclaimer in the | | ||
| 13 | | documentation and/or other materials provided with the distribution. | | ||
| 14 | | | | ||
| 15 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"| | ||
| 16 | | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | | ||
| 17 | | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | | ||
| 18 | | ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | | ||
| 19 | | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | | ||
| 20 | | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | | ||
| 21 | | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | | ||
| 22 | | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | | ||
| 23 | | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | | ||
| 24 | | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF | | ||
| 25 | | THE POSSIBILITY OF SUCH DAMAGE. | | ||
| 26 | +----------------------------------------------------------------------------+ | ||
| 27 | */ | ||
| 28 | #include "gate/io/videosources.h" | ||
| 29 | #include "gate/platforms.h" | ||
| 30 | #include "gate/results.h" | ||
| 31 | |||
| 32 | #if defined(GATE_SYS_WIN) | ||
| 33 | # if defined(GATE_SYS_WINSTORE) || defined(GATE_SYS_WIN16) | ||
| 34 | # define GATE_IO_VIDEO_NO_IMPL | ||
| 35 | # else | ||
| 36 | # define GATE_IO_VIDEO_WINAPI | ||
| 37 | # define GATE_IO_VIDEO_SCREEN_INTERFACE | ||
| 38 | # endif | ||
| 39 | #elif defined(GATE_SYS_ANDROID) | ||
| 40 | # define GATE_IO_VIDEO_ANDROID | ||
| 41 | #elif defined(GATE_SYS_LINUX) | ||
| 42 | # define GATE_IO_VIDEO_X11 | ||
| 43 | # define GATE_IO_VIDEO_SCREEN_INTERFACE | ||
| 44 | #else | ||
| 45 | # define GATE_IO_VIDEO_NO_IMPL | ||
| 46 | #endif | ||
| 47 | |||
| 48 | |||
| 49 | |||
| 50 | #if defined(GATE_IO_VIDEO_SCREEN_INTERFACE) | ||
| 51 | |||
| 52 | static char const* screenctrl_get_interface_name(void* thisptr); | ||
| 53 | static void screenctrl_release(void* thisptr); | ||
| 54 | static int screenctrl_retain(void* thisptr); | ||
| 55 | |||
| 56 | static char const* screenctrl_get_id(void* thisptr); | ||
| 57 | static char const* screenctrl_get_name(void* thisptr); | ||
| 58 | static gate_intptr_t screenctrl_get_handle(void* thisptr); | ||
| 59 | static gate_size_t screenctrl_get_supported_formats(void* thisptr, gate_video_format_t* format_buffer, gate_size_t format_buffer_count); | ||
| 60 | |||
| 61 | static gate_result_t screenctrl_open(void* thisptr, gate_video_format_t const* format); | ||
| 62 | static gate_result_t screenctrl_close(void* thisptr); | ||
| 63 | |||
| 64 | static gate_result_t screenctrl_read(void* thisptr, gate_video_frame_t* frame); | ||
| 65 | |||
| 66 | static gate_result_t screenctrl_get_cursor_pos(void* thisptr, gate_int32_t* ptr_x, gate_int32_t* ptr_y); | ||
| 67 | static gate_result_t screenctrl_move_cursor(void* thisptr, gate_int32_t x, gate_int32_t y); | ||
| 68 | static gate_result_t screenctrl_change_cursor_button_state(void* thisptr, gate_int32_t x, gate_int32_t y, gate_enumint_t button_id, gate_enumint_t button_state); | ||
| 69 | static gate_result_t screenctrl_change_key_state(void* thisptr, gate_input_keycode_t key_code, gate_enumint_t key_state); | ||
| 70 | |||
| 71 | ✗ | static GATE_INTERFACE_VTBL(gate_video_screencontrol) const* init_video_screencontrol_vtbl() | |
| 72 | { | ||
| 73 | static GATE_INTERFACE_VTBL(gate_video_screencontrol) global_vtbl; | ||
| 74 | ✗ | if (global_vtbl.get_interface_name == NULL) | |
| 75 | { | ||
| 76 | GATE_INTERFACE_VTBL(gate_video_screencontrol) const local_vtbl = | ||
| 77 | { | ||
| 78 | &screenctrl_get_interface_name, | ||
| 79 | &screenctrl_release, | ||
| 80 | &screenctrl_retain, | ||
| 81 | |||
| 82 | &screenctrl_read, | ||
| 83 | |||
| 84 | &screenctrl_get_id, | ||
| 85 | &screenctrl_get_name, | ||
| 86 | &screenctrl_get_handle, | ||
| 87 | &screenctrl_get_supported_formats, | ||
| 88 | |||
| 89 | &screenctrl_open, | ||
| 90 | &screenctrl_close, | ||
| 91 | |||
| 92 | &screenctrl_get_cursor_pos, | ||
| 93 | &screenctrl_move_cursor, | ||
| 94 | &screenctrl_change_cursor_button_state, | ||
| 95 | &screenctrl_change_key_state | ||
| 96 | }; | ||
| 97 | ✗ | global_vtbl = local_vtbl; | |
| 98 | } | ||
| 99 | |||
| 100 | ✗ | return &global_vtbl; | |
| 101 | } | ||
| 102 | |||
| 103 | #endif | ||
| 104 | |||
| 105 | |||
| 106 | #if defined(GATE_IO_VIDEO_WINAPI) | ||
| 107 | |||
| 108 | #include "gate/debugging.h" | ||
| 109 | #include "gate/graphics/platform/win32_gdi.h" | ||
| 110 | |||
| 111 | #if !defined(GATE_SYS_WINCE) | ||
| 112 | #define GATE_USE_NT_DESKTOP_API 1 | ||
| 113 | #endif | ||
| 114 | |||
| 115 | typedef struct win32screen_class | ||
| 116 | { | ||
| 117 | GATE_INTERFACE_VTBL(gate_video_screencontrol) const* vtbl; | ||
| 118 | |||
| 119 | gate_atomic_int_t ref_counter; | ||
| 120 | |||
| 121 | unsigned x; | ||
| 122 | unsigned y; | ||
| 123 | unsigned width; | ||
| 124 | unsigned height; | ||
| 125 | unsigned bits_per_pixel; | ||
| 126 | unsigned line_len; | ||
| 127 | |||
| 128 | void* hdesk; | ||
| 129 | TCHAR deskname[128]; | ||
| 130 | gate_size_t deskname_length; | ||
| 131 | |||
| 132 | HDC hscreen; | ||
| 133 | HDC hdc; | ||
| 134 | HGDIOBJ hbmp_backup; | ||
| 135 | HBITMAP hbmp; | ||
| 136 | void* bitmap_bits; | ||
| 137 | gate_size_t bitmap_bits_length; | ||
| 138 | |||
| 139 | } win32screen_t; | ||
| 140 | |||
| 141 | typedef struct | ||
| 142 | { | ||
| 143 | BITMAPINFO info; | ||
| 144 | RGBQUAD colors[256]; | ||
| 145 | } BITMAPINFO_PAL; | ||
| 146 | |||
| 147 | static unsigned init_bmp_pal(RGBQUAD* pal) | ||
| 148 | { | ||
| 149 | gate_color_rgb_t gate_pal[256]; | ||
| 150 | unsigned pal_len = (unsigned)gate_color_palette_rgb8_create(gate_pal); | ||
| 151 | unsigned ndx = 0; | ||
| 152 | for (; ndx != pal_len; ++ndx) | ||
| 153 | { | ||
| 154 | pal[ndx].rgbRed = gate_pal[ndx].r; | ||
| 155 | pal[ndx].rgbGreen = gate_pal[ndx].g; | ||
| 156 | pal[ndx].rgbBlue = gate_pal[ndx].b; | ||
| 157 | pal[ndx].rgbReserved = 0; | ||
| 158 | } | ||
| 159 | return pal_len; | ||
| 160 | } | ||
| 161 | |||
| 162 | static gate_bool_t win32_open_screen(win32screen_t* self) | ||
| 163 | { | ||
| 164 | gate_win32_userapi_t* const user32 = gate_win32_userapi(); | ||
| 165 | gate_result_t succeeded = false; | ||
| 166 | HDC screen = NULL; | ||
| 167 | HDC mem = NULL; | ||
| 168 | HBITMAP bmp = NULL; | ||
| 169 | void* bits = NULL; | ||
| 170 | BITMAPINFO_PAL bmpinfo_pal; | ||
| 171 | BITMAPINFOHEADER* hdr = &bmpinfo_pal.info.bmiHeader; | ||
| 172 | RGBQUAD* ptr_rgb; | ||
| 173 | |||
| 174 | do | ||
| 175 | { | ||
| 176 | if (!user32) break; | ||
| 177 | |||
| 178 | screen = user32->UserGetDC(NULL); | ||
| 179 | if (!screen) break; | ||
| 180 | |||
| 181 | mem = gdi.GdiCreateCompatibleDC(screen); | ||
| 182 | if (!mem) break; | ||
| 183 | |||
| 184 | gate_mem_clear(&bmpinfo_pal, sizeof(bmpinfo_pal)); | ||
| 185 | hdr->biSize = sizeof(BITMAPINFOHEADER); | ||
| 186 | hdr->biWidth = (LONG)self->width; | ||
| 187 | hdr->biHeight = (LONG)self->height; | ||
| 188 | hdr->biPlanes = 1; | ||
| 189 | hdr->biBitCount = self->bits_per_pixel; | ||
| 190 | hdr->biCompression = BI_RGB; | ||
| 191 | hdr->biSizeImage = 0; | ||
| 192 | hdr->biXPelsPerMeter = 0; | ||
| 193 | hdr->biYPelsPerMeter = 0; | ||
| 194 | hdr->biClrImportant = 0; | ||
| 195 | hdr->biClrUsed = 0; | ||
| 196 | |||
| 197 | if (self->bits_per_pixel == 1) | ||
| 198 | { | ||
| 199 | hdr->biClrUsed = 2; | ||
| 200 | ptr_rgb = &bmpinfo_pal.info.bmiColors[0]; | ||
| 201 | ptr_rgb->rgbBlue = ptr_rgb->rgbGreen = ptr_rgb->rgbRed = ptr_rgb->rgbReserved = 0; | ||
| 202 | ptr_rgb = &bmpinfo_pal.info.bmiColors[1]; | ||
| 203 | ptr_rgb->rgbBlue = ptr_rgb->rgbGreen = ptr_rgb->rgbRed = 255; | ||
| 204 | ptr_rgb->rgbReserved = 0; | ||
| 205 | } | ||
| 206 | else if (self->bits_per_pixel == 8) | ||
| 207 | { | ||
| 208 | hdr->biClrUsed = init_bmp_pal(&bmpinfo_pal.info.bmiColors[0]); | ||
| 209 | } | ||
| 210 | bmp = gdi.GdiCreateDIBSection(screen, &bmpinfo_pal.info, DIB_RGB_COLORS, &bits, NULL, 0); | ||
| 211 | if (NULL == bmp) break; | ||
| 212 | |||
| 213 | /* success-case: */ | ||
| 214 | self->hscreen = screen; | ||
| 215 | self->hbmp_backup = gdi.GdiSelectObject(mem, (HGDIOBJ)bmp); | ||
| 216 | self->hdc = mem; | ||
| 217 | self->hbmp = bmp; | ||
| 218 | self->bitmap_bits = bits; | ||
| 219 | succeeded = true; | ||
| 220 | } while (0); | ||
| 221 | |||
| 222 | if (!succeeded) | ||
| 223 | { | ||
| 224 | if (mem) gdi.GdiDeleteDC(mem); | ||
| 225 | if (screen) user32->UserReleaseDC(NULL, screen); | ||
| 226 | } | ||
| 227 | return succeeded; | ||
| 228 | } | ||
| 229 | |||
| 230 | static void win32_close_screen(win32screen_t* self) | ||
| 231 | { | ||
| 232 | if (self->hdc) | ||
| 233 | { | ||
| 234 | gdi.GdiSelectObject(self->hdc, self->hbmp_backup); | ||
| 235 | gdi.GdiDeleteObject((HGDIOBJ)self->hbmp); | ||
| 236 | gdi.GdiDeleteDC(self->hdc); | ||
| 237 | gdi.GdiDeleteDC(self->hscreen); | ||
| 238 | self->hbmp_backup = NULL; | ||
| 239 | self->hbmp = NULL; | ||
| 240 | self->hdc = NULL; | ||
| 241 | } | ||
| 242 | |||
| 243 | if (self->hscreen) | ||
| 244 | { | ||
| 245 | self->hscreen = NULL; | ||
| 246 | } | ||
| 247 | } | ||
| 248 | |||
| 249 | #if defined(GATE_USE_NT_DESKTOP_API) | ||
| 250 | |||
| 251 | static gate_size_t win32_get_desktop_name(void* hdesk, TCHAR* desktop_name_buffer, gate_size_t desktop_name_buffer_capacity) | ||
| 252 | { | ||
| 253 | gate_win32_userapi_t* const user32 = gate_win32_userapi(); | ||
| 254 | DWORD bytes_needed; | ||
| 255 | |||
| 256 | if (!user32 || !user32->UserGetUserObjectInformation || | ||
| 257 | !user32->UserGetUserObjectInformation(hdesk, UOI_NAME, desktop_name_buffer, (DWORD)desktop_name_buffer_capacity * sizeof(TCHAR), &bytes_needed)) | ||
| 258 | { | ||
| 259 | /* api not available, or failed */ | ||
| 260 | return 0; | ||
| 261 | } | ||
| 262 | return bytes_needed / sizeof(TCHAR); | ||
| 263 | } | ||
| 264 | |||
| 265 | static gate_bool_t win32_open_input_desktop(void** ptr_hdesk, TCHAR* ptr_deskname, gate_size_t* ptr_deskname_length) | ||
| 266 | { | ||
| 267 | gate_win32_userapi_t* const user32 = gate_win32_userapi(); | ||
| 268 | void* hdesk; | ||
| 269 | |||
| 270 | if (!user32 || !user32->UserOpenInputDesktop) | ||
| 271 | { | ||
| 272 | return false; | ||
| 273 | } | ||
| 274 | |||
| 275 | hdesk = user32->UserOpenInputDesktop(0, FALSE, GENERIC_ALL); | ||
| 276 | if (hdesk == NULL) | ||
| 277 | { | ||
| 278 | return false; | ||
| 279 | } | ||
| 280 | *ptr_deskname_length = win32_get_desktop_name(hdesk, ptr_deskname, *ptr_deskname_length); | ||
| 281 | *ptr_hdesk = hdesk; | ||
| 282 | return true; | ||
| 283 | } | ||
| 284 | |||
| 285 | static void win32_close_desktop(void** hdesk) | ||
| 286 | { | ||
| 287 | if (*hdesk) | ||
| 288 | { | ||
| 289 | gate_win32_userapi_t* const user32 = gate_win32_userapi(); | ||
| 290 | user32->UserCloseDesktop((HDESK)*hdesk); | ||
| 291 | *hdesk = NULL; | ||
| 292 | } | ||
| 293 | } | ||
| 294 | |||
| 295 | static gate_bool_t win32_are_desktops_equal(void* desk1, TCHAR const* desk1name, gate_size_t desk1name_len, void* desk2, TCHAR const* desk2name, gate_size_t desk2name_len) | ||
| 296 | { | ||
| 297 | if (desk1 == desk2) | ||
| 298 | { | ||
| 299 | return true; | ||
| 300 | } | ||
| 301 | if (desk1name_len != desk2name_len) | ||
| 302 | { | ||
| 303 | return false; | ||
| 304 | } | ||
| 305 | else | ||
| 306 | { | ||
| 307 | return 0 == gate_win32_winstr_comp_range(desk1name, desk2name, desk1name_len); | ||
| 308 | } | ||
| 309 | } | ||
| 310 | |||
| 311 | static gate_bool_t win32_change_thread_desktop(void* target_desktop) | ||
| 312 | { | ||
| 313 | gate_win32_userapi_t* const user32 = gate_win32_userapi(); | ||
| 314 | if (!user32 || !user32->UserSetThreadDesktop | ||
| 315 | || !user32->UserSetThreadDesktop((HDESK)target_desktop)) | ||
| 316 | { | ||
| 317 | GATE_DEBUG_TRACE("SetThreadDesktop() failed"); | ||
| 318 | return false; | ||
| 319 | } | ||
| 320 | return true; | ||
| 321 | } | ||
| 322 | |||
| 323 | static gate_bool_t screenctrl_update_desktop(win32screen_t* self) | ||
| 324 | { | ||
| 325 | void* input_hdesk = NULL; | ||
| 326 | TCHAR input_deskname[128]; | ||
| 327 | gate_size_t input_deskname_len = sizeof(input_deskname) / sizeof(input_deskname[0]); | ||
| 328 | gate_bool_t result = win32_open_input_desktop(&input_hdesk, input_deskname, &input_deskname_len); | ||
| 329 | if (!result) | ||
| 330 | { | ||
| 331 | GATE_DEBUG_TRACE("Failed to open input desktop"); | ||
| 332 | return false; | ||
| 333 | } | ||
| 334 | result = win32_are_desktops_equal(self->hdesk, self->deskname, self->deskname_length, input_hdesk, input_deskname, input_deskname_len); | ||
| 335 | if (result) | ||
| 336 | { | ||
| 337 | /* desktops are equal, no change required */ | ||
| 338 | win32_close_desktop(&input_hdesk); | ||
| 339 | return true; | ||
| 340 | } | ||
| 341 | /* switch to new desktop */ | ||
| 342 | result = win32_change_thread_desktop(input_hdesk); | ||
| 343 | if (!result) | ||
| 344 | { | ||
| 345 | GATE_DEBUG_TRACE("Failed to switch thread-desktop"); | ||
| 346 | win32_close_desktop(&input_hdesk); | ||
| 347 | return false; | ||
| 348 | } | ||
| 349 | /* close old desktop handle and screen objects */ | ||
| 350 | win32_close_screen(self); | ||
| 351 | win32_close_desktop(&self->hdesk); | ||
| 352 | /* install new desktop handle */ | ||
| 353 | self->hdesk = input_hdesk; | ||
| 354 | gate_mem_copy(&self->deskname[0], input_deskname, input_deskname_len * sizeof(TCHAR)); | ||
| 355 | self->deskname_length = input_deskname_len; | ||
| 356 | self->deskname[self->deskname_length] = 0; | ||
| 357 | return true; | ||
| 358 | } | ||
| 359 | |||
| 360 | #endif /* GATE_USE_NT_DESKTOP_API */ | ||
| 361 | |||
| 362 | |||
| 363 | static gate_bool_t screenctrl_is_screen_opened(win32screen_t* self) | ||
| 364 | { | ||
| 365 | return (self->hscreen != NULL) && (self->hdc != NULL) && (self->hbmp != NULL); | ||
| 366 | } | ||
| 367 | |||
| 368 | |||
| 369 | static void screenctrl_destroy(win32screen_t* self) | ||
| 370 | { | ||
| 371 | win32_close_screen(self); | ||
| 372 | #if defined(GATE_USE_NT_DESKTOP_API) | ||
| 373 | win32_close_desktop(&self->hdesk); | ||
| 374 | #endif | ||
| 375 | } | ||
| 376 | |||
| 377 | static char const* screenctrl_get_interface_name(void* thisptr) | ||
| 378 | { | ||
| 379 | return GATE_INTERFACE_NAME_VIDEO_SOURCE_SCREENCONTROL; | ||
| 380 | } | ||
| 381 | |||
| 382 | static void screenctrl_release(void* thisptr) | ||
| 383 | { | ||
| 384 | win32screen_t* const self = (win32screen_t*)thisptr; | ||
| 385 | if (gate_atomic_int_dec(&self->ref_counter) == 0) | ||
| 386 | { | ||
| 387 | screenctrl_destroy(self); | ||
| 388 | } | ||
| 389 | } | ||
| 390 | static int screenctrl_retain(void* thisptr) | ||
| 391 | { | ||
| 392 | win32screen_t* const self = (win32screen_t*)thisptr; | ||
| 393 | return gate_atomic_int_inc(&self->ref_counter); | ||
| 394 | } | ||
| 395 | |||
| 396 | static char const* screenctrl_get_id(void* thisptr) | ||
| 397 | { | ||
| 398 | return "screen"; | ||
| 399 | } | ||
| 400 | static char const* screenctrl_get_name(void* thisptr) | ||
| 401 | { | ||
| 402 | return "screen"; | ||
| 403 | } | ||
| 404 | static gate_intptr_t screenctrl_get_handle(void* thisptr) | ||
| 405 | { | ||
| 406 | win32screen_t* const self = (win32screen_t*)thisptr; | ||
| 407 | return (gate_intptr_t)self->hscreen; | ||
| 408 | } | ||
| 409 | |||
| 410 | #ifndef SM_CXVIRTUALSCREEN | ||
| 411 | #define SM_CXVIRTUALSCREEN 78 | ||
| 412 | #endif | ||
| 413 | |||
| 414 | #ifndef SM_CYVIRTUALSCREEN | ||
| 415 | #define SM_CYVIRTUALSCREEN 79 | ||
| 416 | #endif | ||
| 417 | |||
| 418 | static gate_size_t screenctrl_get_supported_formats(void* thisptr, gate_video_format_t* format_buffer, gate_size_t format_buffer_count) | ||
| 419 | { | ||
| 420 | gate_win32_userapi_t* const user32 = gate_win32_userapi(); | ||
| 421 | int sx, sy; | ||
| 422 | |||
| 423 | GATE_UNUSED_ARG(thisptr); | ||
| 424 | |||
| 425 | if ((format_buffer_count == 0) || (format_buffer == NULL)) | ||
| 426 | { | ||
| 427 | return 0; | ||
| 428 | } | ||
| 429 | |||
| 430 | sx = user32->UserGetSystemMetrics(SM_CXVIRTUALSCREEN); | ||
| 431 | sy = user32->UserGetSystemMetrics(SM_CYVIRTUALSCREEN); | ||
| 432 | if (sx == 0) sx = user32->UserGetSystemMetrics(SM_CXSCREEN); | ||
| 433 | if (sy == 0) sy = user32->UserGetSystemMetrics(SM_CYSCREEN); | ||
| 434 | |||
| 435 | format_buffer->encoding_id = 0; | ||
| 436 | format_buffer->compression = 0; | ||
| 437 | format_buffer->frames_per_second = 5.0f; | ||
| 438 | format_buffer->width = sx; | ||
| 439 | format_buffer->height = sy; | ||
| 440 | format_buffer->bits_per_pixel = 24; | ||
| 441 | |||
| 442 | return 1; | ||
| 443 | } | ||
| 444 | |||
| 445 | static gate_bool_t screenctrl_is_opened(win32screen_t* self) | ||
| 446 | { | ||
| 447 | if ((self->width == 0) || (self->height == 0) || (self->bits_per_pixel == 0)) | ||
| 448 | { | ||
| 449 | return false; | ||
| 450 | } | ||
| 451 | return true; | ||
| 452 | } | ||
| 453 | |||
| 454 | static gate_result_t screenctrl_open(void* thisptr, gate_video_format_t const* format) | ||
| 455 | { | ||
| 456 | win32screen_t* const self = (win32screen_t*)thisptr; | ||
| 457 | |||
| 458 | if (screenctrl_is_opened(self)) | ||
| 459 | { | ||
| 460 | return GATE_RESULT_INVALIDSTATE; | ||
| 461 | } | ||
| 462 | |||
| 463 | if ((NULL == format) || (format->width == 0) || (format->height == 0)) | ||
| 464 | { | ||
| 465 | return GATE_RESULT_INVALIDARG; | ||
| 466 | } | ||
| 467 | |||
| 468 | switch (format->bits_per_pixel) | ||
| 469 | { | ||
| 470 | case 1: | ||
| 471 | case 8: | ||
| 472 | case 16: | ||
| 473 | case 24: | ||
| 474 | self->bits_per_pixel = format->bits_per_pixel; | ||
| 475 | break; | ||
| 476 | default: | ||
| 477 | self->bits_per_pixel = 24; | ||
| 478 | break; | ||
| 479 | } | ||
| 480 | |||
| 481 | self->x = 0; | ||
| 482 | self->y = 0; | ||
| 483 | self->width = format->width; | ||
| 484 | self->height = format->height; | ||
| 485 | self->line_len = ((self->bits_per_pixel * self->width) / 8 + 3) & 0xfffffffc; | ||
| 486 | |||
| 487 | #if defined(GATE_USE_NT_DESKTOP_API) | ||
| 488 | screenctrl_update_desktop(self); | ||
| 489 | #endif | ||
| 490 | |||
| 491 | return GATE_RESULT_OK; | ||
| 492 | } | ||
| 493 | |||
| 494 | static gate_result_t screenctrl_close(void* thisptr) | ||
| 495 | { | ||
| 496 | win32screen_t* const self = (win32screen_t*)thisptr; | ||
| 497 | |||
| 498 | win32_close_screen(self); | ||
| 499 | #if defined(GATE_USE_NT_DESKTOP_API) | ||
| 500 | win32_close_desktop(&self->hdesk); | ||
| 501 | #endif | ||
| 502 | |||
| 503 | self->width = 0; | ||
| 504 | self->height = 0; | ||
| 505 | self->bits_per_pixel; | ||
| 506 | self->bitmap_bits = NULL; | ||
| 507 | self->bitmap_bits_length = 0; | ||
| 508 | |||
| 509 | return GATE_RESULT_OK; | ||
| 510 | } | ||
| 511 | |||
| 512 | static gate_result_t screenctrl_read(void* thisptr, gate_video_frame_t* frame) | ||
| 513 | { | ||
| 514 | win32screen_t* const self = (win32screen_t*)thisptr; | ||
| 515 | gate_bool_t update_succeeded; | ||
| 516 | gate_rasterimage_t raster = GATE_INIT_EMPTY; | ||
| 517 | unsigned y, x; | ||
| 518 | unsigned char const* ptr_src; | ||
| 519 | unsigned char* ptr_dst; | ||
| 520 | |||
| 521 | if ((self->width == 0) || (self->height == 0)) | ||
| 522 | { | ||
| 523 | return GATE_RESULT_INVALIDSTATE; | ||
| 524 | } | ||
| 525 | |||
| 526 | #if defined(GATE_USE_NT_DESKTOP_API) | ||
| 527 | update_succeeded = screenctrl_update_desktop(self); | ||
| 528 | if (!update_succeeded) | ||
| 529 | { | ||
| 530 | GATE_DEBUG_TRACE("Failed to update desktop object"); | ||
| 531 | } | ||
| 532 | #endif | ||
| 533 | |||
| 534 | if (!screenctrl_is_screen_opened(self)) | ||
| 535 | { | ||
| 536 | if (!win32_open_screen(self)) | ||
| 537 | { | ||
| 538 | GATE_DEBUG_TRACE("Failed to open screen"); | ||
| 539 | return GATE_RESULT_FAILED; | ||
| 540 | } | ||
| 541 | } | ||
| 542 | |||
| 543 | update_succeeded = gdi.GdiBitBlt(self->hdc, 0, 0, self->width, self->height, self->hscreen, self->x, self->y, SRCCOPY); | ||
| 544 | if (!update_succeeded) | ||
| 545 | { | ||
| 546 | GATE_DEBUG_TRACE("Failed to copy screen into memory buffer"); | ||
| 547 | return GATE_RESULT_FAILED; | ||
| 548 | } | ||
| 549 | |||
| 550 | switch (self->bits_per_pixel) | ||
| 551 | { | ||
| 552 | case 1: | ||
| 553 | { | ||
| 554 | if (NULL == gate_rasterimage_create(&raster, GATE_IMAGE_PIXELFORMAT_PAL8, self->width, self->height, NULL)) | ||
| 555 | { | ||
| 556 | return GATE_RESULT_OUTOFMEMORY; | ||
| 557 | } | ||
| 558 | for (y = 0; y != self->height; ++y) | ||
| 559 | { | ||
| 560 | ptr_src = (unsigned char const*)self->bitmap_bits + (self->height - 1 - y) * self->line_len; | ||
| 561 | ptr_dst = (unsigned char*)gate_rasterimage_get_line_ptr(&raster, y); | ||
| 562 | for (x = 0; x != self->width; ++x) | ||
| 563 | { | ||
| 564 | gate_uint8_t b = ptr_src[x / 8]; | ||
| 565 | gate_uint8_t bit = x % 8; | ||
| 566 | *ptr_dst = ((b & (1 << (7 - bit))) == 0) ? 0 : 15; | ||
| 567 | ++ptr_dst; | ||
| 568 | } | ||
| 569 | } | ||
| 570 | break; | ||
| 571 | } | ||
| 572 | case 8: | ||
| 573 | { | ||
| 574 | if (NULL == gate_rasterimage_create(&raster, GATE_IMAGE_PIXELFORMAT_PAL8, self->width, self->height, NULL)) | ||
| 575 | { | ||
| 576 | return GATE_RESULT_OUTOFMEMORY; | ||
| 577 | } | ||
| 578 | for (y = 0; y != self->height; ++y) | ||
| 579 | { | ||
| 580 | ptr_src = (unsigned char const*)self->bitmap_bits + (self->height - 1 - y) * self->line_len; | ||
| 581 | ptr_dst = (unsigned char*)gate_rasterimage_get_line_ptr(&raster, y); | ||
| 582 | gate_mem_copy(ptr_dst, ptr_src, self->width); | ||
| 583 | } | ||
| 584 | break; | ||
| 585 | } | ||
| 586 | case 15: | ||
| 587 | case 16: | ||
| 588 | { | ||
| 589 | if (NULL == gate_rasterimage_create(&raster, GATE_IMAGE_PIXELFORMAT_RGB555, self->width, self->height, NULL)) | ||
| 590 | { | ||
| 591 | return GATE_RESULT_OUTOFMEMORY; | ||
| 592 | } | ||
| 593 | for (y = 0; y != self->height; ++y) | ||
| 594 | { | ||
| 595 | ptr_src = (unsigned char const*)self->bitmap_bits + (self->height - 1 - y) * self->line_len; | ||
| 596 | ptr_dst = (unsigned char*)gate_rasterimage_get_line_ptr(&raster, y); | ||
| 597 | for (x = 0; x != self->width; ++x) | ||
| 598 | { | ||
| 599 | gate_uint8_t const b = (ptr_src[0] & 0x1f) << 3; | ||
| 600 | gate_uint8_t const g = ((ptr_src[0] >> 5) | ((ptr_src[1] & 0x03) << 3)) << 3; | ||
| 601 | gate_uint8_t const r = (ptr_src[1] & 0x7c) << 1; | ||
| 602 | |||
| 603 | unsigned const num = ((unsigned)(r >> 3)) << 10 | ||
| 604 | | ((unsigned)(g >> 3)) << 5 | ||
| 605 | | ((unsigned)(b >> 3)); | ||
| 606 | ptr_dst[0] = (gate_uint8_t)(num & 0xff); | ||
| 607 | ptr_dst[1] = (gate_uint8_t)(num >> 8); | ||
| 608 | |||
| 609 | ptr_src += 2; | ||
| 610 | ptr_dst += 2; | ||
| 611 | } | ||
| 612 | } | ||
| 613 | break; | ||
| 614 | } | ||
| 615 | case 24: | ||
| 616 | { | ||
| 617 | if (NULL == gate_rasterimage_create(&raster, GATE_IMAGE_PIXELFORMAT_RGB24, self->width, self->height, NULL)) | ||
| 618 | { | ||
| 619 | return GATE_RESULT_OUTOFMEMORY; | ||
| 620 | } | ||
| 621 | for (y = 0; y != self->height; ++y) | ||
| 622 | { | ||
| 623 | ptr_src = (unsigned char const*)self->bitmap_bits + (self->height - 1 - y) * self->line_len; | ||
| 624 | ptr_dst = (unsigned char*)gate_rasterimage_get_line_ptr(&raster, y); | ||
| 625 | for (x = 0; x != self->width; ++x) | ||
| 626 | { | ||
| 627 | /* BGR to RGB */ | ||
| 628 | ptr_dst[0] = ptr_src[2]; | ||
| 629 | ptr_dst[1] = ptr_src[1]; | ||
| 630 | ptr_dst[2] = ptr_src[0]; | ||
| 631 | ptr_src += 3; | ||
| 632 | ptr_dst += 3; | ||
| 633 | } | ||
| 634 | } | ||
| 635 | break; | ||
| 636 | } | ||
| 637 | default: | ||
| 638 | { | ||
| 639 | return GATE_RESULT_NOTSUPPORTED; | ||
| 640 | } | ||
| 641 | } | ||
| 642 | |||
| 643 | frame->format.encoding_id = 0; | ||
| 644 | frame->format.compression = 0; | ||
| 645 | frame->format.frames_per_second = 5.0f; | ||
| 646 | frame->format.width = self->width; | ||
| 647 | frame->format.height = self->height; | ||
| 648 | frame->format.bits_per_pixel = self->bits_per_pixel; | ||
| 649 | |||
| 650 | frame->record_time = 0; | ||
| 651 | gate_mem_copy(&frame->image, &raster, sizeof(raster)); | ||
| 652 | return GATE_RESULT_OK; | ||
| 653 | } | ||
| 654 | |||
| 655 | static gate_result_t win32_send_input(INPUT* inp) | ||
| 656 | { | ||
| 657 | gate_win32_userapi_t* const user32 = gate_win32_userapi(); | ||
| 658 | UINT result; | ||
| 659 | |||
| 660 | if (!user32) | ||
| 661 | { | ||
| 662 | return GATE_RESULT_NOTAVAILABLE; | ||
| 663 | } | ||
| 664 | |||
| 665 | if (user32->UserSendInput) | ||
| 666 | { | ||
| 667 | result = user32->UserSendInput(1, inp, sizeof(INPUT)); | ||
| 668 | if (result != 1) | ||
| 669 | { | ||
| 670 | return GATE_RESULT_FAILED; | ||
| 671 | } | ||
| 672 | return GATE_RESULT_OK; | ||
| 673 | } | ||
| 674 | |||
| 675 | /* win95 NT 3.1 do not support SendInput: */ | ||
| 676 | if ((inp->type == INPUT_MOUSE) && (user32->UserMouse_event)) | ||
| 677 | { | ||
| 678 | user32->UserMouse_event(inp->mi.dwFlags, inp->mi.dx, inp->mi.dy, inp->mi.mouseData, inp->mi.dwExtraInfo); | ||
| 679 | return GATE_RESULT_OK; | ||
| 680 | } | ||
| 681 | |||
| 682 | if ((inp->type == INPUT_KEYBOARD) && (user32->UserKeybd_event)) | ||
| 683 | { | ||
| 684 | user32->UserKeybd_event((BYTE)inp->ki.wVk, (BYTE)inp->ki.wScan, inp->ki.dwFlags, inp->ki.dwExtraInfo); | ||
| 685 | return GATE_RESULT_OK; | ||
| 686 | } | ||
| 687 | |||
| 688 | return GATE_RESULT_NOTSUPPORTED; | ||
| 689 | } | ||
| 690 | |||
| 691 | #ifndef MOUSEEVENTF_VIRTUALDESK | ||
| 692 | #define MOUSEEVENTF_VIRTUALDESK 0x4000 | ||
| 693 | #endif | ||
| 694 | |||
| 695 | static void win32_prepare_mouse_input(INPUT* inp, gate_int32_t x, gate_int32_t y) | ||
| 696 | { | ||
| 697 | gate_win32_userapi_t* const user32 = gate_win32_userapi(); | ||
| 698 | |||
| 699 | int screen_width; | ||
| 700 | int screen_height; | ||
| 701 | |||
| 702 | if (user32) | ||
| 703 | { | ||
| 704 | screen_width = user32->UserGetSystemMetrics(SM_CXVIRTUALSCREEN); | ||
| 705 | screen_height = user32->UserGetSystemMetrics(SM_CYVIRTUALSCREEN); | ||
| 706 | if (screen_width == 0) screen_width = user32->UserGetSystemMetrics(SM_CXSCREEN); | ||
| 707 | if (screen_height == 0) screen_height = user32->UserGetSystemMetrics(SM_CYSCREEN); | ||
| 708 | inp->mi.dwExtraInfo = user32->UserGetMessageExtraInfo(); | ||
| 709 | } | ||
| 710 | else | ||
| 711 | { | ||
| 712 | screen_width = 640; | ||
| 713 | screen_height = 480; | ||
| 714 | inp->mi.dwExtraInfo = 0; | ||
| 715 | } | ||
| 716 | |||
| 717 | inp->type = INPUT_MOUSE; | ||
| 718 | inp->mi.dx = (screen_width > 0) ? (x * 65536 / screen_width) : 0; | ||
| 719 | inp->mi.dy = (screen_height > 0) ? (y * 65536 / screen_height) : 0; | ||
| 720 | inp->mi.mouseData = 0; | ||
| 721 | inp->mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE | MOUSEEVENTF_VIRTUALDESK; | ||
| 722 | inp->mi.time = 0; | ||
| 723 | } | ||
| 724 | |||
| 725 | static gate_result_t screenctrl_get_cursor_pos(void* thisptr, gate_int32_t* ptr_x, gate_int32_t* ptr_y) | ||
| 726 | { | ||
| 727 | gate_win32_userapi_t* const user32 = gate_win32_userapi(); | ||
| 728 | POINT pnt; | ||
| 729 | if (!user32) | ||
| 730 | { | ||
| 731 | return GATE_RESULT_FAILED; | ||
| 732 | } | ||
| 733 | if (!user32->UserGetCursorPos(&pnt)) | ||
| 734 | { | ||
| 735 | return GATE_RESULT_FAILED; | ||
| 736 | } | ||
| 737 | if (ptr_x) *ptr_x = pnt.x; | ||
| 738 | if (ptr_y) *ptr_y = pnt.y; | ||
| 739 | return GATE_RESULT_OK; | ||
| 740 | } | ||
| 741 | static gate_result_t screenctrl_move_cursor(void* thisptr, gate_int32_t x, gate_int32_t y) | ||
| 742 | { | ||
| 743 | INPUT inp; | ||
| 744 | win32_prepare_mouse_input(&inp, x, y); | ||
| 745 | return win32_send_input(&inp); | ||
| 746 | } | ||
| 747 | static gate_result_t screenctrl_change_cursor_button_state(void* thisptr, gate_int32_t x, gate_int32_t y, gate_enumint_t button_id, gate_enumint_t button_state) | ||
| 748 | { | ||
| 749 | INPUT inp; | ||
| 750 | win32_prepare_mouse_input(&inp, x, y); | ||
| 751 | if (GATE_FLAG_ENABLED(button_state, GATE_VIDEO_SCREENCONTROL_STATE_DOWN)) | ||
| 752 | { | ||
| 753 | /* mouse down */ | ||
| 754 | if (button_id == GATE_VIDEO_SCREENCONTROL_BUTTON_LEFT) inp.mi.dwFlags |= MOUSEEVENTF_LEFTDOWN; | ||
| 755 | if (button_id == GATE_VIDEO_SCREENCONTROL_BUTTON_MIDDLE) inp.mi.dwFlags |= MOUSEEVENTF_MIDDLEDOWN; | ||
| 756 | if (button_id == GATE_VIDEO_SCREENCONTROL_BUTTON_RIGHT) inp.mi.dwFlags |= MOUSEEVENTF_RIGHTDOWN; | ||
| 757 | } | ||
| 758 | else if (GATE_FLAG_ENABLED(button_state, GATE_VIDEO_SCREENCONTROL_STATE_UP)) | ||
| 759 | { | ||
| 760 | /* mouse up */ | ||
| 761 | if (button_id == GATE_VIDEO_SCREENCONTROL_BUTTON_LEFT) inp.mi.dwFlags |= MOUSEEVENTF_LEFTUP; | ||
| 762 | if (button_id == GATE_VIDEO_SCREENCONTROL_BUTTON_MIDDLE) inp.mi.dwFlags |= MOUSEEVENTF_MIDDLEUP; | ||
| 763 | if (button_id == GATE_VIDEO_SCREENCONTROL_BUTTON_RIGHT) inp.mi.dwFlags |= MOUSEEVENTF_RIGHTUP; | ||
| 764 | } | ||
| 765 | return win32_send_input(&inp); | ||
| 766 | } | ||
| 767 | |||
| 768 | static gate_input_keystates_t extract_state_keys_from_key_state_flags(gate_enumint_t key_state) | ||
| 769 | { | ||
| 770 | gate_input_keystates_t value = 0; | ||
| 771 | if (GATE_FLAG_ENABLED(key_state, GATE_VIDEO_SCREENCONTROL_STATE_CTRL)) value |= GATE_KBD_KEYSTATE_CTRL; | ||
| 772 | if (GATE_FLAG_ENABLED(key_state, GATE_VIDEO_SCREENCONTROL_STATE_MENU)) value |= GATE_KBD_KEYSTATE_MENU; | ||
| 773 | if (GATE_FLAG_ENABLED(key_state, GATE_VIDEO_SCREENCONTROL_STATE_SHIFT)) value |= GATE_KBD_KEYSTATE_SHIFT; | ||
| 774 | return value; | ||
| 775 | } | ||
| 776 | |||
| 777 | static void win32_prepare_key_input(INPUT* inp, gate_input_keycode_t keycode, gate_input_keystates_t keystates, gate_bool_t down) | ||
| 778 | { | ||
| 779 | gate_win32_userapi_t* const user32 = gate_win32_userapi(); | ||
| 780 | gate_input_nativekeycode_t vk = 0; | ||
| 781 | |||
| 782 | if (user32 && user32->UserGetMessageExtraInfo) | ||
| 783 | { | ||
| 784 | inp->ki.dwExtraInfo = user32->UserGetMessageExtraInfo(); | ||
| 785 | } | ||
| 786 | else | ||
| 787 | { | ||
| 788 | inp->ki.dwExtraInfo = 0; | ||
| 789 | } | ||
| 790 | |||
| 791 | inp->type = INPUT_KEYBOARD; | ||
| 792 | gate_keyboard_build_native_key(keycode, keystates, &vk); | ||
| 793 | inp->ki.wVk = (WORD)vk; | ||
| 794 | inp->ki.wScan = 0; | ||
| 795 | inp->ki.dwFlags = down ? 0 : KEYEVENTF_KEYUP; | ||
| 796 | inp->ki.time = 0; | ||
| 797 | } | ||
| 798 | |||
| 799 | static gate_result_t screenctrl_change_key_state(void* thisptr, gate_input_keycode_t key_code, gate_enumint_t key_state) | ||
| 800 | { | ||
| 801 | INPUT inp; | ||
| 802 | gate_input_keystates_t state_keys = extract_state_keys_from_key_state_flags(key_state); | ||
| 803 | win32_prepare_key_input(&inp, key_code, state_keys, GATE_FLAG_ENABLED(key_state, GATE_VIDEO_SCREENCONTROL_STATE_DOWN)); | ||
| 804 | return win32_send_input(&inp); | ||
| 805 | } | ||
| 806 | |||
| 807 | |||
| 808 | gate_result_t gate_video_screencontrol_create(gate_video_screencontrol_t** ptr_source) | ||
| 809 | { | ||
| 810 | gate_result_t ret = GATE_RESULT_FAILED; | ||
| 811 | win32screen_t* impl = NULL; | ||
| 812 | |||
| 813 | do | ||
| 814 | { | ||
| 815 | ret = gate_win32_gdi_init(); | ||
| 816 | GATE_BREAK_IF_FAILED(ret); | ||
| 817 | |||
| 818 | impl = (win32screen_t*)gate_mem_alloc(sizeof(win32screen_t)); | ||
| 819 | if (impl == NULL) | ||
| 820 | { | ||
| 821 | ret = GATE_RESULT_OUTOFMEMORY; | ||
| 822 | break; | ||
| 823 | } | ||
| 824 | gate_mem_clear(impl, sizeof(win32screen_t)); | ||
| 825 | impl->vtbl = init_video_screencontrol_vtbl(); | ||
| 826 | gate_atomic_int_init(&impl->ref_counter, 1); | ||
| 827 | |||
| 828 | if (ptr_source) | ||
| 829 | { | ||
| 830 | *ptr_source = (gate_video_screencontrol_t*)impl; | ||
| 831 | impl = NULL; | ||
| 832 | } | ||
| 833 | ret = GATE_RESULT_OK; | ||
| 834 | } while (0); | ||
| 835 | |||
| 836 | if (impl != NULL) | ||
| 837 | { | ||
| 838 | screenctrl_release(impl); | ||
| 839 | } | ||
| 840 | |||
| 841 | return ret; | ||
| 842 | } | ||
| 843 | |||
| 844 | |||
| 845 | #endif | ||
| 846 | |||
| 847 | |||
| 848 | #if defined(GATE_IO_VIDEO_X11) | ||
| 849 | |||
| 850 | #include "gate/platform/posix/xlib.h" | ||
| 851 | #include "gate/debugging.h" | ||
| 852 | |||
| 853 | typedef struct x11screen_class | ||
| 854 | { | ||
| 855 | GATE_INTERFACE_VTBL(gate_video_screencontrol) const* vtbl; | ||
| 856 | |||
| 857 | gate_atomic_int_t ref_counter; | ||
| 858 | Display* x11_display; | ||
| 859 | Window x11_root; | ||
| 860 | gate_video_format_t format; | ||
| 861 | GC x11_gc; | ||
| 862 | int x11_screen; | ||
| 863 | Colormap x11_colmap; | ||
| 864 | } x11screen_t; | ||
| 865 | |||
| 866 | |||
| 867 | ✗ | static gate_result_t x11screen_init(x11screen_t* self) | |
| 868 | { | ||
| 869 | gate_result_t result; | ||
| 870 | |||
| 871 | do | ||
| 872 | { | ||
| 873 | ✗ | result = gate_xlib_load_functions(); | |
| 874 | ✗ | GATE_BREAK_IF_FAILED(result); | |
| 875 | |||
| 876 | ✗ | self->x11_display = xlib.XOpenDisplay(NULL); | |
| 877 | ✗ | if (self->x11_display == NULL) | |
| 878 | { | ||
| 879 | ✗ | self->x11_display = xlib.XOpenDisplay(":0"); | |
| 880 | } | ||
| 881 | ✗ | if (self->x11_display == NULL) | |
| 882 | { | ||
| 883 | ✗ | GATE_DEBUG_TRACE("Failed to open X11 display"); | |
| 884 | ✗ | result = GATE_RESULT_FAILED; | |
| 885 | ✗ | break; | |
| 886 | } | ||
| 887 | ✗ | self->x11_root = xlib.XDefaultRootWindow(self->x11_display); | |
| 888 | |||
| 889 | } while (0); | ||
| 890 | |||
| 891 | ✗ | return result; | |
| 892 | } | ||
| 893 | |||
| 894 | ✗ | static void x11screen_destroy(x11screen_t* self) | |
| 895 | { | ||
| 896 | |||
| 897 | ✗ | if (self->x11_display) | |
| 898 | { | ||
| 899 | ✗ | xlib.XCloseDisplay(self->x11_display); | |
| 900 | ✗ | self->x11_display = NULL; | |
| 901 | } | ||
| 902 | ✗ | } | |
| 903 | |||
| 904 | |||
| 905 | ✗ | static char const* screenctrl_get_interface_name(void* thisptr) | |
| 906 | { | ||
| 907 | ✗ | return GATE_INTERFACE_NAME_VIDEO_SOURCE_SCREENCONTROL; | |
| 908 | } | ||
| 909 | |||
| 910 | |||
| 911 | ✗ | static void screenctrl_release(void* thisptr) | |
| 912 | { | ||
| 913 | ✗ | x11screen_t* const self = (x11screen_t*)thisptr; | |
| 914 | ✗ | if (gate_atomic_int_dec(&self->ref_counter) == 0) | |
| 915 | { | ||
| 916 | ✗ | x11screen_destroy(self); | |
| 917 | ✗ | gate_mem_dealloc(self); | |
| 918 | } | ||
| 919 | ✗ | } | |
| 920 | ✗ | static int screenctrl_retain(void* thisptr) | |
| 921 | { | ||
| 922 | ✗ | x11screen_t* const self = (x11screen_t*)thisptr; | |
| 923 | ✗ | return gate_atomic_int_inc(&self->ref_counter); | |
| 924 | } | ||
| 925 | ✗ | static char const* screenctrl_get_id(void* thisptr) | |
| 926 | { | ||
| 927 | ✗ | return "screen"; | |
| 928 | } | ||
| 929 | ✗ | static char const* screenctrl_get_name(void* thisptr) | |
| 930 | { | ||
| 931 | ✗ | return "screen"; | |
| 932 | } | ||
| 933 | ✗ | static gate_intptr_t screenctrl_get_handle(void* thisptr) | |
| 934 | { | ||
| 935 | ✗ | x11screen_t* const self = (x11screen_t*)thisptr; | |
| 936 | ✗ | return (gate_intptr_t)self->x11_display; | |
| 937 | } | ||
| 938 | ✗ | static gate_size_t screenctrl_get_supported_formats(void* thisptr, gate_video_format_t* format_buffer, gate_size_t format_buffer_count) | |
| 939 | { | ||
| 940 | ✗ | x11screen_t* const self = (x11screen_t*)thisptr; | |
| 941 | ✗ | XWindowAttributes attrs = GATE_INIT_EMPTY; | |
| 942 | |||
| 943 | ✗ | if ((format_buffer == NULL) || (format_buffer_count < 1)) | |
| 944 | { | ||
| 945 | ✗ | GATE_DEBUG_TRACE("Not enough space in output buffer"); | |
| 946 | ✗ | return 0; | |
| 947 | } | ||
| 948 | |||
| 949 | /* get screen dimensions */ | ||
| 950 | ✗ | xlib.XGetWindowAttributes(self->x11_display, self->x11_root, &attrs); | |
| 951 | |||
| 952 | ✗ | format_buffer->encoding_id = 0; | |
| 953 | ✗ | format_buffer->compression = 0; | |
| 954 | ✗ | format_buffer->frames_per_second = 5; | |
| 955 | ✗ | format_buffer->width = (gate_uint32_t)attrs.width; | |
| 956 | ✗ | format_buffer->height = (gate_uint32_t)attrs.height; | |
| 957 | ✗ | format_buffer->bits_per_pixel = 32; | |
| 958 | |||
| 959 | ✗ | return 1; /* one entry was used */ | |
| 960 | } | ||
| 961 | |||
| 962 | ✗ | static gate_result_t screenctrl_open(void* thisptr, gate_video_format_t const* format) | |
| 963 | { | ||
| 964 | ✗ | x11screen_t* const self = (x11screen_t*)thisptr; | |
| 965 | |||
| 966 | ✗ | if (self->x11_gc != NULL) | |
| 967 | { | ||
| 968 | ✗ | GATE_DEBUG_TRACE("Error: X11 GC is already initialized"); | |
| 969 | ✗ | return GATE_RESULT_INVALIDSTATE; | |
| 970 | } | ||
| 971 | |||
| 972 | ✗ | gate_mem_copy(&self->format, format, sizeof(gate_video_format_t)); | |
| 973 | |||
| 974 | ✗ | self->x11_gc = xlib.XCreateGC(self->x11_display, self->x11_root, 0, NULL); | |
| 975 | ✗ | if (!self->x11_gc) | |
| 976 | { | ||
| 977 | ✗ | GATE_DEBUG_TRACE("Error: Failed to create GC on current display"); | |
| 978 | ✗ | return GATE_RESULT_FAILED; | |
| 979 | } | ||
| 980 | ✗ | self->x11_screen = xlib.XDefaultScreen(self->x11_display); | |
| 981 | ✗ | self->x11_colmap = xlib.XDefaultColormap(self->x11_display, self->x11_screen); | |
| 982 | ✗ | return GATE_RESULT_OK; | |
| 983 | } | ||
| 984 | ✗ | static gate_result_t screenctrl_close(void* thisptr) | |
| 985 | { | ||
| 986 | ✗ | x11screen_t* const self = (x11screen_t*)thisptr; | |
| 987 | |||
| 988 | ✗ | if (!self->x11_gc) | |
| 989 | { | ||
| 990 | ✗ | xlib.XFreeGC(self->x11_display, self->x11_gc); | |
| 991 | ✗ | self->x11_gc = NULL; | |
| 992 | ✗ | self->x11_screen = 0; | |
| 993 | ✗ | self->x11_colmap = 0; | |
| 994 | } | ||
| 995 | ✗ | return GATE_RESULT_OK; | |
| 996 | } | ||
| 997 | |||
| 998 | ✗ | static gate_result_t ximage32_to_raster(XImage* ptr_ximage, gate_rasterimage_t* ptr_raster) | |
| 999 | { | ||
| 1000 | unsigned char const* ptr_src_pixel; | ||
| 1001 | unsigned int y; | ||
| 1002 | |||
| 1003 | ✗ | if (NULL == gate_rasterimage_create(ptr_raster, GATE_IMAGE_PIXELFORMAT_RGB24, | |
| 1004 | ✗ | (unsigned)ptr_ximage->width, (unsigned)ptr_ximage->height, NULL)) | |
| 1005 | { | ||
| 1006 | ✗ | return GATE_RESULT_OUTOFMEMORY; | |
| 1007 | } | ||
| 1008 | |||
| 1009 | ✗ | ptr_src_pixel = (unsigned char const*)ptr_ximage->data; | |
| 1010 | ✗ | for (y = 0; y != ptr_raster->height; ++y) | |
| 1011 | { | ||
| 1012 | ✗ | gate_color_rgb_t* ptr_dst_pixel = (gate_color_rgb_t*)gate_rasterimage_get_line_ptr(ptr_raster, y); | |
| 1013 | unsigned int x; | ||
| 1014 | ✗ | for (x = 0; x != ptr_raster->width; ++x) | |
| 1015 | { | ||
| 1016 | ✗ | ptr_dst_pixel->b = *(ptr_src_pixel++); | |
| 1017 | ✗ | ptr_dst_pixel->g = *(ptr_src_pixel++); | |
| 1018 | ✗ | ptr_dst_pixel->r = *(ptr_src_pixel++); | |
| 1019 | ✗ | ++ptr_src_pixel; /* skip A value */ | |
| 1020 | ✗ | ++ptr_dst_pixel; | |
| 1021 | } | ||
| 1022 | } | ||
| 1023 | ✗ | return GATE_RESULT_OK; | |
| 1024 | } | ||
| 1025 | |||
| 1026 | ✗ | static gate_result_t ximage16_to_raster(XImage* ptr_ximage, gate_rasterimage_t* ptr_raster) | |
| 1027 | { | ||
| 1028 | unsigned char const* ptr_src_pixel; | ||
| 1029 | unsigned int y; | ||
| 1030 | |||
| 1031 | ✗ | if (NULL == gate_rasterimage_create(ptr_raster, GATE_IMAGE_PIXELFORMAT_RGB24, | |
| 1032 | ✗ | (unsigned)ptr_ximage->width, (unsigned)ptr_ximage->height, NULL)) | |
| 1033 | { | ||
| 1034 | ✗ | return GATE_RESULT_OUTOFMEMORY; | |
| 1035 | } | ||
| 1036 | |||
| 1037 | ✗ | ptr_src_pixel = (unsigned char const*)ptr_ximage->data;; | |
| 1038 | ✗ | for (y = 0; y != ptr_raster->height; ++y) | |
| 1039 | { | ||
| 1040 | ✗ | gate_color_rgb_t* ptr_dst_pixel = (gate_color_rgb_t*)gate_rasterimage_get_line_ptr(ptr_raster, y); | |
| 1041 | unsigned int x; | ||
| 1042 | ✗ | for (x = 0; x != ptr_raster->width; ++x) | |
| 1043 | { | ||
| 1044 | ✗ | const gate_uint16_t col = (gate_uint16_t)(ptr_src_pixel[0]) | (((gate_uint16_t)ptr_src_pixel[1]) << 8); | |
| 1045 | ✗ | ptr_dst_pixel->b = (gate_uint8_t)((col & 0x001F) << 3); | |
| 1046 | ✗ | ptr_dst_pixel->g = (gate_uint8_t)((col & 0x07e0) >> 3); | |
| 1047 | ✗ | ptr_dst_pixel->r = (gate_uint8_t)((col & 0xF800) >> 8); | |
| 1048 | ✗ | ptr_src_pixel += 2; | |
| 1049 | ✗ | ++ptr_dst_pixel; | |
| 1050 | } | ||
| 1051 | } | ||
| 1052 | ✗ | return GATE_RESULT_OK; | |
| 1053 | } | ||
| 1054 | |||
| 1055 | ✗ | static gate_result_t ximage_to_raster(Display* display, Colormap colmap, XImage* ptr_ximage, gate_rasterimage_t* ptr_raster) | |
| 1056 | { | ||
| 1057 | unsigned int y; | ||
| 1058 | |||
| 1059 | ✗ | if (NULL == gate_rasterimage_create(ptr_raster, GATE_IMAGE_PIXELFORMAT_RGB24, | |
| 1060 | ✗ | (unsigned)ptr_ximage->width, (unsigned)ptr_ximage->height, NULL)) | |
| 1061 | { | ||
| 1062 | ✗ | return GATE_RESULT_OUTOFMEMORY; | |
| 1063 | } | ||
| 1064 | |||
| 1065 | ✗ | for (y = 0; y != ptr_raster->height; ++y) | |
| 1066 | { | ||
| 1067 | ✗ | gate_color_rgb_t* ptr_dst_pixel = (gate_color_rgb_t*)gate_rasterimage_get_line_ptr(ptr_raster, y); | |
| 1068 | unsigned int x; | ||
| 1069 | ✗ | for (x = 0; x != ptr_raster->width; ++x) | |
| 1070 | { | ||
| 1071 | XColor xcol; | ||
| 1072 | ✗ | xcol.pixel = xlib.XGetPixel(ptr_ximage, (int)x, (int)y); | |
| 1073 | ✗ | xlib.XQueryColor(display, colmap, &xcol); | |
| 1074 | |||
| 1075 | ✗ | ptr_dst_pixel->r = (gate_uint8_t)(xcol.red >> 8); | |
| 1076 | ✗ | ptr_dst_pixel->g = (gate_uint8_t)(xcol.green >> 8); | |
| 1077 | ✗ | ptr_dst_pixel->b = (gate_uint8_t)(xcol.blue >> 8); | |
| 1078 | ✗ | ++ptr_dst_pixel; | |
| 1079 | } | ||
| 1080 | } | ||
| 1081 | ✗ | return GATE_RESULT_OK; | |
| 1082 | } | ||
| 1083 | |||
| 1084 | ✗ | static gate_result_t screenctrl_read(void* thisptr, gate_video_frame_t* frame) | |
| 1085 | { | ||
| 1086 | ✗ | gate_result_t ret = GATE_RESULT_FAILED; | |
| 1087 | ✗ | x11screen_t* const self = (x11screen_t*)thisptr; | |
| 1088 | ✗ | XImage* ptr_ximage = NULL; | |
| 1089 | ✗ | gate_rasterimage_t raster = GATE_INIT_EMPTY; | |
| 1090 | |||
| 1091 | do | ||
| 1092 | { | ||
| 1093 | ✗ | ptr_ximage = xlib.XGetImage(self->x11_display, self->x11_root, | |
| 1094 | 0, 0, self->format.width, self->format.height, AllPlanes, ZPixmap); | ||
| 1095 | ✗ | if (ptr_ximage == NULL) | |
| 1096 | { | ||
| 1097 | ✗ | ret = GATE_RESULT_FAILED; | |
| 1098 | ✗ | break; | |
| 1099 | } | ||
| 1100 | |||
| 1101 | ✗ | if ((ptr_ximage->bits_per_pixel == 32) && (ptr_ximage->depth == 24)) | |
| 1102 | { | ||
| 1103 | ✗ | ret = ximage32_to_raster(ptr_ximage, &raster); | |
| 1104 | } | ||
| 1105 | ✗ | else if ((ptr_ximage->bits_per_pixel == 16) && (ptr_ximage->depth == 16)) | |
| 1106 | { | ||
| 1107 | ✗ | ret = ximage16_to_raster(ptr_ximage, &raster); | |
| 1108 | } | ||
| 1109 | else | ||
| 1110 | { | ||
| 1111 | ✗ | ret = ximage_to_raster(self->x11_display, self->x11_colmap, ptr_ximage, &raster); | |
| 1112 | } | ||
| 1113 | |||
| 1114 | ✗ | GATE_BREAK_IF_FAILED(ret); | |
| 1115 | |||
| 1116 | ✗ | frame->format.bits_per_pixel = raster.bits_per_pixel; | |
| 1117 | ✗ | frame->format.compression = 0; | |
| 1118 | ✗ | frame->format.encoding_id = 0; | |
| 1119 | ✗ | frame->format.frames_per_second = self->format.frames_per_second; | |
| 1120 | ✗ | frame->format.width = raster.width; | |
| 1121 | ✗ | frame->format.height = raster.height; | |
| 1122 | ✗ | gate_rasterimage_duplicate(&frame->image, &raster); | |
| 1123 | ✗ | frame->record_time = 0; | |
| 1124 | |||
| 1125 | ✗ | ret = GATE_RESULT_OK; | |
| 1126 | } while (0); | ||
| 1127 | |||
| 1128 | ✗ | gate_rasterimage_release(&raster); | |
| 1129 | |||
| 1130 | ✗ | if (ptr_ximage != NULL) | |
| 1131 | { | ||
| 1132 | ✗ | xlib.XDestroyImage(ptr_ximage); | |
| 1133 | } | ||
| 1134 | |||
| 1135 | ✗ | return ret; | |
| 1136 | } | ||
| 1137 | |||
| 1138 | ✗ | static gate_result_t screenctrl_get_cursor_pos(void* thisptr, gate_int32_t* ptr_x, gate_int32_t* ptr_y) | |
| 1139 | { | ||
| 1140 | ✗ | gate_result_t ret = GATE_RESULT_FAILED; | |
| 1141 | ✗ | x11screen_t* const self = (x11screen_t*)thisptr; | |
| 1142 | Window win, subwin; | ||
| 1143 | int gx, gy, wx, wy; | ||
| 1144 | unsigned int state; | ||
| 1145 | |||
| 1146 | ✗ | if (xlib.XQueryPointer(self->x11_display, self->x11_root, &win, &subwin, &gx, &gy, &wx, &wy, &state)) | |
| 1147 | { | ||
| 1148 | ✗ | if (ptr_x) *ptr_x = gx; | |
| 1149 | ✗ | if (ptr_y) *ptr_y = gy; | |
| 1150 | ✗ | return GATE_RESULT_OK; | |
| 1151 | } | ||
| 1152 | ✗ | return GATE_RESULT_FAILED; | |
| 1153 | } | ||
| 1154 | |||
| 1155 | ✗ | static gate_result_t screenctrl_move_cursor(void* thisptr, gate_int32_t x, gate_int32_t y) | |
| 1156 | { | ||
| 1157 | ✗ | gate_result_t ret = GATE_RESULT_FAILED; | |
| 1158 | ✗ | x11screen_t* const self = (x11screen_t*)thisptr; | |
| 1159 | ✗ | xlib.XWarpPointer(self->x11_display, None, self->x11_root, 0, 0, 0, 0, (int)x, (int)y); | |
| 1160 | ✗ | return GATE_RESULT_OK; | |
| 1161 | } | ||
| 1162 | |||
| 1163 | ✗ | static gate_result_t screenctrl_change_cursor_button_state(void* thisptr, gate_int32_t x, gate_int32_t y, gate_enumint_t button_id, gate_enumint_t button_state) | |
| 1164 | { | ||
| 1165 | /* http://www.linuxquestions.org/questions/programming-9/simulating-a-mouse-click-594576/ */ | ||
| 1166 | |||
| 1167 | ✗ | gate_result_t ret = GATE_RESULT_FAILED; | |
| 1168 | ✗ | x11screen_t* const self = (x11screen_t*)thisptr; | |
| 1169 | ✗ | XEvent xevt = GATE_INIT_EMPTY; | |
| 1170 | ✗ | XButtonEvent* evt = &xevt.xbutton; | |
| 1171 | |||
| 1172 | ✗ | xlib.XWarpPointer(self->x11_display, None, self->x11_root, 0, 0, 0, 0, (int)x, (int)y); | |
| 1173 | |||
| 1174 | /* find inner sub-window */ | ||
| 1175 | ✗ | evt->type = (button_state == GATE_VIDEO_SCREENCONTROL_STATE_DOWN) ? ButtonPress : ButtonRelease; | |
| 1176 | ✗ | evt->display = self->x11_display; | |
| 1177 | ✗ | evt->same_screen = True; | |
| 1178 | ✗ | evt->time = CurrentTime; | |
| 1179 | |||
| 1180 | ✗ | evt->subwindow = self->x11_root; | |
| 1181 | do | ||
| 1182 | { | ||
| 1183 | ✗ | evt->window = evt->subwindow; | |
| 1184 | ✗ | xlib.XQueryPointer(self->x11_display, evt->window, &evt->root, &evt->subwindow, &evt->x_root, &evt->y_root, &evt->x, &evt->y, &evt->state); | |
| 1185 | ✗ | } while (evt->subwindow != 0); | |
| 1186 | |||
| 1187 | |||
| 1188 | |||
| 1189 | ✗ | evt->state = 0; | |
| 1190 | ✗ | if (button_id == GATE_VIDEO_SCREENCONTROL_BUTTON_LEFT) | |
| 1191 | { | ||
| 1192 | ✗ | evt->button = Button1; | |
| 1193 | ✗ | evt->state |= Button1Mask; | |
| 1194 | } | ||
| 1195 | ✗ | else if (button_id == GATE_VIDEO_SCREENCONTROL_BUTTON_MIDDLE) | |
| 1196 | { | ||
| 1197 | ✗ | evt->button = Button2; | |
| 1198 | ✗ | evt->state |= Button2Mask; | |
| 1199 | } | ||
| 1200 | ✗ | else if (button_id == GATE_VIDEO_SCREENCONTROL_BUTTON_RIGHT) | |
| 1201 | { | ||
| 1202 | ✗ | evt->button = Button3; | |
| 1203 | ✗ | evt->state |= Button3Mask; | |
| 1204 | } | ||
| 1205 | |||
| 1206 | ✗ | xlib.XSendEvent(self->x11_display, PointerWindow, True, 0xfff, &xevt); | |
| 1207 | ✗ | xlib.XFlush(self->x11_display); | |
| 1208 | |||
| 1209 | ✗ | return GATE_RESULT_OK; | |
| 1210 | } | ||
| 1211 | |||
| 1212 | ✗ | static gate_result_t screenctrl_change_key_state(void* thisptr, gate_input_keycode_t key_code, gate_enumint_t key_state) | |
| 1213 | { | ||
| 1214 | ✗ | gate_result_t ret = GATE_RESULT_FAILED; | |
| 1215 | ✗ | x11screen_t* const self = (x11screen_t*)thisptr; | |
| 1216 | XKeyEvent evt; | ||
| 1217 | int focus_state; | ||
| 1218 | ✗ | Window focus_win = self->x11_root; | |
| 1219 | ✗ | KeySym x11_key_sym = key_code; | |
| 1220 | ✗ | gate_bool_t key_down = GATE_FLAG_ENABLED(key_state, GATE_VIDEO_SCREENCONTROL_STATE_DOWN); | |
| 1221 | ✗ | unsigned int x11_state = 0; | |
| 1222 | |||
| 1223 | ✗ | if (key_down && GATE_FLAG_ENABLED(key_state, GATE_VIDEO_SCREENCONTROL_STATE_CTRL)) | |
| 1224 | { | ||
| 1225 | ✗ | x11_state |= ControlMask; | |
| 1226 | } | ||
| 1227 | ✗ | if (key_down && GATE_FLAG_ENABLED(key_state, GATE_VIDEO_SCREENCONTROL_STATE_MENU)) | |
| 1228 | { | ||
| 1229 | ✗ | x11_state |= Mod1Mask; | |
| 1230 | } | ||
| 1231 | ✗ | if (key_down && GATE_FLAG_ENABLED(key_state, GATE_VIDEO_SCREENCONTROL_STATE_SHIFT)) | |
| 1232 | { | ||
| 1233 | ✗ | x11_state |= ShiftMask; | |
| 1234 | } | ||
| 1235 | |||
| 1236 | ✗ | xlib.XGetInputFocus(self->x11_display, &focus_win, &focus_state); | |
| 1237 | |||
| 1238 | ✗ | evt.display = self->x11_display; | |
| 1239 | ✗ | evt.window = focus_win; | |
| 1240 | ✗ | evt.root = self->x11_root; | |
| 1241 | ✗ | evt.subwindow = None; | |
| 1242 | ✗ | evt.time = CurrentTime; | |
| 1243 | ✗ | evt.x = 1; | |
| 1244 | ✗ | evt.y = 1; | |
| 1245 | ✗ | evt.x_root = 1; | |
| 1246 | ✗ | evt.y_root = 1; | |
| 1247 | ✗ | evt.same_screen = True; | |
| 1248 | ✗ | evt.send_event = True; | |
| 1249 | ✗ | evt.keycode = xlib.XKeysymToKeycode(self->x11_display, x11_key_sym); | |
| 1250 | ✗ | evt.state = x11_state; | |
| 1251 | ✗ | evt.type = key_down ? KeyPress : KeyRelease; | |
| 1252 | ✗ | xlib.XSendEvent(evt.display, evt.window, True, KeyPressMask, (XEvent*)&evt); | |
| 1253 | |||
| 1254 | ✗ | return GATE_RESULT_OK; | |
| 1255 | } | ||
| 1256 | |||
| 1257 | |||
| 1258 | |||
| 1259 | ✗ | gate_result_t gate_video_screencontrol_create(gate_video_screencontrol_t** ptr_source) | |
| 1260 | { | ||
| 1261 | ✗ | gate_result_t ret = GATE_RESULT_FAILED; | |
| 1262 | ✗ | x11screen_t* impl = NULL; | |
| 1263 | |||
| 1264 | do | ||
| 1265 | { | ||
| 1266 | ✗ | impl = (x11screen_t*)gate_mem_alloc(sizeof(x11screen_t)); | |
| 1267 | ✗ | if (impl == NULL) | |
| 1268 | { | ||
| 1269 | ✗ | ret = GATE_RESULT_OUTOFMEMORY; | |
| 1270 | ✗ | break; | |
| 1271 | } | ||
| 1272 | ✗ | gate_mem_clear(impl, sizeof(x11screen_t)); | |
| 1273 | ✗ | impl->vtbl = init_video_screencontrol_vtbl(); | |
| 1274 | ✗ | gate_atomic_int_init(&impl->ref_counter, 1); | |
| 1275 | |||
| 1276 | ✗ | ret = x11screen_init(impl); | |
| 1277 | ✗ | GATE_BREAK_IF_FAILED(ret); | |
| 1278 | |||
| 1279 | ✗ | if (ptr_source) | |
| 1280 | { | ||
| 1281 | ✗ | *ptr_source = (gate_video_screencontrol_t*)impl; | |
| 1282 | ✗ | impl = NULL; | |
| 1283 | } | ||
| 1284 | ✗ | ret = GATE_RESULT_OK; | |
| 1285 | } while (0); | ||
| 1286 | |||
| 1287 | ✗ | if (impl != NULL) | |
| 1288 | { | ||
| 1289 | ✗ | screenctrl_release(impl); | |
| 1290 | } | ||
| 1291 | |||
| 1292 | ✗ | return ret; | |
| 1293 | } | ||
| 1294 | |||
| 1295 | #endif | ||
| 1296 | |||
| 1297 | #if defined(GATE_IO_VIDEO_ANDROID) | ||
| 1298 | |||
| 1299 | gate_result_t gate_video_screencontrol_create(gate_video_screencontrol_t** ptr_source) | ||
| 1300 | { | ||
| 1301 | GATE_UNUSED_ARG(ptr_source); | ||
| 1302 | /* TODO */ | ||
| 1303 | return GATE_RESULT_NOTSUPPORTED; | ||
| 1304 | } | ||
| 1305 | |||
| 1306 | #endif | ||
| 1307 | |||
| 1308 | |||
| 1309 | #if defined(GATE_IO_VIDEO_NO_IMPL) | ||
| 1310 | |||
| 1311 | gate_result_t gate_video_screencontrol_create(gate_video_screencontrol_t** ptr_source) | ||
| 1312 | { | ||
| 1313 | GATE_UNUSED_ARG(ptr_source); | ||
| 1314 | return GATE_RESULT_NOTSUPPORTED; | ||
| 1315 | } | ||
| 1316 | |||
| 1317 | #endif | ||
| 1318 |