1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4 
5 #ifdef USE_SDL_VIDEO
6 
7 // FIXME: make libfsml independent of libfsemu
8 #include "../emu/video.h"
9 
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <stddef.h>
13 #include <string.h>
14 
15 #ifdef USE_SDL2
16 #define USE_SDL
17 #endif
18 
19 #ifdef USE_SDL
20 #include <SDL.h>
21 #endif
22 
23 //#ifdef USE_GLIB
24 //#include <glib.h>
25 //#endif
26 
27 #include <fs/conf.h>
28 #include <fs/lazyness.h>
29 #ifdef WITH_GLEW
30 #include <GL/glew.h>
31 #endif
32 #include <fs/glib.h>
33 #include <fs/ml.h>
34 #include <fs/thread.h>
35 
36 #ifdef USE_OPENGL
37 #include <fs/ml/opengl.h>
38 #endif
39 #include <fs/ml/options.h>
40 
41 #define FSE_INTERNAL_API
42 #include <fs/emu/input.h>
43 #include <fs/emu/monitor.h>
44 #include <fs/emu/video.h>
45 #include "ml_internal.h"
46 
47 SDL_Window *g_fs_ml_window = NULL;
48 SDL_GLContext g_fs_ml_context = 0;
49 int g_fs_ml_had_input_grab = 0;
50 int g_fs_ml_was_fullscreen = 0;
51 
52 static GQueue *g_video_event_queue;
53 static fs_mutex *g_video_event_mutex;
54 static fs_thread_id_t g_video_thread_id;
55 static fs_condition *g_video_cond;
56 static fs_mutex *g_video_mutex;
57 static int g_display;
58 static int g_has_input_grab = 0;
59 static int g_initial_input_grab = 0;
60 static bool g_grab_input_on_activate;
61 static int g_fs_ml_automatic_input_grab = 1;
62 static int g_fs_ml_keyboard_input_grab = 1;
63 static int g_fsaa = 0;
64 static int g_f12_state, g_f11_state;
65 static char *g_window_title;
66 static int g_window_width, g_window_height;
67 static int g_window_x, g_window_y;
68 static int g_window_resizable;
69 static int g_fullscreen_width, g_fullscreen_height;
70 static GLint g_max_texture_size;
71 
72 #define FS_ML_VIDEO_EVENT_GRAB_INPUT 1
73 #define FS_ML_VIDEO_EVENT_UNGRAB_INPUT 2
74 #define FS_ML_VIDEO_EVENT_SHOW_CURSOR 3
75 #define FS_ML_VIDEO_EVENT_HIDE_CURSOR 4
76 #define FS_ML_VIDEO_EVENT_TOGGLE_FULLSCREEN 5
77 #define FS_ML_VIDEO_EVENT_ENABLE_FULLSCREEN 6
78 #define FS_ML_VIDEO_EVENT_DISABLE_FULLSCREEN 7
79 #define FS_ML_VIDEO_EVENT_ACTIVATE_WINDOW_SWITCHER 8
80 
81 #define FULLSCREEN_FULLSCREEN 0
82 #define FULLSCREEN_WINDOW 1
83 #define FULLSCREEN_DESKTOP 2
84 
is_video_thread(void)85 static inline bool is_video_thread(void)
86 {
87     return fs_thread_id() == g_video_thread_id;
88 }
89 
fs_ml_get_max_texture_size()90 int fs_ml_get_max_texture_size()
91 {
92     return g_max_texture_size;
93 }
94 
fs_ml_get_fullscreen_width()95 int fs_ml_get_fullscreen_width()
96 {
97     return g_fullscreen_width;
98 }
99 
fs_ml_get_fullscreen_height()100 int fs_ml_get_fullscreen_height()
101 {
102     return g_fullscreen_height;
103 }
104 
fs_ml_get_windowed_width()105 int fs_ml_get_windowed_width()
106 {
107     return g_window_width;
108 }
109 
fs_ml_get_windowed_height()110 int fs_ml_get_windowed_height()
111 {
112     return g_window_height;
113 }
114 
post_video_event(int event)115 static void post_video_event(int event)
116 {
117     if (fse_drivers()) {
118         // printf("FSE_DRIVERS: ignoring post_video_event\n");
119     } else {
120         fs_mutex_lock(g_video_event_mutex);
121         g_queue_push_head(g_video_event_queue, FS_INT_TO_POINTER(event));
122         fs_mutex_unlock(g_video_event_mutex);
123     }
124 }
125 
process_video_events(void)126 static void process_video_events(void)
127 {
128     fs_mutex_lock(g_video_event_mutex);
129     int count = g_queue_get_length(g_video_event_queue);
130     for (int i = 0; i < count; i++) {
131         int event = FS_POINTER_TO_INT(g_queue_pop_tail(g_video_event_queue));
132         if (event == FS_ML_VIDEO_EVENT_GRAB_INPUT) {
133             fs_ml_set_input_grab(true);
134         } else if (event == FS_ML_VIDEO_EVENT_UNGRAB_INPUT) {
135             fs_ml_set_input_grab(false);
136         } else if (event == FS_ML_VIDEO_EVENT_SHOW_CURSOR) {
137             fs_ml_show_cursor(1, 1);
138         } else if (event == FS_ML_VIDEO_EVENT_HIDE_CURSOR) {
139             fs_ml_show_cursor(0, 1);
140         } else if (event == FS_ML_VIDEO_EVENT_TOGGLE_FULLSCREEN) {
141             fs_ml_toggle_fullscreen();
142         } else if (event == FS_ML_VIDEO_EVENT_ENABLE_FULLSCREEN) {
143             fs_ml_set_fullscreen(true);
144         } else if (event == FS_ML_VIDEO_EVENT_DISABLE_FULLSCREEN) {
145             fs_ml_set_fullscreen(false);
146         }
147     }
148     fs_mutex_unlock(g_video_event_mutex);
149 }
150 
fs_ml_input_grab(void)151 bool fs_ml_input_grab(void)
152 {
153     return g_has_input_grab;
154 }
155 
fs_ml_automatic_input_grab(void)156 bool fs_ml_automatic_input_grab(void)
157 {
158     return g_fs_ml_automatic_input_grab;
159 }
160 
fs_ml_set_input_grab(bool grab)161 void fs_ml_set_input_grab(bool grab)
162 {
163     if (!is_video_thread()) {
164         post_video_event(grab ? FS_ML_VIDEO_EVENT_GRAB_INPUT :
165                                 FS_ML_VIDEO_EVENT_UNGRAB_INPUT);
166         /* FIXME: Not really, yet */
167         g_has_input_grab = grab ? 1 : 0;
168         return;
169     }
170 
171     if (grab) {
172         fs_log("[INPUT] Grabbing input\n");
173     } else {
174         fs_log("[INPUT] Releasing input\n");
175     }
176     SDL_SetWindowGrab(g_fs_ml_window, grab ? SDL_TRUE : SDL_FALSE);
177     SDL_SetRelativeMouseMode(grab ? SDL_TRUE : SDL_FALSE);
178     if (fs_ml_cursor_allowed())
179         fs_ml_show_cursor(!grab, 1);
180     g_has_input_grab = grab ? 1 : 0;
181 }
182 
fs_ml_activate_window_switcher(void)183 void fs_ml_activate_window_switcher(void)
184 {
185     if (!is_video_thread()) {
186         post_video_event(FS_ML_VIDEO_EVENT_ACTIVATE_WINDOW_SWITCHER);
187         return;
188     }
189 
190     fs_ml_activate_window_switcher_impl();
191 }
192 
fs_ml_set_input_grab_on_activate(bool grab)193 void fs_ml_set_input_grab_on_activate(bool grab)
194 {
195     g_grab_input_on_activate = grab;
196 }
197 
fs_ml_set_video_fsaa(int fsaa)198 void fs_ml_set_video_fsaa(int fsaa)
199 {
200     g_fsaa = fsaa;
201 }
202 
fs_ml_show_cursor(int show,int immediate)203 void fs_ml_show_cursor(int show, int immediate)
204 {
205     if (immediate) {
206         SDL_ShowCursor(show);
207     }
208     else {
209         post_video_event(show ? FS_ML_VIDEO_EVENT_SHOW_CURSOR :
210                 FS_ML_VIDEO_EVENT_HIDE_CURSOR);
211     }
212 }
213 
log_opengl_information(void)214 static void log_opengl_information(void)
215 {
216     static int written = 0;
217     if (written) {
218         return;
219     }
220     written = 1;
221     char *software_renderer = NULL;
222     const char *str;
223     str = (const char*) glGetString(GL_VENDOR);
224     if (str) {
225         fs_log("opengl vendor: %s\n", str);
226     }
227     str = (const char*) glGetString(GL_RENDERER);
228     if (str) {
229         fs_log("opengl renderer: %s\n", str);
230         if (strstr(str, "GDI Generic") != NULL) {
231             software_renderer = g_strdup(str);
232         } else if (strstr(str, "llvmpipe") != NULL) {
233             software_renderer = g_strdup(str);
234         }
235     }
236     str = (const char*) glGetString(GL_VERSION);
237     if (str) {
238         fs_log("opengl version: %s\n", str);
239     }
240     str = (const char*) glGetString(GL_SHADING_LANGUAGE_VERSION);
241     if (str) {
242         fs_log("opengl shading language version: %s\n", str);
243     }
244     str = (const char*) glGetString(GL_EXTENSIONS);
245     if (str) {
246         fs_log("opengl extensions: %s\n", str);
247     }
248     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &g_max_texture_size);
249     fs_log("opengl max texture size (estimate): %dx%d\n", g_max_texture_size,
250             g_max_texture_size);
251 
252     if (software_renderer) {
253         fs_emu_warning("No HW OpenGL driver: %s", software_renderer);
254         g_free(software_renderer);
255     }
256 }
257 
set_video_mode()258 static void set_video_mode()
259 {
260     int flags = SDL_WINDOW_OPENGL;
261     if (g_fs_emu_video_fullscreen_mode != FULLSCREEN_WINDOW &&
262             g_window_resizable) {
263         flags |= SDL_WINDOW_RESIZABLE;
264     }
265     int x = g_window_x, y = g_window_y;
266     int w = -1, h = -1;
267 
268 //    if (g_initial_input_grab) {
269 //        flags |= SDL_WINDOW_INPUT_GRABBED;
270 //        g_has_input_grab = 1;
271 //    }
272 
273     if (g_fs_emu_video_fullscreen == 1) {
274         w = g_fullscreen_width;
275         h = g_fullscreen_height;
276         //w = g_window_width;
277         //h = g_window_height;
278 
279         if (g_fs_emu_video_fullscreen_mode == FULLSCREEN_WINDOW) {
280             fs_log("using fullscreen window mode\n");
281             //x = 0;
282             //y = 0;
283             //w = g_fullscreen_width;
284             //h = g_fullscreen_height;
285             flags |= SDL_WINDOW_BORDERLESS;
286 
287             FSEmuMonitor monitor;
288             fs_emu_monitor_get_by_index(g_display, &monitor);
289             x = monitor.rect.x;
290             y = monitor.rect.y;
291             w = monitor.rect.w;
292             h = monitor.rect.h;
293         }
294         else if (g_fs_emu_video_fullscreen_mode == FULLSCREEN_DESKTOP) {
295             fs_log("using fullscreen desktop mode\n");
296             // the width and height will not be used for the fullscreen
297             // desktop mode, only for the window when toggling fullscreen
298             // state
299 #if 0
300             w = g_window_width;
301             h = g_window_height;
302 #else
303             FSEmuMonitor monitor;
304             fs_emu_monitor_get_by_index(g_display, &monitor);
305             x = monitor.rect.x;
306             y = monitor.rect.y;
307             w = monitor.rect.w;
308             h = monitor.rect.h;
309 #endif
310             flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
311         }
312         else {
313             fs_log("using SDL_FULLSCREEN mode\n");
314             flags |= SDL_WINDOW_FULLSCREEN;
315         }
316         fs_log("setting (fullscreen) video mode %d %d\n", w, h);
317     }
318     else {
319         w = g_window_width;
320         h = g_window_height;
321 
322         fs_log("using windowed mode\n");
323         //SDL_putenv("SDL_VIDEO_WINDOW_POS=");
324         fs_log("setting (windowed) video mode %d %d\n", w, h);
325     }
326 
327     if (fs_config_get_boolean("window_border") == 0) {
328         fs_log("borderless window requested\n");
329         flags |= SDL_WINDOW_BORDERLESS;
330     }
331 
332     // special flags for command line usage
333     if (fs_config_get_boolean("window_hidden") == 1) {
334         fs_log("hidden window requested\n");
335         flags |= SDL_WINDOW_HIDDEN;
336     }
337     if (fs_config_get_boolean("window_minimized") == 1) {
338         fs_log("minimized window requested\n");
339         flags |= SDL_WINDOW_MINIMIZED;
340     }
341 
342 #if 0
343     Uint8 data[] = "\0";
344     SDL_Cursor *cursor = SDL_CreateCursor(data, data, 8, 1, 0, 0);
345     SDL_SetCursor(cursor);
346 #endif
347 
348     g_fs_ml_video_width = w;
349     g_fs_ml_video_height = h;
350     fs_log("[SDL] CreateWindow(x=%d, y=%d, w=%d, h=%d, flags=%d)\n",
351            x, y, w, h, flags);
352     g_fs_ml_window = SDL_CreateWindow(g_window_title, x, y, w, h, flags);
353 
354     int assume_refresh_rate = fs_config_get_int("assume_refresh_rate");
355     if (assume_refresh_rate != FS_CONFIG_NONE) {
356         fs_log("[DISPLAY] Assuming host refresh rate: %d Hz (from config)\n",
357                 assume_refresh_rate);
358         g_fs_emu_video_frame_rate_host = assume_refresh_rate;
359     } else {
360         SDL_DisplayMode mode;
361         if (SDL_GetWindowDisplayMode(g_fs_ml_window, &mode) == 0) {
362             g_fs_emu_video_frame_rate_host = mode.refresh_rate;
363         } else {
364             g_fs_emu_video_frame_rate_host = 0;
365         }
366         fs_log("[DISPLAY] Host refresh rate: %d Hz\n",
367                g_fs_emu_video_frame_rate_host);
368     }
369 
370     if (g_fs_emu_video_frame_rate_host) {
371         g_fs_ml_target_frame_time = 1000000 / g_fs_emu_video_frame_rate_host;
372     }
373 
374     g_fs_ml_context = SDL_GL_CreateContext(g_fs_ml_window);
375 #ifdef WITH_GLEW
376     static int glew_initialized = 0;
377     if (!glew_initialized) {
378         GLenum err = glewInit();
379         if (GLEW_OK != err) {
380           fprintf(stderr, "[GLEW] Error: %s\n", glewGetErrorString(err));
381           fs_emu_fatal("[GLEW] Error initializing glew");
382         }
383         fs_log("[GLEW] Version %s\n", glewGetString(GLEW_VERSION));
384         glew_initialized = 1;
385     }
386 #elif defined(WITH_GLAD)
387     static int glad_initialized = 0;
388     if (!glad_initialized) {
389         if (!gladLoadGLLoader((GLADloadproc) SDL_GL_GetProcAddress)) {
390             fs_emu_fatal("[GLAD] Failed to initialize OpenGL context");
391         }
392         glad_initialized = 1;
393     }
394 #endif
395     fs_ml_configure_window();
396 
397     // FIXME: this can be removed
398     g_fs_ml_opengl_context_stamp++;
399 
400     log_opengl_information();
401 }
402 
fs_ml_fullscreen(void)403 bool fs_ml_fullscreen(void)
404 {
405     /* FIXME: This can return (kind of) false answer if a fullscreen
406      * event is unprocessed in the event queue. */
407     return g_fs_emu_video_fullscreen;
408 }
409 
fs_ml_set_fullscreen(bool fullscreen)410 void fs_ml_set_fullscreen(bool fullscreen)
411 {
412     if (!is_video_thread()) {
413         if (fullscreen) {
414             fs_log("Posting enable fullscreen event\n");
415             post_video_event(FS_ML_VIDEO_EVENT_ENABLE_FULLSCREEN);
416         } else {
417             fs_log("Posting disable fullscreen event\n");
418             post_video_event(FS_ML_VIDEO_EVENT_DISABLE_FULLSCREEN);
419         }
420         return;
421     }
422 
423     if (fullscreen == g_fs_emu_video_fullscreen)
424         return;
425 
426     if (g_fs_emu_video_fullscreen_mode == FULLSCREEN_WINDOW) {
427         fs_emu_warning("Cannot toggle fullscreen with fullscreen-mode=window");
428         return;
429     }
430 
431     int display_index = 0;
432     SDL_DisplayMode mode;
433     memset(&mode, 0, sizeof(SDL_DisplayMode));
434     if (SDL_GetDesktopDisplayMode(display_index, &mode) == 0) {
435         SDL_SetWindowDisplayMode(g_fs_ml_window, &mode);
436     }
437 
438     int flags = 0;
439     if (fullscreen) {
440         if (g_fs_emu_video_fullscreen_mode == FULLSCREEN_DESKTOP)
441             flags = SDL_WINDOW_FULLSCREEN_DESKTOP;
442         else
443             flags = SDL_WINDOW_FULLSCREEN;
444     }
445     SDL_SetWindowFullscreen(g_fs_ml_window, flags);
446     g_fs_emu_video_fullscreen = fullscreen;
447 }
448 
fs_ml_toggle_fullscreen(void)449 void fs_ml_toggle_fullscreen(void)
450 {
451     if (!is_video_thread()) {
452         fs_log("Posting toggle video event\n");
453         post_video_event(FS_ML_VIDEO_EVENT_TOGGLE_FULLSCREEN);
454         return;
455     }
456     fs_ml_set_fullscreen(!fs_ml_fullscreen());
457 }
458 
459 static int g_fs_emu_monitor_count;
460 // static FSEmuMonitor g_fs_emu_monitors[FS_EMU_MONITOR_MAX_COUNT];
461 static GArray *g_fs_emu_monitors;
462 
fs_emu_monitor_compare(gconstpointer a,gconstpointer b)463 static gint fs_emu_monitor_compare(gconstpointer a, gconstpointer b)
464 {
465     FSEmuMonitor *am = (FSEmuMonitor *) a;
466     FSEmuMonitor *bm = (FSEmuMonitor *) b;
467 
468     return am->rect.x - bm->rect.x;
469 }
470 
fs_ml_video_mode_get_current(fs_ml_video_mode * mode)471 int fs_ml_video_mode_get_current(fs_ml_video_mode *mode)
472 {
473     mode->width = 0;
474     mode->height = 0;
475     mode->fps = 0;
476     mode->bpp = 0;
477     mode->flags = 0;
478 
479     FSEmuMonitor monitor;
480     fs_emu_monitor_get_by_index(g_display, &monitor);
481     mode->width = monitor.rect.w;
482     mode->height = monitor.rect.h;
483     mode->fps = monitor.refresh_rate;
484 
485     if (mode->fps == 0) {
486         fs_log("WARNING: refresh rate was not detected\n");
487         fs_log("full video sync will not be enabled automatically, but can "
488                 "be forced\n");
489     }
490     return 0;
491 }
492 
fs_emu_monitor_init()493 static void fs_emu_monitor_init()
494 {
495     static bool initialized = false;
496     if (initialized) {
497         return;
498     }
499     initialized = true;
500 
501     g_fs_emu_monitors = g_array_new(false, true, sizeof(FSEmuMonitor));
502 
503     int display_index = 0;
504     while (true) {
505         SDL_DisplayMode mode;
506         int error = SDL_GetDesktopDisplayMode(display_index, &mode);
507         if (error) {
508             break;
509         }
510 
511         FSEmuMonitor monitor;
512         monitor.index = display_index;
513         SDL_Rect rect;
514         error = SDL_GetDisplayBounds(display_index, &rect);
515         if (error) {
516             fs_log("Error retrieving display bounds for display %d: %s\n",
517                    display_index, SDL_GetError());
518             monitor.rect.x = 0;
519             monitor.rect.y = 0;
520             monitor.rect.w = 1024;
521             monitor.rect.h = 768;
522             monitor.refresh_rate = 1;
523         } else {
524             monitor.rect.x = rect.x;
525             monitor.rect.y = rect.y;
526             monitor.rect.w = rect.w;
527             monitor.rect.h = rect.h;
528             monitor.refresh_rate = mode.refresh_rate;
529         }
530         fs_log("[DISPLAY] %d: %dx%d+%d+%d @%d\n", display_index,
531                monitor.rect.w, monitor.rect.h, monitor.rect.x, monitor.rect.y,
532                monitor.refresh_rate);
533         g_array_append_val(g_fs_emu_monitors, monitor);
534         display_index += 1;
535     }
536     g_fs_emu_monitor_count = display_index;
537 
538 #if 0
539     SDL_DisplayMode mode;
540     int error = SDL_GetCurrentDisplayMode(display_index, &mode);
541     if (error) {
542         fs_log("SDL_GetCurrentDisplayMode failed\n");
543         SDL_ShowSimpleMessageBox(
544             SDL_MESSAGEBOX_ERROR, "Display Error",
545             "SDL_GetCurrentDisplayMode failed.", NULL);
546         exit(1);
547     }
548     g_fs_emu_monitor_count = SDL_GetNumVideoDisplays();
549 
550     if (g_fs_emu_monitor_count < 1) {
551         fs_log("Error %d retrieving number of displays/monitors\n",
552                g_fs_emu_monitor_count);
553         g_fs_emu_monitor_count = 1;
554     }
555     if (g_fs_emu_monitor_count >  FS_EMU_MONITOR_MAX_COUNT) {
556         fs_log("Limiting number of displays to %d\n",
557                 FS_EMU_MONITOR_MAX_COUNT);
558         g_fs_emu_monitor_count =  FS_EMU_MONITOR_MAX_COUNT;
559     }
560 
561     for (int i = 0; i < g_fs_emu_monitor_count; i++) {
562         SDL_Rect rect;
563         FSEmuMonitor monitor;
564         int error = SDL_GetDisplayBounds(i, &rect);
565         if (error) {
566             fs_log("Error retrieving display bounds for display %d: %s\n",
567                    i, SDL_GetError());
568             /* Setting dummy values on error*/
569             rect.x = 0;
570             rect.y = 0;
571             rect.w = 1024;
572             rect.h = 768;
573         }
574 
575         monitor.rect.x = rect.x;
576         monitor.rect.y = rect.y;
577         monitor.rect.w = rect.w;
578         monitor.rect.h = rect.h;
579         monitor.index = i;
580 
581         g_array_append_val(g_fs_emu_monitors, monitor);
582     }
583 #endif
584 
585     g_array_sort(g_fs_emu_monitors, fs_emu_monitor_compare);
586     for (int i = 0; i < g_fs_emu_monitor_count; i++) {
587         g_array_index(g_fs_emu_monitors, FSEmuMonitor, i).index = i;
588         /* Set physical position flags (left, m-left, m-right, right) */
589         int flags = 0;
590         for (int j = 0; j < 4; j++) {
591             int pos = (g_fs_emu_monitor_count - 1.0) * j / 3.0 + 0.5;
592             fs_log("Monitor - j %d pos %d\n", j, pos);
593             if (pos == i) {
594                 flags |= (1 << j);
595             }
596         }
597         fs_log("Monitor index %d flags %d\n", i, flags);
598         g_array_index(g_fs_emu_monitors, FSEmuMonitor, i).flags = flags;
599     }
600 }
601 
fs_emu_monitor_count()602 int fs_emu_monitor_count()
603 {
604     return g_fs_emu_monitor_count;
605 }
606 
fs_emu_monitor_get_by_index(int index,FSEmuMonitor * monitor)607 bool fs_emu_monitor_get_by_index(int index, FSEmuMonitor* monitor)
608 {
609     if (index < 0 || index >= g_fs_emu_monitor_count) {
610         monitor->index = -1;
611         monitor->flags = 0;
612         monitor->rect.x = 0;
613         monitor->rect.y = 0;
614         monitor->rect.w = 1024;
615         monitor->rect.h = 768;
616         monitor->refresh_rate = 1;
617         return false;
618     }
619     SDL_assert(monitor != NULL);
620     memcpy(monitor, &g_array_index(g_fs_emu_monitors, FSEmuMonitor, index),
621            sizeof(FSEmuMonitor));
622     return true;
623 }
624 
fs_emu_monitor_get_by_flag(int flag,FSEmuMonitor * monitor)625 bool fs_emu_monitor_get_by_flag(int flag, FSEmuMonitor* monitor)
626 {
627     for (int i = 0; i < g_fs_emu_monitor_count; i++) {
628         if ((g_array_index(g_fs_emu_monitors,
629                           FSEmuMonitor, i).flags & flag) == flag) {
630             fs_log("Monitor: found index %d for flag %d\n", i, flag);
631             return fs_emu_monitor_get_by_index(i, monitor);
632         }
633     }
634     fs_emu_monitor_get_by_index(0, monitor);
635     return false;
636 }
637 
fs_ml_video_create_window(const char * title)638 int fs_ml_video_create_window(const char *title)
639 {
640     fs_log("fs_ml_video_create_window\n");
641     g_window_title = g_strdup(title);
642 
643     g_fs_ml_keyboard_input_grab = fs_config_get_boolean(
644             "keyboard_input_grab");
645     if (g_fs_ml_automatic_input_grab == FS_CONFIG_NONE) {
646         g_fs_ml_keyboard_input_grab = 1;
647     }
648     fs_log("keyboard input grab: %d\n", g_fs_ml_keyboard_input_grab);
649 
650     static int initialized = 0;
651 
652     SDL_SetHint(SDL_HINT_GRAB_KEYBOARD,
653                 g_fs_ml_keyboard_input_grab ? "1" : "0");
654     SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
655 #ifdef WINDOWS
656     SDL_SetHint(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, "1");
657 #endif
658 
659     SDL_Init(SDL_INIT_VIDEO);
660 
661     SDL_version cversion, lversion;
662     SDL_VERSION(&cversion);
663     SDL_GetVersion(&lversion);
664     fs_log("[SDL] Version %d.%d.%d (Compiled against %d.%d.%d)\n",
665            lversion.major, lversion.minor, lversion.patch,
666            cversion.major, cversion.minor, cversion.patch);
667 
668     if (!initialized) {
669         int display_index = 0;
670         SDL_DisplayMode mode;
671         int error = SDL_GetCurrentDisplayMode(display_index, &mode);
672         if (error) {
673             fs_log("SDL_GetCurrentDisplayMode failed\n");
674             SDL_ShowSimpleMessageBox(
675                 SDL_MESSAGEBOX_ERROR, "Display Error",
676                 "SDL_GetCurrentDisplayMode failed.", NULL);
677             exit(1);
678         }
679 
680         fs_emu_monitor_init();
681 
682         const char *mon = fs_config_get_const_string("monitor");
683         int mon_flag = -1;
684         if (mon == NULL) {
685             mon = "middle-left";
686         }
687         if (strcmp(mon, "left") == 0) {
688             mon_flag = FS_EMU_MONITOR_FLAG_LEFT;
689         } else if (strcmp(mon, "middle-left") == 0) {
690             mon_flag = FS_EMU_MONITOR_FLAG_MIDDLE_LEFT;
691         } else if (strcmp(mon, "middle-right") == 0) {
692             mon_flag = FS_EMU_MONITOR_FLAG_MIDDLE_RIGHT;
693         } else if (strcmp(mon, "right") == 0) {
694             mon_flag = FS_EMU_MONITOR_FLAG_RIGHT;
695         }
696         else {
697             mon_flag = FS_EMU_MONITOR_FLAG_MIDDLE_LEFT;
698         }
699         FSEmuMonitor monitor;
700         fs_emu_monitor_get_by_flag(mon_flag, &monitor);
701         fs_log("Monitor \"%s\" (flag %d) => index %d\n",
702                mon, mon_flag, monitor.index);
703         g_display = monitor.index;
704 
705         g_fullscreen_width = fs_config_get_int("fullscreen_width");
706         if (g_fullscreen_width == FS_CONFIG_NONE) {
707             g_fullscreen_width = mode.w;
708         }
709         g_fullscreen_height = fs_config_get_int("fullscreen_height");
710         if (g_fullscreen_height == FS_CONFIG_NONE) {
711             g_fullscreen_height = mode.h;
712         }
713 
714         if (g_fs_emu_video_fullscreen_mode_string == NULL) {
715             g_fs_emu_video_fullscreen_mode = -1;
716         }
717         else if (g_ascii_strcasecmp(g_fs_emu_video_fullscreen_mode_string,
718                 "window") == 0) {
719             g_fs_emu_video_fullscreen_mode = FULLSCREEN_WINDOW;
720         }
721         else if (g_ascii_strcasecmp(g_fs_emu_video_fullscreen_mode_string,
722                 "fullscreen") == 0) {
723             g_fs_emu_video_fullscreen_mode = FULLSCREEN_FULLSCREEN;
724         }
725         else if (g_ascii_strcasecmp(g_fs_emu_video_fullscreen_mode_string,
726                 "desktop") == 0) {
727             g_fs_emu_video_fullscreen_mode = FULLSCREEN_DESKTOP;
728         }
729         if (g_fs_emu_video_fullscreen_mode == -1) {
730 #ifdef MACOSX
731             g_fs_emu_video_fullscreen_mode = FULLSCREEN_FULLSCREEN;
732 #else
733             g_fs_emu_video_fullscreen_mode = FULLSCREEN_FULLSCREEN;
734 #endif
735             fs_log("[SDL] Defaulting to fullscreen_mode = desktop for SDL 2\n");
736             g_fs_emu_video_fullscreen_mode = FULLSCREEN_DESKTOP;
737         }
738 
739         initialized = 1;
740     }
741 
742     if (g_fs_ml_video_sync) {
743         g_fs_ml_vblank_sync = 1;
744     }
745 
746     SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
747 
748     if (g_fsaa) {
749         fs_log("setting FSAA samples to %d\n", g_fsaa);
750         SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
751         SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, g_fsaa);
752     }
753 
754     g_window_width = fs_config_get_int("window_width");
755     if (g_window_width == FS_CONFIG_NONE) {
756         g_window_width = 1920 / 2;
757     }
758     g_window_height = fs_config_get_int("window_height");
759     if (g_window_height == FS_CONFIG_NONE) {
760         g_window_height = 1080/ 2;
761     }
762     g_window_x = fs_config_get_int("window_x");
763     if (g_window_x == FS_CONFIG_NONE) {
764         g_window_x = SDL_WINDOWPOS_CENTERED;
765     }
766     g_window_y = fs_config_get_int("window_y");
767     if (g_window_y == FS_CONFIG_NONE) {
768         g_window_y = SDL_WINDOWPOS_CENTERED;
769     }
770     g_window_resizable = fs_config_get_boolean("window_resizable");
771     if (g_window_resizable == FS_CONFIG_NONE) {
772         g_window_resizable = 1;
773     }
774 
775     g_fs_ml_automatic_input_grab = fs_config_get_boolean(
776             "automatic_input_grab");
777     if (g_fs_ml_automatic_input_grab == FS_CONFIG_NONE) {
778         if (fs_ml_mouse_integration()) {
779             g_fs_ml_automatic_input_grab = 0;
780         } else {
781             g_fs_ml_automatic_input_grab = 1;
782         }
783     }
784     fs_log("automatic input grab: %d\n", g_fs_ml_automatic_input_grab);
785 
786     g_initial_input_grab = g_fs_ml_automatic_input_grab;
787     if (fs_config_get_boolean("initial_input_grab") == 1) {
788         g_initial_input_grab = 1;
789     }
790     else if (fs_config_get_boolean("initial_input_grab") == 0 ||
791             // deprecated names:
792             fs_config_get_boolean("input_grab") == 0 ||
793             fs_config_get_boolean("grab_input") == 0) {
794         g_initial_input_grab = 0;
795     }
796 
797     set_video_mode();
798 
799     if (g_fs_ml_vblank_sync) {
800         fs_emu_log("*** Setting swap interval to 1 ***\n");
801         if (SDL_GL_SetSwapInterval(1) != 0) {
802             fs_emu_warning("SDL_GL_SetSwapInterval(1) failed");
803         }
804     }
805     else {
806         fs_emu_log("*** Setting swap interval to 0 ***\n");
807         SDL_GL_SetSwapInterval(0);
808     }
809 
810     fs_log("initial input grab: %d\n", g_initial_input_grab);
811     if (g_initial_input_grab && !g_has_input_grab) {
812         fs_ml_set_input_grab(true);
813     }
814     fs_ml_show_cursor(0, 1);
815 
816     /* This looks a bit peculiar, but it helps to show the window in
817      * fullscreen mode as soon as possible to reduce flickering,
818        at least under GNOME 3. */
819     glClearColor(0.0, 0.0, 0.0, 1.0);
820     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
821     SDL_GL_SwapWindow(g_fs_ml_window);
822     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
823     SDL_GL_SwapWindow(g_fs_ml_window);
824     int64_t start_time = fs_emu_monotonic_time();
825     SDL_Event event;
826     while (fs_emu_monotonic_time() - start_time < 100 * 1000) {
827         SDL_WaitEventTimeout(&event, 10);
828         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
829         SDL_GL_SwapWindow(g_fs_ml_window);
830     }
831 
832     // this function must be called from the video thread
833     fs_log("init_opengl\n");
834     fse_init_video_opengl();
835 
836     SDL_StartTextInput();
837 
838 #ifdef WINDOWS
839     if (!fs_config_false(OPTION_RAW_INPUT)) {
840         fs_ml_init_raw_input();
841     }
842 #endif
843 
844     fs_log("create windows is done\n");
845     return 1;
846 }
847 
848 int g_fs_ml_running = 1;
849 
850 #ifndef WINDOWS
851 // using separate implementation on Windows with raw input
fs_ml_clear_keyboard_modifier_state()852 void fs_ml_clear_keyboard_modifier_state()
853 {
854 
855 }
856 #endif
857 
858 #include "sdl2_keys.h"
859 // modifiers have values in SDL and SDL2, except META is renamed to GUI
860 #define KMOD_LMETA KMOD_LGUI
861 #define KMOD_RMETA KMOD_RGUI
862 #define KMOD_META (KMOD_LMETA|KMOD_RMETA)
863 
on_resize(int width,int height)864 static void on_resize(int width, int height)
865 {
866     if (width == g_fs_ml_video_width && height == g_fs_ml_video_height) {
867         fs_log("got resize event, but size was unchanged\n");
868         return;
869     }
870     if (g_fs_emu_video_fullscreen) {
871         fs_log("not updating window size in fullscreen\n");
872     }
873     else if (width == g_fullscreen_width &&
874         height == g_fullscreen_height) {
875         fs_log("not setting window size to fullscreen size\n");
876     }
877     else {
878         g_window_width = width;
879         g_window_height = height;
880         fs_log("resize event %d %d\n", width, height);
881     }
882     g_fs_ml_video_width = width;
883     g_fs_ml_video_height = height;
884 }
885 
fs_ml_event_loop(void)886 int fs_ml_event_loop(void)
887 {
888     // printf("fs_ml_event_loop\n");
889     int result = 0;
890     SDL_Event event;
891     while (SDL_PollEvent(&event)) {
892         switch(event.type) {
893         case SDL_QUIT:
894             fs_log("Received SDL_QUIT\n");
895             fs_ml_maybe_quit();
896 #ifdef FSE_DRIVERS
897             printf("returning 1 from fs_ml_event_loop\n");
898             result = 1;
899 #endif
900             continue;
901         case SDL_WINDOWEVENT:
902             if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
903                 on_resize(event.window.data1, event.window.data2);
904             } else if (event.window.event == SDL_WINDOWEVENT_CLOSE) {
905                 event.type = SDL_QUIT;
906                 SDL_PushEvent(&event);
907             } else if (event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) {
908                 if (g_grab_input_on_activate) {
909                     fs_log("Window focus gained - grabbing input\n");
910                     g_grab_input_on_activate = false;
911                     fs_ml_set_input_grab(true);
912 #ifdef MACOSX
913                 } else if (fs_ml_input_grab()) {
914                     /* Input grab could be "lost" due to Cmd+Tab */
915                     fs_log("[INPUT] Forcing re-grab of input on macOS\n");
916                     fs_ml_set_input_grab(false);
917                     fs_ml_set_input_grab(true);
918 #endif
919                 }
920             }
921             continue;
922         case SDL_KEYDOWN:
923         case SDL_KEYUP:
924             if (g_fs_log_input) {
925                 fs_log("SDL key sym %d mod %d scancode %d state %d repeat %d\n",
926                         event.key.keysym.sym, event.key.keysym.mod,
927                         event.key.keysym.scancode, event.key.state,
928                        event.key.repeat);
929             }
930             if (event.key.repeat) {
931                 continue;
932             }
933             if (event.key.keysym.sym == 0 && event.key.keysym.scancode == 0) {
934                 /* ignore "ghost key" seen on OS X which without this
935                  * specific check will cause the A key to be mysteriously
936                  * pressed. */
937                 if (g_fs_log_input) {
938                     fs_log("- ignored key with keysym 0 and scancode 0\n");
939                 }
940                 continue;
941             }
942             /*
943             if (event.key.keysym.sym == SDLK_F12) {
944                 g_f12_state = event.key.state ? FS_ML_KEY_MOD_F12 : 0;
945                 printf("-- g_f12_state is %d\n", g_f12_state);
946             }
947             else if (event.key.keysym.sym == SDLK_F11) {
948                 g_f11_state = event.key.state ? FS_ML_KEY_MOD_F11 : 0;
949             }
950             */
951 
952             const Uint8* key_state;
953             int num_keys;
954             key_state = SDL_GetKeyboardState(&num_keys);
955             g_f11_state = key_state[SDL_SCANCODE_F11] ? FS_ML_KEY_MOD_F11 : 0;
956             g_f12_state = key_state[SDL_SCANCODE_F12] ? FS_ML_KEY_MOD_F12 : 0;
957 
958             int key = -1;
959             if (event.key.keysym.scancode <= LAST_SDL2_SCANCODE) {
960                 key = g_sdl2_keys[event.key.keysym.scancode];
961             }
962 #if defined(MACOSX)
963 #elif defined(WINDOWS)
964 #else
965             else if (event.key.keysym.sym == SDLK_MODE) {
966                 key = SDLK_RALT;
967             }
968 #endif
969             else {
970                 key = fs_ml_scancode_to_key(event.key.keysym.scancode);
971             }
972 
973 #ifdef USE_SDL2
974             if (0) {
975                 // the below trick does not currently work for SDL2, as
976                 // there is no mapping yet for translated keys
977             }
978 #else
979             if (g_f12_state || g_f11_state) {
980                 // leave translated key code in keysym
981             }
982 #endif
983             else if (key >= 0) {
984                 if (g_fs_log_input) {
985                     fs_log("- key code set to %d (was %d) based on "
986                            "scancode %d\n", key, event.key.keysym.sym,
987                            event.key.keysym.scancode);
988                 }
989                 event.key.keysym.sym = key;
990             }
991 
992             int mod = event.key.keysym.mod;
993             if (mod & KMOD_LSHIFT || mod & KMOD_RSHIFT)
994                 event.key.keysym.mod |= KMOD_SHIFT;
995 #if 0
996             if (mod & KMOD_LALT || mod & KMOD_RALT)
997                 event.key.keysym.mod |= KMOD_ALT;
998 #endif
999             if (mod & KMOD_LCTRL || mod & KMOD_RCTRL)
1000                 event.key.keysym.mod |= KMOD_CTRL;
1001 #if 0
1002             if (mod & KMOD_LMETA || mod & KMOD_RMETA)
1003                 event.key.keysym.mod |= KMOD_META;
1004 #endif
1005 
1006             /* Filter out other modidifers */
1007             event.key.keysym.mod &=
1008                         KMOD_SHIFT | KMOD_ALT | KMOD_CTRL | KMOD_META;
1009             /* Add F11/F12 modifier state */
1010             event.key.keysym.mod |= g_f11_state | g_f12_state;
1011 
1012             //printf("%d %d %d %d\n", event.key.keysym.mod,
1013             //        KMOD_ALT, KMOD_LALT, KMOD_RALT);
1014             break;
1015         //case SDL_MOUSEBUTTONDOWN:
1016         //    printf("--- mousebutton down ---\n");
1017         }
1018         fs_ml_event *new_event = NULL;
1019 
1020         if (event.type == SDL_KEYDOWN) {
1021             new_event = fs_ml_alloc_event();
1022             new_event->type = FS_ML_KEYDOWN;
1023             new_event->key.keysym.sym = event.key.keysym.sym;
1024             new_event->key.keysym.mod = event.key.keysym.mod;
1025             new_event->key.state = event.key.state;
1026         }
1027         else if (event.type == SDL_KEYUP) {
1028             new_event = fs_ml_alloc_event();
1029             new_event->type = FS_ML_KEYUP;
1030             new_event->key.keysym.sym = event.key.keysym.sym;
1031             new_event->key.keysym.mod = event.key.keysym.mod;
1032             new_event->key.state = event.key.state;
1033         }
1034         else if (event.type == SDL_JOYBUTTONDOWN) {
1035             if (g_fs_log_input) {
1036                 fs_log("SDL_JOYBUTTONDOWN which %d button %d state %d\n",
1037                        event.jbutton.which, event.jbutton.button,
1038                        event.jbutton.state);
1039             }
1040             new_event = fs_ml_alloc_event();
1041             new_event->type = FS_ML_JOYBUTTONDOWN;
1042             new_event->jbutton.which = \
1043                     g_fs_ml_sdl_joystick_index_map[event.jbutton.which];
1044             new_event->jbutton.button = event.jbutton.button;
1045             new_event->jbutton.state = event.jbutton.state;
1046         }
1047         else if (event.type == SDL_JOYBUTTONUP) {
1048             if (g_fs_log_input) {
1049                 fs_log("SDL_JOYBUTTONUP which %d button %d state %d\n",
1050                        event.jbutton.which, event.jbutton.button,
1051                        event.jbutton.state);
1052             }
1053             new_event = fs_ml_alloc_event();
1054             new_event->type = FS_ML_JOYBUTTONUP;
1055             new_event->jbutton.which = \
1056                     g_fs_ml_sdl_joystick_index_map[event.jbutton.which];
1057             new_event->jbutton.button = event.jbutton.button;
1058             new_event->jbutton.state = event.jbutton.state;
1059         }
1060         else if (event.type == SDL_JOYAXISMOTION) {
1061             /* Not logging axis motion, too much noise */
1062             new_event = fs_ml_alloc_event();
1063             new_event->type = FS_ML_JOYAXISMOTION;
1064             new_event->jaxis.which = \
1065                     g_fs_ml_sdl_joystick_index_map[event.jaxis.which];
1066             new_event->jaxis.axis = event.jaxis.axis;
1067             new_event->jaxis.value = event.jaxis.value;
1068         }
1069         else if (event.type == SDL_JOYHATMOTION) {
1070             if (g_fs_log_input) {
1071                 fs_log("SDL_JOYHATMOTION which %d hat %d value %d\n",
1072                        event.jhat.which, event.jhat.hat, event.jhat.value);
1073             }
1074             new_event = fs_ml_alloc_event();
1075             new_event->type = FS_ML_JOYHATMOTION;
1076             new_event->jhat.which = \
1077                     g_fs_ml_sdl_joystick_index_map[event.jhat.which];
1078             new_event->jhat.hat = event.jhat.hat;
1079             new_event->jhat.value = event.jhat.value;
1080         }
1081         else if (event.type == SDL_MOUSEMOTION) {
1082             new_event = fs_ml_alloc_event();
1083             new_event->type = FS_ML_MOUSEMOTION;
1084             new_event->motion.device = g_fs_ml_first_mouse_index;
1085             new_event->motion.xrel = event.motion.xrel;
1086             new_event->motion.yrel = event.motion.yrel;
1087             /* Absolute window coordinates */
1088             new_event->motion.x = event.motion.x;
1089             new_event->motion.y = event.motion.y;
1090             //printf("ISREL %d\n", SDL_GetRelativeMouseMode());
1091 
1092             if (g_fs_log_input) {
1093                 fs_log("SDL mouse event x: %4d y: %4d xrel: %4d yrel: %4d\n",
1094                     event.motion.x, event.motion.y,
1095                     event.motion.xrel, event.motion.yrel);
1096             }
1097         }
1098         else if (event.type == SDL_MOUSEBUTTONDOWN) {
1099             new_event = fs_ml_alloc_event();
1100             new_event->type = FS_ML_MOUSEBUTTONDOWN;
1101             new_event->button.device = g_fs_ml_first_mouse_index;
1102             new_event->button.button = event.button.button;
1103 #ifdef MACOSX
1104             if (new_event->button.button == 1) {
1105                 int mod = SDL_GetModState();
1106                 if (mod & KMOD_ALT) {
1107                     new_event->button.button = 2;
1108                 }
1109                 else if (mod & KMOD_CTRL) {
1110                     new_event->button.button = 3;
1111                 }
1112             }
1113 #endif
1114             new_event->button.state = event.button.state;
1115         }
1116         else if (event.type == SDL_MOUSEBUTTONUP) {
1117             new_event = fs_ml_alloc_event();
1118             new_event->type = FS_ML_MOUSEBUTTONUP;
1119             new_event->button.device = g_fs_ml_first_mouse_index;
1120             new_event->button.button = event.button.button;
1121 #ifdef MACOSX
1122             if (new_event->button.button == 1) {
1123                 int mod = SDL_GetModState();
1124                 if (mod & KMOD_ALT) {
1125                     new_event->button.button = 2;
1126                 }
1127                 else if (mod & KMOD_CTRL) {
1128                     new_event->button.button = 3;
1129                 }
1130             }
1131 #endif
1132             new_event->button.state = event.button.state;
1133         }
1134         else if (event.type == SDL_MOUSEWHEEL) {
1135             /*
1136             if (event.wheel.which == SDL_TOUCH_MOUSEID) {
1137 
1138             }
1139             */
1140             if (event.wheel.y) {
1141                 if (g_fs_log_input) {
1142                     fs_log("SDL mouse event y-scroll: %4d\n",
1143                         event.wheel.y);
1144                 }
1145                 new_event = fs_ml_alloc_event();
1146                 new_event->type = FS_ML_MOUSEBUTTONDOWN;
1147                 if (event.wheel.y > 0) {
1148                     new_event->button.button = FS_ML_BUTTON_WHEELUP;
1149                 }
1150                 else {
1151                     new_event->button.button = FS_ML_BUTTON_WHEELDOWN;
1152                 }
1153                 new_event->button.device = g_fs_ml_first_mouse_index;
1154                 new_event->button.state = 1;
1155             }
1156         }
1157         else if (event.type == SDL_TEXTINPUT) {
1158             new_event = fs_ml_alloc_event();
1159             new_event->type = FS_ML_TEXTINPUT;
1160             memcpy(&(new_event->text.text), &(event.text.text),
1161                    MIN(TEXTINPUTEVENT_TEXT_SIZE, SDL_TEXTINPUTEVENT_TEXT_SIZE));
1162             new_event->text.text[TEXTINPUTEVENT_TEXT_SIZE - 1] = 0;
1163         }
1164 
1165         if (new_event) {
1166             fs_ml_post_event(new_event);
1167         }
1168     }
1169     return result;
1170 }
1171 
fs_ml_video_swap_buffers()1172 void fs_ml_video_swap_buffers()
1173 {
1174     SDL_GL_SwapWindow(g_fs_ml_window);
1175 }
1176 
post_main_loop(void)1177 static void post_main_loop(void)
1178 {
1179     /* We want to improve the transitioning from FS-UAE back to e.g.
1180      * FS-UAE Game Center - avoid blinking cursor - so we try to move it (to
1181      * the bottom right of the screen). This probably requires that the
1182      * cursor is not grabbed (SDL often keeps the cursor in the center of the
1183      * screen then). */
1184     if (g_fs_emu_video_fullscreen) {
1185         if (SDL_getenv("FSGS_RETURN_CURSOR_TO") &&
1186                 SDL_getenv("FSGS_RETURN_CURSOR_TO")[0]) {
1187             int x = -1; int y = -1;
1188             sscanf(SDL_getenv("FSGS_RETURN_CURSOR_TO"), "%d,%d", &x, &y);
1189             if (x != -1 && y != -1) {
1190 #if 0
1191                 fs_log("trying to move mouse cursor to x=%d y=%d\n", x, y);
1192 #endif
1193                 Uint8 data[] = "\0";
1194                 SDL_SetWindowGrab(g_fs_ml_window, SDL_FALSE);
1195                 /* Setting invisible cursor so we won't see it when we
1196                  * enable the cursor in order to move it. */
1197                 SDL_Cursor *cursor = SDL_CreateCursor(data, data, 8, 1, 0, 0);
1198                 SDL_SetCursor(cursor);
1199                 SDL_ShowCursor(SDL_ENABLE);
1200                 SDL_WarpMouseInWindow(g_fs_ml_window, x, y);
1201             }
1202         }
1203     }
1204 }
1205 
fs_ml_main_loop(void)1206 int fs_ml_main_loop(void)
1207 {
1208     while (g_fs_ml_running) {
1209         fs_ml_event_loop();
1210         process_video_events();
1211         fs_ml_prevent_power_saving();
1212         fs_ml_render_iteration();
1213     }
1214     post_main_loop();
1215     return 0;
1216 }
1217 
fs_ml_video_init()1218 void fs_ml_video_init()
1219 {
1220     FS_ML_INIT_ONCE;
1221 
1222     g_video_thread_id = fs_thread_id();
1223     g_video_cond = fs_condition_create();
1224     g_video_mutex = fs_mutex_create();
1225     g_video_event_queue = g_queue_new();
1226     g_video_event_mutex = fs_mutex_create();
1227 
1228     fs_ml_render_init();
1229 }
1230 
1231 #endif
1232