1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2007 Blender Foundation but based
17  * on ghostwinlay.c (C) 2001-2002 by NaN Holding BV
18  * All rights reserved.
19  */
20 
21 /** \file
22  * \ingroup wm
23  *
24  * Window management, wrap GHOST.
25  */
26 
27 #include <math.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 
32 #include "DNA_listBase.h"
33 #include "DNA_screen_types.h"
34 #include "DNA_windowmanager_types.h"
35 #include "DNA_workspace_types.h"
36 
37 #include "MEM_guardedalloc.h"
38 
39 #include "GHOST_C-api.h"
40 
41 #include "BLI_blenlib.h"
42 #include "BLI_math.h"
43 #include "BLI_utildefines.h"
44 
45 #include "BLT_translation.h"
46 
47 #include "BKE_context.h"
48 #include "BKE_global.h"
49 #include "BKE_icons.h"
50 #include "BKE_layer.h"
51 #include "BKE_main.h"
52 #include "BKE_screen.h"
53 #include "BKE_workspace.h"
54 
55 #include "RNA_access.h"
56 #include "RNA_define.h"
57 #include "RNA_enum_types.h"
58 
59 #include "WM_api.h"
60 #include "WM_types.h"
61 #include "wm.h"
62 #include "wm_draw.h"
63 #include "wm_event_system.h"
64 #include "wm_files.h"
65 #include "wm_platform_support.h"
66 #include "wm_window.h"
67 #include "wm_window_private.h"
68 #ifdef WITH_XR_OPENXR
69 #  include "wm_xr.h"
70 #endif
71 
72 #include "ED_anim_api.h"
73 #include "ED_fileselect.h"
74 #include "ED_render.h"
75 #include "ED_scene.h"
76 #include "ED_screen.h"
77 
78 #include "UI_interface.h"
79 #include "UI_interface_icons.h"
80 
81 #include "PIL_time.h"
82 
83 #include "BLF_api.h"
84 #include "GPU_batch.h"
85 #include "GPU_batch_presets.h"
86 #include "GPU_context.h"
87 #include "GPU_framebuffer.h"
88 #include "GPU_immediate.h"
89 #include "GPU_init_exit.h"
90 #include "GPU_platform.h"
91 #include "GPU_state.h"
92 #include "GPU_texture.h"
93 
94 #include "UI_resources.h"
95 
96 /* for assert */
97 #ifndef NDEBUG
98 #  include "BLI_threads.h"
99 #endif
100 
101 /* the global to talk to ghost */
102 static GHOST_SystemHandle g_system = NULL;
103 
104 typedef enum eWinOverrideFlag {
105   WIN_OVERRIDE_GEOM = (1 << 0),
106   WIN_OVERRIDE_WINSTATE = (1 << 1),
107 } eWinOverrideFlag;
108 
109 #define GHOST_WINDOW_STATE_DEFAULT GHOST_kWindowStateMaximized
110 
111 /**
112  * Override defaults or startup file when #eWinOverrideFlag is set.
113  * These values are typically set by command line arguments.
114  */
115 static struct WMInitStruct {
116   /* window geometry */
117   int size_x, size_y;
118   int start_x, start_y;
119 
120   int windowstate;
121   eWinOverrideFlag override_flag;
122 
123   bool window_focus;
124   bool native_pixels;
125 } wm_init_state = {
126     .windowstate = GHOST_WINDOW_STATE_DEFAULT,
127     .window_focus = true,
128     .native_pixels = true,
129 };
130 
131 /* -------------------------------------------------------------------- */
132 /** \name Window Open & Close
133  * \{ */
134 
135 static void wm_window_set_drawable(wmWindowManager *wm, wmWindow *win, bool activate);
136 static int wm_window_timer(const bContext *C);
137 
138 /* XXX this one should correctly check for apple top header...
139  * done for Cocoa : returns window contents (and not frame) max size*/
wm_get_screensize(int * r_width,int * r_height)140 void wm_get_screensize(int *r_width, int *r_height)
141 {
142   unsigned int uiwidth;
143   unsigned int uiheight;
144 
145   GHOST_GetMainDisplayDimensions(g_system, &uiwidth, &uiheight);
146   *r_width = uiwidth;
147   *r_height = uiheight;
148 }
149 
150 /* size of all screens (desktop), useful since the mouse is bound by this */
wm_get_desktopsize(int * r_width,int * r_height)151 void wm_get_desktopsize(int *r_width, int *r_height)
152 {
153   unsigned int uiwidth;
154   unsigned int uiheight;
155 
156   GHOST_GetAllDisplayDimensions(g_system, &uiwidth, &uiheight);
157   *r_width = uiwidth;
158   *r_height = uiheight;
159 }
160 
161 /* keeps offset and size within monitor bounds */
162 /* XXX solve dual screen... */
wm_window_check_position(rcti * rect)163 static void wm_window_check_position(rcti *rect)
164 {
165   int width, height;
166   wm_get_screensize(&width, &height);
167 
168   if (rect->xmin < 0) {
169     rect->xmax -= rect->xmin;
170     rect->xmin = 0;
171   }
172   if (rect->ymin < 0) {
173     rect->ymax -= rect->ymin;
174     rect->ymin = 0;
175   }
176   if (rect->xmax > width) {
177     int d = rect->xmax - width;
178     rect->xmax -= d;
179     rect->xmin -= d;
180   }
181   if (rect->ymax > height) {
182     int d = rect->ymax - height;
183     rect->ymax -= d;
184     rect->ymin -= d;
185   }
186 
187   if (rect->xmin < 0) {
188     rect->xmin = 0;
189   }
190   if (rect->ymin < 0) {
191     rect->ymin = 0;
192   }
193 }
194 
wm_ghostwindow_destroy(wmWindowManager * wm,wmWindow * win)195 static void wm_ghostwindow_destroy(wmWindowManager *wm, wmWindow *win)
196 {
197   if (win->ghostwin) {
198     /* Prevents non-drawable state of main windows (bugs T22967,
199      * T25071 and possibly T22477 too). Always clear it even if
200      * this window was not the drawable one, because we mess with
201      * drawing context to discard the GW context. */
202     wm_window_clear_drawable(wm);
203 
204     if (win == wm->winactive) {
205       wm->winactive = NULL;
206     }
207 
208     /* We need this window's opengl context active to discard it. */
209     GHOST_ActivateWindowDrawingContext(win->ghostwin);
210     GPU_context_active_set(win->gpuctx);
211 
212     /* Delete local gpu context.  */
213     GPU_context_discard(win->gpuctx);
214 
215     GHOST_DisposeWindow(g_system, win->ghostwin);
216     win->ghostwin = NULL;
217     win->gpuctx = NULL;
218   }
219 }
220 
221 /* including window itself, C can be NULL.
222  * ED_screen_exit should have been called */
wm_window_free(bContext * C,wmWindowManager * wm,wmWindow * win)223 void wm_window_free(bContext *C, wmWindowManager *wm, wmWindow *win)
224 {
225   /* update context */
226   if (C) {
227     WM_event_remove_handlers(C, &win->handlers);
228     WM_event_remove_handlers(C, &win->modalhandlers);
229 
230     if (CTX_wm_window(C) == win) {
231       CTX_wm_window_set(C, NULL);
232     }
233   }
234 
235   BKE_screen_area_map_free(&win->global_areas);
236 
237   /* end running jobs, a job end also removes its timer */
238   LISTBASE_FOREACH_MUTABLE (wmTimer *, wt, &wm->timers) {
239     if (wt->win == win && wt->event_type == TIMERJOBS) {
240       wm_jobs_timer_ended(wm, wt);
241     }
242   }
243 
244   /* timer removing, need to call this api function */
245   LISTBASE_FOREACH_MUTABLE (wmTimer *, wt, &wm->timers) {
246     if (wt->win == win) {
247       WM_event_remove_timer(wm, win, wt);
248     }
249   }
250 
251   if (win->eventstate) {
252     MEM_freeN(win->eventstate);
253   }
254 
255   if (win->cursor_keymap_status) {
256     MEM_freeN(win->cursor_keymap_status);
257   }
258 
259   WM_gestures_free_all(win);
260 
261   wm_event_free_all(win);
262 
263   wm_ghostwindow_destroy(wm, win);
264 
265   BKE_workspace_instance_hook_free(G_MAIN, win->workspace_hook);
266   MEM_freeN(win->stereo3d_format);
267 
268   MEM_freeN(win);
269 }
270 
find_free_winid(wmWindowManager * wm)271 static int find_free_winid(wmWindowManager *wm)
272 {
273   int id = 1;
274 
275   LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
276     if (id <= win->winid) {
277       id = win->winid + 1;
278     }
279   }
280   return id;
281 }
282 
283 /* don't change context itself */
wm_window_new(const Main * bmain,wmWindowManager * wm,wmWindow * parent,bool dialog)284 wmWindow *wm_window_new(const Main *bmain, wmWindowManager *wm, wmWindow *parent, bool dialog)
285 {
286   wmWindow *win = MEM_callocN(sizeof(wmWindow), "window");
287 
288   BLI_addtail(&wm->windows, win);
289   win->winid = find_free_winid(wm);
290 
291   /* Dialogs may have a child window as parent. Otherwise, a child must not be a parent too. */
292   win->parent = (!dialog && parent && parent->parent) ? parent->parent : parent;
293   win->stereo3d_format = MEM_callocN(sizeof(Stereo3dFormat), "Stereo 3D Format (window)");
294   win->workspace_hook = BKE_workspace_instance_hook_create(bmain, win->winid);
295 
296   return win;
297 }
298 
299 /* part of wm_window.c api */
wm_window_copy(Main * bmain,wmWindowManager * wm,wmWindow * win_src,const bool duplicate_layout,const bool child)300 wmWindow *wm_window_copy(Main *bmain,
301                          wmWindowManager *wm,
302                          wmWindow *win_src,
303                          const bool duplicate_layout,
304                          const bool child)
305 {
306   const bool is_dialog = GHOST_IsDialogWindow(win_src->ghostwin);
307   wmWindow *win_parent = (child) ? win_src : win_src->parent;
308   wmWindow *win_dst = wm_window_new(bmain, wm, win_parent, is_dialog);
309   WorkSpace *workspace = WM_window_get_active_workspace(win_src);
310   WorkSpaceLayout *layout_old = WM_window_get_active_layout(win_src);
311 
312   win_dst->posx = win_src->posx + 10;
313   win_dst->posy = win_src->posy;
314   win_dst->sizex = win_src->sizex;
315   win_dst->sizey = win_src->sizey;
316 
317   win_dst->scene = win_src->scene;
318   STRNCPY(win_dst->view_layer_name, win_src->view_layer_name);
319   BKE_workspace_active_set(win_dst->workspace_hook, workspace);
320   WorkSpaceLayout *layout_new = duplicate_layout ? ED_workspace_layout_duplicate(
321                                                        bmain, workspace, layout_old, win_dst) :
322                                                    layout_old;
323   BKE_workspace_active_layout_set(win_dst->workspace_hook, win_dst->winid, workspace, layout_new);
324 
325   *win_dst->stereo3d_format = *win_src->stereo3d_format;
326 
327   return win_dst;
328 }
329 
330 /**
331  * A higher level version of copy that tests the new window can be added.
332  * (called from the operator directly)
333  */
wm_window_copy_test(bContext * C,wmWindow * win_src,const bool duplicate_layout,const bool child)334 wmWindow *wm_window_copy_test(bContext *C,
335                               wmWindow *win_src,
336                               const bool duplicate_layout,
337                               const bool child)
338 {
339   Main *bmain = CTX_data_main(C);
340   wmWindowManager *wm = CTX_wm_manager(C);
341 
342   wmWindow *win_dst = wm_window_copy(bmain, wm, win_src, duplicate_layout, child);
343 
344   WM_check(C);
345 
346   if (win_dst->ghostwin) {
347     WM_event_add_notifier_ex(wm, CTX_wm_window(C), NC_WINDOW | NA_ADDED, NULL);
348     return win_dst;
349   }
350   wm_window_close(C, wm, win_dst);
351   return NULL;
352 }
353 
354 /** \} */
355 
356 /* -------------------------------------------------------------------- */
357 /** \name Quit Confirmation Dialog
358  * \{ */
359 
wm_save_file_on_quit_dialog_callback(bContext * C,void * UNUSED (user_data))360 static void wm_save_file_on_quit_dialog_callback(bContext *C, void *UNUSED(user_data))
361 {
362   wm_exit_schedule_delayed(C);
363 }
364 
365 /**
366  * Call the confirm dialog on quitting. It's displayed in the context window so
367  * caller should set it as desired.
368  */
wm_confirm_quit(bContext * C)369 static void wm_confirm_quit(bContext *C)
370 {
371   wmGenericCallback *action = MEM_callocN(sizeof(*action), __func__);
372   action->exec = wm_save_file_on_quit_dialog_callback;
373   wm_close_file_dialog(C, action);
374 }
375 
376 /**
377  * Call the quit confirmation prompt or exit directly if needed. The use can
378  * still cancel via the confirmation popup. Also, this may not quit Blender
379  * immediately, but rather schedule the closing.
380  *
381  * \param win: The window to show the confirmation popup/window in.
382  */
wm_quit_with_optional_confirmation_prompt(bContext * C,wmWindow * win)383 void wm_quit_with_optional_confirmation_prompt(bContext *C, wmWindow *win)
384 {
385   wmWindow *win_ctx = CTX_wm_window(C);
386 
387   /* The popup will be displayed in the context window which may not be set
388    * here (this function gets called outside of normal event handling loop). */
389   CTX_wm_window_set(C, win);
390 
391   if (U.uiflag & USER_SAVE_PROMPT) {
392     if (wm_file_or_image_is_modified(CTX_data_main(C), CTX_wm_manager(C)) && !G.background) {
393       wm_window_raise(win);
394       wm_confirm_quit(C);
395     }
396     else {
397       wm_exit_schedule_delayed(C);
398     }
399   }
400   else {
401     wm_exit_schedule_delayed(C);
402   }
403 
404   CTX_wm_window_set(C, win_ctx);
405 }
406 
407 /** \} */
408 
409 /* this is event from ghost, or exit-blender op */
wm_window_close(bContext * C,wmWindowManager * wm,wmWindow * win)410 void wm_window_close(bContext *C, wmWindowManager *wm, wmWindow *win)
411 {
412   wmWindow *win_other;
413 
414   /* First check if there is another main window remaining. */
415   for (win_other = wm->windows.first; win_other; win_other = win_other->next) {
416     if (win_other != win && win_other->parent == NULL && !WM_window_is_temp_screen(win_other)) {
417       break;
418     }
419   }
420 
421   if (win->parent == NULL && win_other == NULL) {
422     wm_quit_with_optional_confirmation_prompt(C, win);
423     return;
424   }
425 
426   /* Close child windows */
427   LISTBASE_FOREACH_MUTABLE (wmWindow *, iter_win, &wm->windows) {
428     if (iter_win->parent == win) {
429       wm_window_close(C, wm, iter_win);
430     }
431   }
432 
433   bScreen *screen = WM_window_get_active_screen(win);
434   WorkSpace *workspace = WM_window_get_active_workspace(win);
435   WorkSpaceLayout *layout = BKE_workspace_active_layout_get(win->workspace_hook);
436 
437   BLI_remlink(&wm->windows, win);
438 
439   CTX_wm_window_set(C, win); /* needed by handlers */
440   WM_event_remove_handlers(C, &win->handlers);
441   WM_event_remove_handlers(C, &win->modalhandlers);
442 
443   /* for regular use this will _never_ be NULL,
444    * however we may be freeing an improperly initialized window. */
445   if (screen) {
446     ED_screen_exit(C, win, screen);
447   }
448 
449   wm_window_free(C, wm, win);
450 
451   /* if temp screen, delete it after window free (it stops jobs that can access it) */
452   if (screen && screen->temp) {
453     Main *bmain = CTX_data_main(C);
454 
455     BLI_assert(BKE_workspace_layout_screen_get(layout) == screen);
456     BKE_workspace_layout_remove(bmain, workspace, layout);
457     WM_event_add_notifier(C, NC_SCREEN | ND_LAYOUTDELETE, NULL);
458   }
459 }
460 
wm_window_title(wmWindowManager * wm,wmWindow * win)461 void wm_window_title(wmWindowManager *wm, wmWindow *win)
462 {
463   if (WM_window_is_temp_screen(win)) {
464     /* nothing to do for 'temp' windows,
465      * because WM_window_open_temp always sets window title  */
466   }
467   else if (win->ghostwin) {
468     /* this is set to 1 if you don't have startup.blend open */
469     if (G.save_over && BKE_main_blendfile_path_from_global()[0]) {
470       char str[sizeof(((Main *)NULL)->name) + 24];
471       BLI_snprintf(str,
472                    sizeof(str),
473                    "Blender%s [%s%s]",
474                    wm->file_saved ? "" : "*",
475                    BKE_main_blendfile_path_from_global(),
476                    G_MAIN->recovered ? " (Recovered)" : "");
477       GHOST_SetTitle(win->ghostwin, str);
478     }
479     else {
480       GHOST_SetTitle(win->ghostwin, "Blender");
481     }
482 
483     /* Informs GHOST of unsaved changes, to set window modified visual indicator (macOS)
484      * and to give hint of unsaved changes for a user warning mechanism in case of OS application
485      * terminate request (e.g. OS Shortcut Alt+F4, Command+Q, (...), or session end). */
486     GHOST_SetWindowModifiedState(win->ghostwin, (GHOST_TUns8)!wm->file_saved);
487   }
488 }
489 
WM_window_set_dpi(const wmWindow * win)490 void WM_window_set_dpi(const wmWindow *win)
491 {
492   float auto_dpi = GHOST_GetDPIHint(win->ghostwin);
493 
494   /* Clamp auto DPI to 96, since our font/interface drawing does not work well
495    * with lower sizes. The main case we are interested in supporting is higher
496    * DPI. If a smaller UI is desired it is still possible to adjust UI scale. */
497   auto_dpi = max_ff(auto_dpi, 96.0f);
498 
499   /* Lazily init UI scale size, preserving backwards compatibility by
500    * computing UI scale from ratio of previous DPI and auto DPI */
501   if (U.ui_scale == 0) {
502     int virtual_pixel = (U.virtual_pixel == VIRTUAL_PIXEL_NATIVE) ? 1 : 2;
503 
504     if (U.dpi == 0) {
505       U.ui_scale = virtual_pixel;
506     }
507     else {
508       U.ui_scale = (virtual_pixel * U.dpi * 96.0f) / (auto_dpi * 72.0f);
509     }
510 
511     CLAMP(U.ui_scale, 0.25f, 4.0f);
512   }
513 
514   /* Blender's UI drawing assumes DPI 72 as a good default following macOS
515    * while Windows and Linux use DPI 96. GHOST assumes a default 96 so we
516    * remap the DPI to Blender's convention. */
517   auto_dpi *= GHOST_GetNativePixelSize(win->ghostwin);
518   int dpi = auto_dpi * U.ui_scale * (72.0 / 96.0f);
519 
520   /* Automatically set larger pixel size for high DPI. */
521   int pixelsize = max_ii(1, (int)(dpi / 64));
522   /* User adjustment for pixel size. */
523   pixelsize = max_ii(1, pixelsize + U.ui_line_width);
524 
525   /* Set user preferences globals for drawing, and for forward compatibility. */
526   U.pixelsize = pixelsize;
527   U.dpi = dpi / pixelsize;
528   U.virtual_pixel = (pixelsize == 1) ? VIRTUAL_PIXEL_NATIVE : VIRTUAL_PIXEL_DOUBLE;
529   U.dpi_fac = ((U.pixelsize * (float)U.dpi) / 72.0f);
530   U.inv_dpi_fac = 1.0f / U.dpi_fac;
531 
532   /* Set user preferences globals for drawing, and for forward compatibility. */
533   U.widget_unit = (U.pixelsize * U.dpi * 20 + 36) / 72;
534   /* If line thickness differs from scaling factor then adjustments need to be made */
535   U.widget_unit += 2 * ((int)U.pixelsize - (int)U.dpi_fac);
536 
537   /* update font drawing */
538   BLF_default_dpi(U.pixelsize * U.dpi);
539 }
540 
wm_window_update_eventstate(wmWindow * win)541 static void wm_window_update_eventstate(wmWindow *win)
542 {
543   /* Update mouse position when a window is activated. */
544   wm_get_cursor_position(win, &win->eventstate->x, &win->eventstate->y);
545 }
546 
wm_window_ensure_eventstate(wmWindow * win)547 static void wm_window_ensure_eventstate(wmWindow *win)
548 {
549   if (win->eventstate) {
550     return;
551   }
552 
553   win->eventstate = MEM_callocN(sizeof(wmEvent), "window event state");
554   wm_window_update_eventstate(win);
555 }
556 
557 /* belongs to below */
wm_window_ghostwindow_add(wmWindowManager * wm,const char * title,wmWindow * win,bool is_dialog)558 static void wm_window_ghostwindow_add(wmWindowManager *wm,
559                                       const char *title,
560                                       wmWindow *win,
561                                       bool is_dialog)
562 {
563 
564   /* a new window is created when pageflip mode is required for a window */
565   GHOST_GLSettings glSettings = {0};
566   if (win->stereo3d_format->display_mode == S3D_DISPLAY_PAGEFLIP) {
567     glSettings.flags |= GHOST_glStereoVisual;
568   }
569 
570   if (G.debug & G_DEBUG_GPU) {
571     glSettings.flags |= GHOST_glDebugContext;
572   }
573 
574   int scr_w, scr_h;
575   wm_get_screensize(&scr_w, &scr_h);
576   int posy = (scr_h - win->posy - win->sizey);
577 
578   /* Clear drawable so we can set the new window. */
579   wmWindow *prev_windrawable = wm->windrawable;
580   wm_window_clear_drawable(wm);
581 
582   GHOST_WindowHandle ghostwin;
583   if (is_dialog && win->parent) {
584     ghostwin = GHOST_CreateDialogWindow(g_system,
585                                         win->parent->ghostwin,
586                                         title,
587                                         win->posx,
588                                         posy,
589                                         win->sizex,
590                                         win->sizey,
591                                         (GHOST_TWindowState)win->windowstate,
592                                         GHOST_kDrawingContextTypeOpenGL,
593                                         glSettings);
594   }
595   else {
596     ghostwin = GHOST_CreateWindow(g_system,
597                                   title,
598                                   win->posx,
599                                   posy,
600                                   win->sizex,
601                                   win->sizey,
602                                   (GHOST_TWindowState)win->windowstate,
603                                   GHOST_kDrawingContextTypeOpenGL,
604                                   glSettings);
605   }
606 
607   if (ghostwin) {
608     win->gpuctx = GPU_context_create(ghostwin);
609 
610     /* needed so we can detect the graphics card below */
611     GPU_init();
612 
613     /* Set window as drawable upon creation. Note this has already been
614      * it has already been activated by GHOST_CreateWindow. */
615     wm_window_set_drawable(wm, win, false);
616 
617     win->ghostwin = ghostwin;
618     GHOST_SetWindowUserData(ghostwin, win); /* pointer back */
619 
620     wm_window_ensure_eventstate(win);
621 
622     /* store actual window size in blender window */
623     GHOST_RectangleHandle bounds = GHOST_GetClientBounds(win->ghostwin);
624 
625     /* win32: gives undefined window size when minimized */
626     if (GHOST_GetWindowState(win->ghostwin) != GHOST_kWindowStateMinimized) {
627       win->sizex = GHOST_GetWidthRectangle(bounds);
628       win->sizey = GHOST_GetHeightRectangle(bounds);
629     }
630     GHOST_DisposeRectangle(bounds);
631 
632 #ifndef __APPLE__
633     /* set the state here, so minimized state comes up correct on windows */
634     if (wm_init_state.window_focus) {
635       GHOST_SetWindowState(ghostwin, (GHOST_TWindowState)win->windowstate);
636     }
637 #endif
638     /* until screens get drawn, make it nice gray */
639     GPU_clear_color(0.55f, 0.55f, 0.55f, 1.0f);
640 
641     /* needed here, because it's used before it reads userdef */
642     WM_window_set_dpi(win);
643 
644     wm_window_swap_buffers(win);
645 
646     // GHOST_SetWindowState(ghostwin, GHOST_kWindowStateModified);
647   }
648   else {
649     wm_window_set_drawable(wm, prev_windrawable, false);
650   }
651 }
652 
wm_window_ghostwindow_ensure(wmWindowManager * wm,wmWindow * win,bool is_dialog)653 static void wm_window_ghostwindow_ensure(wmWindowManager *wm, wmWindow *win, bool is_dialog)
654 {
655   if (win->ghostwin == NULL) {
656     if ((win->sizex == 0) || (wm_init_state.override_flag & WIN_OVERRIDE_GEOM)) {
657       win->posx = wm_init_state.start_x;
658       win->posy = wm_init_state.start_y;
659       win->sizex = wm_init_state.size_x;
660       win->sizey = wm_init_state.size_y;
661 
662       if (wm_init_state.override_flag & WIN_OVERRIDE_GEOM) {
663         win->windowstate = GHOST_kWindowStateNormal;
664         wm_init_state.override_flag &= ~WIN_OVERRIDE_GEOM;
665       }
666       else {
667         win->windowstate = GHOST_WINDOW_STATE_DEFAULT;
668       }
669     }
670 
671     if (wm_init_state.override_flag & WIN_OVERRIDE_WINSTATE) {
672       win->windowstate = wm_init_state.windowstate;
673       wm_init_state.override_flag &= ~WIN_OVERRIDE_WINSTATE;
674     }
675 
676     /* without this, cursor restore may fail, T45456 */
677     if (win->cursor == 0) {
678       win->cursor = WM_CURSOR_DEFAULT;
679     }
680 
681     wm_window_ghostwindow_add(wm, "Blender", win, is_dialog);
682   }
683 
684   if (win->ghostwin != NULL) {
685     /* If we have no ghostwin this is a buggy window that should be removed.
686      * However we still need to initialize it correctly so the screen doesn't hang. */
687 
688     /* happens after fileread */
689     wm_window_ensure_eventstate(win);
690 
691     WM_window_set_dpi(win);
692   }
693 
694   /* add keymap handlers (1 handler for all keys in map!) */
695   wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "Window", 0, 0);
696   WM_event_add_keymap_handler(&win->handlers, keymap);
697 
698   keymap = WM_keymap_ensure(wm->defaultconf, "Screen", 0, 0);
699   WM_event_add_keymap_handler(&win->handlers, keymap);
700 
701   keymap = WM_keymap_ensure(wm->defaultconf, "Screen Editing", 0, 0);
702   WM_event_add_keymap_handler(&win->modalhandlers, keymap);
703 
704   /* add drop boxes */
705   {
706     ListBase *lb = WM_dropboxmap_find("Window", 0, 0);
707     WM_event_add_dropbox_handler(&win->handlers, lb);
708   }
709   wm_window_title(wm, win);
710 
711   /* add topbar */
712   ED_screen_global_areas_refresh(win);
713 }
714 
715 /**
716  * Initialize #wmWindow without ghostwin, open these and clear.
717  *
718  * window size is read from window, if 0 it uses prefsize
719  * called in #WM_check, also inits stuff after file read.
720  *
721  * \warning
722  * After running, 'win->ghostwin' can be NULL in rare cases
723  * (where OpenGL driver fails to create a context for eg).
724  * We could remove them with #wm_window_ghostwindows_remove_invalid
725  * but better not since caller may continue to use.
726  * Instead, caller needs to handle the error case and cleanup.
727  */
wm_window_ghostwindows_ensure(wmWindowManager * wm)728 void wm_window_ghostwindows_ensure(wmWindowManager *wm)
729 {
730   BLI_assert(G.background == false);
731 
732   /* No command-line prefsize? then we set this.
733    * Note that these values will be used only
734    * when there is no startup.blend yet.
735    */
736   if (wm_init_state.size_x == 0) {
737     wm_get_screensize(&wm_init_state.size_x, &wm_init_state.size_y);
738 
739     /* note!, this isnt quite correct, active screen maybe offset 1000s if PX,
740      * we'd need a wm_get_screensize like function that gives offset,
741      * in practice the window manager will likely move to the correct monitor */
742     wm_init_state.start_x = 0;
743     wm_init_state.start_y = 0;
744   }
745 
746   LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
747     wm_window_ghostwindow_ensure(wm, win, false);
748   }
749 }
750 
751 /**
752  * Call after #wm_window_ghostwindows_ensure or #WM_check
753  * (after loading a new file) in the unlikely event a window couldn't be created.
754  */
wm_window_ghostwindows_remove_invalid(bContext * C,wmWindowManager * wm)755 void wm_window_ghostwindows_remove_invalid(bContext *C, wmWindowManager *wm)
756 {
757   BLI_assert(G.background == false);
758 
759   LISTBASE_FOREACH_MUTABLE (wmWindow *, win, &wm->windows) {
760     if (win->ghostwin == NULL) {
761       wm_window_close(C, wm, win);
762     }
763   }
764 }
765 
766 /* Update window size and position based on data from GHOST window. */
wm_window_update_size_position(wmWindow * win)767 static bool wm_window_update_size_position(wmWindow *win)
768 {
769   GHOST_RectangleHandle client_rect = GHOST_GetClientBounds(win->ghostwin);
770   int l, t, r, b;
771   GHOST_GetRectangle(client_rect, &l, &t, &r, &b);
772 
773   GHOST_DisposeRectangle(client_rect);
774 
775   int scr_w, scr_h;
776   wm_get_desktopsize(&scr_w, &scr_h);
777   int sizex = r - l;
778   int sizey = b - t;
779   int posx = l;
780   int posy = scr_h - t - win->sizey;
781 
782   if (win->sizex != sizex || win->sizey != sizey || win->posx != posx || win->posy != posy) {
783     win->sizex = sizex;
784     win->sizey = sizey;
785     win->posx = posx;
786     win->posy = posy;
787     return true;
788   }
789   return false;
790 }
791 
792 /**
793  * new window, no screen yet, but we open ghostwindow for it,
794  * also gets the window level handlers
795  * \note area-rip calls this.
796  * \return the window or NULL.
797  */
WM_window_open(bContext * C,const rcti * rect)798 wmWindow *WM_window_open(bContext *C, const rcti *rect)
799 {
800   wmWindowManager *wm = CTX_wm_manager(C);
801   wmWindow *win_prev = CTX_wm_window(C);
802   wmWindow *win = wm_window_new(CTX_data_main(C), wm, win_prev, false);
803 
804   win->posx = rect->xmin;
805   win->posy = rect->ymin;
806   win->sizex = BLI_rcti_size_x(rect);
807   win->sizey = BLI_rcti_size_y(rect);
808 
809   WM_check(C);
810 
811   if (win->ghostwin) {
812     return win;
813   }
814 
815   wm_window_close(C, wm, win);
816   CTX_wm_window_set(C, win_prev);
817   return NULL;
818 }
819 
820 /**
821  * Uses `screen->temp` tag to define what to do, currently it limits
822  * to only one "temp" window for render out, preferences, filewindow, etc...
823  *
824  * \param space_type: SPACE_VIEW3D, SPACE_INFO, ... (eSpace_Type)
825  * \return the window or NULL in case of failure.
826  */
WM_window_open_temp(bContext * C,const char * title,int x,int y,int sizex,int sizey,int space_type,bool dialog)827 wmWindow *WM_window_open_temp(bContext *C,
828                               const char *title,
829                               int x,
830                               int y,
831                               int sizex,
832                               int sizey,
833                               int space_type,
834                               bool dialog)
835 {
836   Main *bmain = CTX_data_main(C);
837   wmWindowManager *wm = CTX_wm_manager(C);
838   wmWindow *win_prev = CTX_wm_window(C);
839   Scene *scene = CTX_data_scene(C);
840   ViewLayer *view_layer = CTX_data_view_layer(C);
841 
842   /* convert to native OS window coordinates */
843   const float native_pixel_size = GHOST_GetNativePixelSize(win_prev->ghostwin);
844   x /= native_pixel_size;
845   y /= native_pixel_size;
846   sizex /= native_pixel_size;
847   sizey /= native_pixel_size;
848 
849   /* calculate position */
850   rcti rect;
851   rect.xmin = x + win_prev->posx - sizex / 2;
852   rect.ymin = y + win_prev->posy - sizey / 2;
853   rect.xmax = rect.xmin + sizex;
854   rect.ymax = rect.ymin + sizey;
855 
856   /* changes rect to fit within desktop */
857   wm_window_check_position(&rect);
858 
859   /* Reuse temporary or dialog window if one is open (but don't use a dialog for a regular
860    * temporary window, or vice versa). */
861   wmWindow *win = NULL;
862   LISTBASE_FOREACH (wmWindow *, win_iter, &wm->windows) {
863     if (WM_window_is_temp_screen(win_iter) &&
864         (dialog == GHOST_IsDialogWindow(win_iter->ghostwin))) {
865       win = win_iter;
866     }
867   }
868 
869   /* add new window? */
870   if (win == NULL) {
871     win = wm_window_new(bmain, wm, win_prev, dialog);
872 
873     win->posx = rect.xmin;
874     win->posy = rect.ymin;
875   }
876 
877   bScreen *screen = WM_window_get_active_screen(win);
878 
879   win->sizex = BLI_rcti_size_x(&rect);
880   win->sizey = BLI_rcti_size_y(&rect);
881 
882   if (WM_window_get_active_workspace(win) == NULL) {
883     WorkSpace *workspace = WM_window_get_active_workspace(win_prev);
884     BKE_workspace_active_set(win->workspace_hook, workspace);
885   }
886 
887   if (screen == NULL) {
888     /* add new screen layout */
889     WorkSpace *workspace = WM_window_get_active_workspace(win);
890     WorkSpaceLayout *layout = ED_workspace_layout_add(bmain, workspace, win, "temp");
891 
892     screen = BKE_workspace_layout_screen_get(layout);
893     WM_window_set_active_layout(win, workspace, layout);
894   }
895 
896   /* Set scene and view layer to match original window. */
897   STRNCPY(win->view_layer_name, view_layer->name);
898   if (WM_window_get_active_scene(win) != scene) {
899     ED_screen_scene_change(C, win, scene);
900   }
901 
902   screen->temp = 1;
903 
904   /* make window active, and validate/resize */
905   CTX_wm_window_set(C, win);
906   const bool new_window = (win->ghostwin == NULL);
907   if (new_window) {
908     wm_window_ghostwindow_ensure(wm, win, dialog);
909   }
910   WM_check(C);
911 
912   /* It's possible `win->ghostwin == NULL`.
913    * instead of attempting to cleanup here (in a half finished state),
914    * finish setting up the screen, then free it at the end of the function,
915    * to avoid having to take into account a partially-created window.
916    */
917 
918   /* ensure it shows the right spacetype editor */
919   ScrArea *area = screen->areabase.first;
920   CTX_wm_area_set(C, area);
921 
922   ED_area_newspace(C, area, space_type, false);
923 
924   ED_screen_change(C, screen);
925 
926   if (!new_window) {
927     /* Set size in GHOST window and then update size and position from GHOST,
928      * in case they where changed by GHOST to fit the monitor/screen. */
929     wm_window_set_size(win, win->sizex, win->sizey);
930     wm_window_update_size_position(win);
931   }
932 
933   /* Refresh screen dimensions, after the effective window size is known. */
934   ED_screen_refresh(wm, win);
935 
936   if (win->ghostwin) {
937     wm_window_raise(win);
938     GHOST_SetTitle(win->ghostwin, title);
939     return win;
940   }
941 
942   /* very unlikely! but opening a new window can fail */
943   wm_window_close(C, wm, win);
944   CTX_wm_window_set(C, win_prev);
945 
946   return NULL;
947 }
948 
949 /* ****************** Operators ****************** */
950 
wm_window_close_exec(bContext * C,wmOperator * UNUSED (op))951 int wm_window_close_exec(bContext *C, wmOperator *UNUSED(op))
952 {
953   wmWindowManager *wm = CTX_wm_manager(C);
954   wmWindow *win = CTX_wm_window(C);
955   wm_window_close(C, wm, win);
956   return OPERATOR_FINISHED;
957 }
958 
wm_window_new_exec(bContext * C,wmOperator * UNUSED (op))959 int wm_window_new_exec(bContext *C, wmOperator *UNUSED(op))
960 {
961   wmWindow *win_src = CTX_wm_window(C);
962 
963   bool ok = (wm_window_copy_test(C, win_src, true, true) != NULL);
964 
965   return ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
966 }
967 
wm_window_new_main_exec(bContext * C,wmOperator * UNUSED (op))968 int wm_window_new_main_exec(bContext *C, wmOperator *UNUSED(op))
969 {
970   wmWindow *win_src = CTX_wm_window(C);
971 
972   bool ok = (wm_window_copy_test(C, win_src, true, false) != NULL);
973 
974   return ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
975 }
976 
977 /* fullscreen operator callback */
wm_window_fullscreen_toggle_exec(bContext * C,wmOperator * UNUSED (op))978 int wm_window_fullscreen_toggle_exec(bContext *C, wmOperator *UNUSED(op))
979 {
980   wmWindow *window = CTX_wm_window(C);
981 
982   if (G.background) {
983     return OPERATOR_CANCELLED;
984   }
985 
986   GHOST_TWindowState state = GHOST_GetWindowState(window->ghostwin);
987   if (state != GHOST_kWindowStateFullScreen) {
988     GHOST_SetWindowState(window->ghostwin, GHOST_kWindowStateFullScreen);
989   }
990   else {
991     GHOST_SetWindowState(window->ghostwin, GHOST_kWindowStateNormal);
992   }
993 
994   return OPERATOR_FINISHED;
995 }
996 
997 /* ************ events *************** */
998 
wm_cursor_position_from_ghost(wmWindow * win,int * x,int * y)999 void wm_cursor_position_from_ghost(wmWindow *win, int *x, int *y)
1000 {
1001   float fac = GHOST_GetNativePixelSize(win->ghostwin);
1002 
1003   GHOST_ScreenToClient(win->ghostwin, *x, *y, x, y);
1004   *x *= fac;
1005 
1006   *y = (win->sizey - 1) - *y;
1007   *y *= fac;
1008 }
1009 
wm_cursor_position_to_ghost(wmWindow * win,int * x,int * y)1010 void wm_cursor_position_to_ghost(wmWindow *win, int *x, int *y)
1011 {
1012   float fac = GHOST_GetNativePixelSize(win->ghostwin);
1013 
1014   *x /= fac;
1015   *y /= fac;
1016   *y = win->sizey - *y - 1;
1017 
1018   GHOST_ClientToScreen(win->ghostwin, *x, *y, x, y);
1019 }
1020 
wm_get_cursor_position(wmWindow * win,int * x,int * y)1021 void wm_get_cursor_position(wmWindow *win, int *x, int *y)
1022 {
1023   if (UNLIKELY(G.f & G_FLAG_EVENT_SIMULATE)) {
1024     *x = win->eventstate->x;
1025     *y = win->eventstate->y;
1026     return;
1027   }
1028   GHOST_GetCursorPosition(g_system, x, y);
1029   wm_cursor_position_from_ghost(win, x, y);
1030 }
1031 
1032 typedef enum {
1033   SHIFT = 's',
1034   CONTROL = 'c',
1035   ALT = 'a',
1036   OS = 'C',
1037 } modifierKeyType;
1038 
1039 /* check if specified modifier key type is pressed */
query_qual(modifierKeyType qual)1040 static int query_qual(modifierKeyType qual)
1041 {
1042   GHOST_TModifierKeyMask left, right;
1043   switch (qual) {
1044     case SHIFT:
1045       left = GHOST_kModifierKeyLeftShift;
1046       right = GHOST_kModifierKeyRightShift;
1047       break;
1048     case CONTROL:
1049       left = GHOST_kModifierKeyLeftControl;
1050       right = GHOST_kModifierKeyRightControl;
1051       break;
1052     case OS:
1053       left = right = GHOST_kModifierKeyOS;
1054       break;
1055     case ALT:
1056     default:
1057       left = GHOST_kModifierKeyLeftAlt;
1058       right = GHOST_kModifierKeyRightAlt;
1059       break;
1060   }
1061 
1062   int val = 0;
1063   GHOST_GetModifierKeyState(g_system, left, &val);
1064   if (!val) {
1065     GHOST_GetModifierKeyState(g_system, right, &val);
1066   }
1067 
1068   return val;
1069 }
1070 
wm_window_set_drawable(wmWindowManager * wm,wmWindow * win,bool activate)1071 static void wm_window_set_drawable(wmWindowManager *wm, wmWindow *win, bool activate)
1072 {
1073   BLI_assert(ELEM(wm->windrawable, NULL, win));
1074 
1075   wm->windrawable = win;
1076   if (activate) {
1077     GHOST_ActivateWindowDrawingContext(win->ghostwin);
1078   }
1079   GPU_context_active_set(win->gpuctx);
1080 }
1081 
wm_window_clear_drawable(wmWindowManager * wm)1082 void wm_window_clear_drawable(wmWindowManager *wm)
1083 {
1084   if (wm->windrawable) {
1085     wm->windrawable = NULL;
1086   }
1087 }
1088 
wm_window_make_drawable(wmWindowManager * wm,wmWindow * win)1089 void wm_window_make_drawable(wmWindowManager *wm, wmWindow *win)
1090 {
1091   BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get());
1092 
1093   if (win != wm->windrawable && win->ghostwin) {
1094     //      win->lmbut = 0; /* keeps hanging when mousepressed while other window opened */
1095     wm_window_clear_drawable(wm);
1096 
1097     if (G.debug & G_DEBUG_EVENTS) {
1098       printf("%s: set drawable %d\n", __func__, win->winid);
1099     }
1100 
1101     wm_window_set_drawable(wm, win, true);
1102 
1103     /* this can change per window */
1104     WM_window_set_dpi(win);
1105   }
1106 }
1107 
1108 /* Reset active the current window opengl drawing context. */
wm_window_reset_drawable(void)1109 void wm_window_reset_drawable(void)
1110 {
1111   BLI_assert(BLI_thread_is_main());
1112   BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get());
1113   wmWindowManager *wm = G_MAIN->wm.first;
1114 
1115   if (wm == NULL) {
1116     return;
1117   }
1118   wmWindow *win = wm->windrawable;
1119 
1120   if (win && win->ghostwin) {
1121     wm_window_clear_drawable(wm);
1122     wm_window_set_drawable(wm, win, true);
1123   }
1124 }
1125 
1126 /**
1127  * Called by ghost, here we handle events for windows themselves or send to event system.
1128  *
1129  * Mouse coordinate conversion happens here.
1130  */
ghost_event_proc(GHOST_EventHandle evt,GHOST_TUserDataPtr C_void_ptr)1131 static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr)
1132 {
1133   bContext *C = C_void_ptr;
1134   wmWindowManager *wm = CTX_wm_manager(C);
1135   GHOST_TEventType type = GHOST_GetEventType(evt);
1136 #if 0
1137   /* We may want to use time from ghost, currently `PIL_check_seconds_timer` is used instead. */
1138   uint64_t time = GHOST_GetEventTime(evt);
1139 #endif
1140 
1141   if (type == GHOST_kEventQuitRequest) {
1142     /* Find an active window to display quit dialog in. */
1143     GHOST_WindowHandle ghostwin = GHOST_GetEventWindow(evt);
1144 
1145     wmWindow *win;
1146     if (ghostwin && GHOST_ValidWindow(g_system, ghostwin)) {
1147       win = GHOST_GetWindowUserData(ghostwin);
1148     }
1149     else {
1150       win = wm->winactive;
1151     }
1152 
1153     /* Display quit dialog or quit immediately. */
1154     if (win) {
1155       wm_quit_with_optional_confirmation_prompt(C, win);
1156     }
1157     else {
1158       wm_exit_schedule_delayed(C);
1159     }
1160   }
1161   else {
1162     GHOST_WindowHandle ghostwin = GHOST_GetEventWindow(evt);
1163     GHOST_TEventDataPtr data = GHOST_GetEventData(evt);
1164 
1165     /* Ghost now can call this function for life resizes,
1166      * but it should return if WM didn't initialize yet.
1167      * Can happen on file read (especially full size window). */
1168     if ((wm->initialized & WM_WINDOW_IS_INIT) == 0) {
1169       return 1;
1170     }
1171     if (!ghostwin) {
1172       /* XXX - should be checked, why are we getting an event here, and */
1173       /* what is it? */
1174       puts("<!> event has no window");
1175       return 1;
1176     }
1177     if (!GHOST_ValidWindow(g_system, ghostwin)) {
1178       /* XXX - should be checked, why are we getting an event here, and */
1179       /* what is it? */
1180       puts("<!> event has invalid window");
1181       return 1;
1182     }
1183     wmWindow *win = GHOST_GetWindowUserData(ghostwin);
1184 
1185     switch (type) {
1186       case GHOST_kEventWindowDeactivate:
1187         wm_event_add_ghostevent(wm, win, type, data);
1188         win->active = 0; /* XXX */
1189 
1190         /* clear modifiers for inactive windows */
1191         win->eventstate->alt = 0;
1192         win->eventstate->ctrl = 0;
1193         win->eventstate->shift = 0;
1194         win->eventstate->oskey = 0;
1195         win->eventstate->keymodifier = 0;
1196 
1197         break;
1198       case GHOST_kEventWindowActivate: {
1199         GHOST_TEventKeyData kdata;
1200         const int keymodifier = ((query_qual(SHIFT) ? KM_SHIFT : 0) |
1201                                  (query_qual(CONTROL) ? KM_CTRL : 0) |
1202                                  (query_qual(ALT) ? KM_ALT : 0) | (query_qual(OS) ? KM_OSKEY : 0));
1203 
1204         /* Win23/GHOST modifier bug, see T40317 */
1205 #ifndef WIN32
1206 //#  define USE_WIN_ACTIVATE
1207 #endif
1208 
1209         /* No context change! C->wm->windrawable is drawable, or for area queues. */
1210         wm->winactive = win;
1211 
1212         win->active = 1;
1213         //              window_handle(win, INPUTCHANGE, win->active);
1214 
1215         /* bad ghost support for modifier keys... so on activate we set the modifiers again */
1216 
1217         /* TODO: This is not correct since a modifier may be held when a window is activated...
1218          * better solve this at ghost level. attempted fix r54450 but it caused bug T34255.
1219          *
1220          * For now don't send GHOST_kEventKeyDown events, just set the 'eventstate'.
1221          */
1222         kdata.ascii = '\0';
1223         kdata.utf8_buf[0] = '\0';
1224 
1225         if (win->eventstate->shift) {
1226           if ((keymodifier & KM_SHIFT) == 0) {
1227             kdata.key = GHOST_kKeyLeftShift;
1228             wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata);
1229           }
1230         }
1231 #ifdef USE_WIN_ACTIVATE
1232         else {
1233           if (keymodifier & KM_SHIFT) {
1234             win->eventstate->shift = KM_MOD_FIRST;
1235           }
1236         }
1237 #endif
1238         if (win->eventstate->ctrl) {
1239           if ((keymodifier & KM_CTRL) == 0) {
1240             kdata.key = GHOST_kKeyLeftControl;
1241             wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata);
1242           }
1243         }
1244 #ifdef USE_WIN_ACTIVATE
1245         else {
1246           if (keymodifier & KM_CTRL) {
1247             win->eventstate->ctrl = KM_MOD_FIRST;
1248           }
1249         }
1250 #endif
1251         if (win->eventstate->alt) {
1252           if ((keymodifier & KM_ALT) == 0) {
1253             kdata.key = GHOST_kKeyLeftAlt;
1254             wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata);
1255           }
1256         }
1257 #ifdef USE_WIN_ACTIVATE
1258         else {
1259           if (keymodifier & KM_ALT) {
1260             win->eventstate->alt = KM_MOD_FIRST;
1261           }
1262         }
1263 #endif
1264         if (win->eventstate->oskey) {
1265           if ((keymodifier & KM_OSKEY) == 0) {
1266             kdata.key = GHOST_kKeyOS;
1267             wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata);
1268           }
1269         }
1270 #ifdef USE_WIN_ACTIVATE
1271         else {
1272           if (keymodifier & KM_OSKEY) {
1273             win->eventstate->oskey = KM_MOD_FIRST;
1274           }
1275         }
1276 #endif
1277 
1278 #undef USE_WIN_ACTIVATE
1279 
1280         /* keymodifier zero, it hangs on hotkeys that open windows otherwise */
1281         win->eventstate->keymodifier = 0;
1282 
1283         /* entering window, update mouse pos. but no event */
1284         wm_window_update_eventstate(win);
1285 
1286         win->addmousemove = 1; /* enables highlighted buttons */
1287 
1288         wm_window_make_drawable(wm, win);
1289 
1290         /* window might be focused by mouse click in configuration of window manager
1291          * when focus is not following mouse
1292          * click could have been done on a button and depending on window manager settings
1293          * click would be passed to blender or not, but in any case button under cursor
1294          * should be activated, so at max next click on button without moving mouse
1295          * would trigger its handle function
1296          * currently it seems to be common practice to generate new event for, but probably
1297          * we'll need utility function for this? (sergey)
1298          */
1299         wmEvent event;
1300         wm_event_init_from_window(win, &event);
1301         event.type = MOUSEMOVE;
1302         event.prevx = event.x;
1303         event.prevy = event.y;
1304         event.is_repeat = false;
1305 
1306         wm_event_add(win, &event);
1307 
1308         break;
1309       }
1310       case GHOST_kEventWindowClose: {
1311         wm_window_close(C, wm, win);
1312         break;
1313       }
1314       case GHOST_kEventWindowUpdate: {
1315         if (G.debug & G_DEBUG_EVENTS) {
1316           printf("%s: ghost redraw %d\n", __func__, win->winid);
1317         }
1318 
1319         wm_window_make_drawable(wm, win);
1320         WM_event_add_notifier(C, NC_WINDOW, NULL);
1321 
1322         break;
1323       }
1324       case GHOST_kEventWindowSize:
1325       case GHOST_kEventWindowMove: {
1326         GHOST_TWindowState state = GHOST_GetWindowState(win->ghostwin);
1327         win->windowstate = state;
1328 
1329         WM_window_set_dpi(win);
1330 
1331         /* win32: gives undefined window size when minimized */
1332         if (state != GHOST_kWindowStateMinimized) {
1333           /*
1334            * Ghost sometimes send size or move events when the window hasn't changed.
1335            * One case of this is using compiz on linux. To alleviate the problem
1336            * we ignore all such event here.
1337            *
1338            * It might be good to eventually do that at Ghost level, but that is for
1339            * another time.
1340            */
1341           if (wm_window_update_size_position(win)) {
1342             const bScreen *screen = WM_window_get_active_screen(win);
1343 
1344             /* debug prints */
1345             if (G.debug & G_DEBUG_EVENTS) {
1346               const char *state_str;
1347               state = GHOST_GetWindowState(win->ghostwin);
1348 
1349               if (state == GHOST_kWindowStateNormal) {
1350                 state_str = "normal";
1351               }
1352               else if (state == GHOST_kWindowStateMinimized) {
1353                 state_str = "minimized";
1354               }
1355               else if (state == GHOST_kWindowStateMaximized) {
1356                 state_str = "maximized";
1357               }
1358               else if (state == GHOST_kWindowStateFullScreen) {
1359                 state_str = "fullscreen";
1360               }
1361               else {
1362                 state_str = "<unknown>";
1363               }
1364 
1365               printf("%s: window %d state = %s\n", __func__, win->winid, state_str);
1366 
1367               if (type != GHOST_kEventWindowSize) {
1368                 printf("win move event pos %d %d size %d %d\n",
1369                        win->posx,
1370                        win->posy,
1371                        win->sizex,
1372                        win->sizey);
1373               }
1374             }
1375 
1376             wm_window_make_drawable(wm, win);
1377             BKE_icon_changed(screen->id.icon_id);
1378             WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
1379             WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, NULL);
1380 
1381 #if defined(__APPLE__) || defined(WIN32)
1382             /* OSX and Win32 don't return to the mainloop while resize */
1383             wm_window_timer(C);
1384             wm_event_do_handlers(C);
1385             wm_event_do_notifiers(C);
1386             wm_draw_update(C);
1387 #endif
1388           }
1389         }
1390         break;
1391       }
1392 
1393       case GHOST_kEventWindowDPIHintChanged: {
1394         WM_window_set_dpi(win);
1395         /* font's are stored at each DPI level, without this we can easy load 100's of fonts */
1396         BLF_cache_clear();
1397 
1398         WM_main_add_notifier(NC_WINDOW, NULL);             /* full redraw */
1399         WM_main_add_notifier(NC_SCREEN | NA_EDITED, NULL); /* refresh region sizes */
1400         break;
1401       }
1402 
1403       case GHOST_kEventOpenMainFile: {
1404         const char *path = GHOST_GetEventData(evt);
1405 
1406         if (path) {
1407           wmOperatorType *ot = WM_operatortype_find("WM_OT_open_mainfile", false);
1408           /* operator needs a valid window in context, ensures
1409            * it is correctly set */
1410           CTX_wm_window_set(C, win);
1411 
1412           PointerRNA props_ptr;
1413           WM_operator_properties_create_ptr(&props_ptr, ot);
1414           RNA_string_set(&props_ptr, "filepath", path);
1415           RNA_boolean_set(&props_ptr, "display_file_selector", false);
1416           WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr);
1417           WM_operator_properties_free(&props_ptr);
1418 
1419           CTX_wm_window_set(C, NULL);
1420         }
1421         break;
1422       }
1423       case GHOST_kEventDraggingDropDone: {
1424         GHOST_TEventDragnDropData *ddd = GHOST_GetEventData(evt);
1425 
1426         /* entering window, update mouse pos */
1427         wm_window_update_eventstate(win);
1428 
1429         wmEvent event;
1430         wm_event_init_from_window(win, &event); /* copy last state, like mouse coords */
1431 
1432         /* activate region */
1433         event.type = MOUSEMOVE;
1434         event.prevx = event.x;
1435         event.prevy = event.y;
1436         event.is_repeat = false;
1437 
1438         /* No context change! C->wm->windrawable is drawable, or for area queues. */
1439         wm->winactive = win;
1440 
1441         win->active = 1;
1442 
1443         wm_event_add(win, &event);
1444 
1445         /* make blender drop event with custom data pointing to wm drags */
1446         event.type = EVT_DROP;
1447         event.val = KM_RELEASE;
1448         event.custom = EVT_DATA_DRAGDROP;
1449         event.customdata = &wm->drags;
1450         event.customdatafree = 1;
1451 
1452         wm_event_add(win, &event);
1453 
1454         /* printf("Drop detected\n"); */
1455 
1456         /* add drag data to wm for paths: */
1457 
1458         if (ddd->dataType == GHOST_kDragnDropTypeFilenames) {
1459           GHOST_TStringArray *stra = ddd->data;
1460 
1461           for (int a = 0; a < stra->count; a++) {
1462             printf("drop file %s\n", stra->strings[a]);
1463             /* try to get icon type from extension */
1464             int icon = ED_file_extension_icon((char *)stra->strings[a]);
1465 
1466             WM_event_start_drag(C, icon, WM_DRAG_PATH, stra->strings[a], 0.0, WM_DRAG_NOP);
1467             /* void poin should point to string, it makes a copy */
1468             break; /* only one drop element supported now */
1469           }
1470         }
1471 
1472         break;
1473       }
1474       case GHOST_kEventNativeResolutionChange: {
1475         /* Only update if the actual pixel size changes. */
1476         float prev_pixelsize = U.pixelsize;
1477         WM_window_set_dpi(win);
1478 
1479         if (U.pixelsize != prev_pixelsize) {
1480           BKE_icon_changed(WM_window_get_active_screen(win)->id.icon_id);
1481 
1482           /* Close all popups since they are positioned with the pixel
1483            * size baked in and it's difficult to correct them. */
1484           CTX_wm_window_set(C, win);
1485           UI_popup_handlers_remove_all(C, &win->modalhandlers);
1486           CTX_wm_window_set(C, NULL);
1487 
1488           wm_window_make_drawable(wm, win);
1489 
1490           WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
1491           WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, NULL);
1492         }
1493 
1494         break;
1495       }
1496       case GHOST_kEventTrackpad: {
1497         GHOST_TEventTrackpadData *pd = data;
1498 
1499         wm_cursor_position_from_ghost(win, &pd->x, &pd->y);
1500         wm_event_add_ghostevent(wm, win, type, data);
1501         break;
1502       }
1503       case GHOST_kEventCursorMove: {
1504         GHOST_TEventCursorData *cd = data;
1505 
1506         wm_cursor_position_from_ghost(win, &cd->x, &cd->y);
1507         wm_event_add_ghostevent(wm, win, type, data);
1508         break;
1509       }
1510       case GHOST_kEventButtonDown:
1511       case GHOST_kEventButtonUp: {
1512         if (win->active == 0) {
1513           /* Entering window, update cursor and tablet state.
1514            * (ghost sends win-activate *after* the mouse-click in window!) */
1515           wm_window_update_eventstate(win);
1516         }
1517 
1518         wm_event_add_ghostevent(wm, win, type, data);
1519         break;
1520       }
1521       default: {
1522         wm_event_add_ghostevent(wm, win, type, data);
1523         break;
1524       }
1525     }
1526   }
1527   return 1;
1528 }
1529 
1530 /**
1531  * This timer system only gives maximum 1 timer event per redraw cycle,
1532  * to prevent queues to get overloaded.
1533  * Timer handlers should check for delta to decide if they just update, or follow real time.
1534  * Timer handlers can also set duration to match frames passed
1535  */
wm_window_timer(const bContext * C)1536 static int wm_window_timer(const bContext *C)
1537 {
1538   Main *bmain = CTX_data_main(C);
1539   wmWindowManager *wm = CTX_wm_manager(C);
1540   double time = PIL_check_seconds_timer();
1541   int retval = 0;
1542 
1543   /* Mutable in case the timer gets removed. */
1544   LISTBASE_FOREACH_MUTABLE (wmTimer *, wt, &wm->timers) {
1545     wmWindow *win = wt->win;
1546 
1547     if (wt->sleep != 0) {
1548       continue;
1549     }
1550 
1551     if (time > wt->ntime) {
1552       wt->delta = time - wt->ltime;
1553       wt->duration += wt->delta;
1554       wt->ltime = time;
1555       wt->ntime = wt->stime + wt->timestep * ceil(wt->duration / wt->timestep);
1556 
1557       if (wt->event_type == TIMERJOBS) {
1558         wm_jobs_timer(wm, wt);
1559       }
1560       else if (wt->event_type == TIMERAUTOSAVE) {
1561         wm_autosave_timer(bmain, wm, wt);
1562       }
1563       else if (wt->event_type == TIMERNOTIFIER) {
1564         WM_main_add_notifier(POINTER_AS_UINT(wt->customdata), NULL);
1565       }
1566       else if (win) {
1567         wmEvent event;
1568         wm_event_init_from_window(win, &event);
1569 
1570         event.type = wt->event_type;
1571         event.val = KM_NOTHING;
1572         event.keymodifier = 0;
1573         event.is_repeat = false;
1574         event.custom = EVT_DATA_TIMER;
1575         event.customdata = wt;
1576         wm_event_add(win, &event);
1577 
1578         retval = 1;
1579       }
1580     }
1581   }
1582   return retval;
1583 }
1584 
wm_window_process_events(const bContext * C)1585 void wm_window_process_events(const bContext *C)
1586 {
1587   BLI_assert(BLI_thread_is_main());
1588 
1589   int hasevent = GHOST_ProcessEvents(g_system, 0); /* 0 is no wait */
1590 
1591   if (hasevent) {
1592     GHOST_DispatchEvents(g_system);
1593   }
1594   hasevent |= wm_window_timer(C);
1595 #ifdef WITH_XR_OPENXR
1596   /* XR events don't use the regular window queues. So here we don't only trigger
1597    * processing/dispatching but also handling. */
1598   hasevent |= wm_xr_events_handle(CTX_wm_manager(C));
1599 #endif
1600 
1601   /* no event, we sleep 5 milliseconds */
1602   if (hasevent == 0) {
1603     PIL_sleep_ms(5);
1604   }
1605 }
1606 
1607 /* -------------------------------------------------------------------- */
1608 /** \name Ghost Init/Exit
1609  * \{ */
1610 
1611 /**
1612  * \note #bContext can be null in background mode because we don't
1613  * need to event handling.
1614  */
wm_ghost_init(bContext * C)1615 void wm_ghost_init(bContext *C)
1616 {
1617   if (!g_system) {
1618     GHOST_EventConsumerHandle consumer;
1619 
1620     if (C != NULL) {
1621       consumer = GHOST_CreateEventConsumer(ghost_event_proc, C);
1622     }
1623 
1624     g_system = GHOST_CreateSystem();
1625     GHOST_SystemInitDebug(g_system, G.debug & G_DEBUG_GHOST);
1626 
1627     if (C != NULL) {
1628       GHOST_AddEventConsumer(g_system, consumer);
1629     }
1630 
1631     if (wm_init_state.native_pixels) {
1632       GHOST_UseNativePixels();
1633     }
1634 
1635     GHOST_UseWindowFocus(wm_init_state.window_focus);
1636   }
1637 }
1638 
wm_ghost_exit(void)1639 void wm_ghost_exit(void)
1640 {
1641   if (g_system) {
1642     GHOST_DisposeSystem(g_system);
1643   }
1644   g_system = NULL;
1645 }
1646 
1647 /** \} */
1648 
1649 /* -------------------------------------------------------------------- */
1650 /** \name Event Timer
1651  * \{ */
1652 
1653 /* to (de)activate running timers temporary */
WM_event_timer_sleep(wmWindowManager * wm,wmWindow * UNUSED (win),wmTimer * timer,bool do_sleep)1654 void WM_event_timer_sleep(wmWindowManager *wm,
1655                           wmWindow *UNUSED(win),
1656                           wmTimer *timer,
1657                           bool do_sleep)
1658 {
1659   LISTBASE_FOREACH (wmTimer *, wt, &wm->timers) {
1660     if (wt == timer) {
1661       wt->sleep = do_sleep;
1662       break;
1663     }
1664   }
1665 }
1666 
WM_event_add_timer(wmWindowManager * wm,wmWindow * win,int event_type,double timestep)1667 wmTimer *WM_event_add_timer(wmWindowManager *wm, wmWindow *win, int event_type, double timestep)
1668 {
1669   wmTimer *wt = MEM_callocN(sizeof(wmTimer), "window timer");
1670 
1671   wt->event_type = event_type;
1672   wt->ltime = PIL_check_seconds_timer();
1673   wt->ntime = wt->ltime + timestep;
1674   wt->stime = wt->ltime;
1675   wt->timestep = timestep;
1676   wt->win = win;
1677 
1678   BLI_addtail(&wm->timers, wt);
1679 
1680   return wt;
1681 }
1682 
WM_event_add_timer_notifier(wmWindowManager * wm,wmWindow * win,unsigned int type,double timestep)1683 wmTimer *WM_event_add_timer_notifier(wmWindowManager *wm,
1684                                      wmWindow *win,
1685                                      unsigned int type,
1686                                      double timestep)
1687 {
1688   wmTimer *wt = MEM_callocN(sizeof(wmTimer), "window timer");
1689 
1690   wt->event_type = TIMERNOTIFIER;
1691   wt->ltime = PIL_check_seconds_timer();
1692   wt->ntime = wt->ltime + timestep;
1693   wt->stime = wt->ltime;
1694   wt->timestep = timestep;
1695   wt->win = win;
1696   wt->customdata = POINTER_FROM_UINT(type);
1697   wt->flags |= WM_TIMER_NO_FREE_CUSTOM_DATA;
1698 
1699   BLI_addtail(&wm->timers, wt);
1700 
1701   return wt;
1702 }
1703 
WM_event_remove_timer(wmWindowManager * wm,wmWindow * UNUSED (win),wmTimer * timer)1704 void WM_event_remove_timer(wmWindowManager *wm, wmWindow *UNUSED(win), wmTimer *timer)
1705 {
1706   /* extra security check */
1707   wmTimer *wt = NULL;
1708   LISTBASE_FOREACH (wmTimer *, timer_iter, &wm->timers) {
1709     if (timer_iter == timer) {
1710       wt = timer_iter;
1711     }
1712   }
1713   if (wt == NULL) {
1714     return;
1715   }
1716 
1717   if (wm->reports.reporttimer == wt) {
1718     wm->reports.reporttimer = NULL;
1719   }
1720 
1721   BLI_remlink(&wm->timers, wt);
1722   if (wt->customdata != NULL && (wt->flags & WM_TIMER_NO_FREE_CUSTOM_DATA) == 0) {
1723     MEM_freeN(wt->customdata);
1724   }
1725   MEM_freeN(wt);
1726 
1727   /* there might be events in queue with this timer as customdata */
1728   LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
1729     LISTBASE_FOREACH (wmEvent *, event, &win->queue) {
1730       if (event->customdata == wt) {
1731         event->customdata = NULL;
1732         event->type = EVENT_NONE; /* timer users customdata, dont want NULL == NULL */
1733       }
1734     }
1735   }
1736 }
1737 
WM_event_remove_timer_notifier(wmWindowManager * wm,wmWindow * win,wmTimer * timer)1738 void WM_event_remove_timer_notifier(wmWindowManager *wm, wmWindow *win, wmTimer *timer)
1739 {
1740   timer->customdata = NULL;
1741   WM_event_remove_timer(wm, win, timer);
1742 }
1743 
1744 /** \} */
1745 
1746 /* -------------------------------------------------------------------- */
1747 /** \name Clipboard
1748  * \{ */
1749 
wm_clipboard_text_get_ex(bool selection,int * r_len,bool firstline)1750 static char *wm_clipboard_text_get_ex(bool selection, int *r_len, bool firstline)
1751 {
1752   if (G.background) {
1753     *r_len = 0;
1754     return NULL;
1755   }
1756 
1757   char *buf = (char *)GHOST_getClipboard(selection);
1758   if (!buf) {
1759     *r_len = 0;
1760     return NULL;
1761   }
1762 
1763   /* always convert from \r\n to \n */
1764   char *newbuf = MEM_mallocN(strlen(buf) + 1, __func__);
1765   char *p2 = newbuf;
1766 
1767   if (firstline) {
1768     /* will return an over-alloc'ed value in the case there are newlines */
1769     for (char *p = buf; *p; p++) {
1770       if ((*p != '\n') && (*p != '\r')) {
1771         *(p2++) = *p;
1772       }
1773       else {
1774         break;
1775       }
1776     }
1777   }
1778   else {
1779     for (char *p = buf; *p; p++) {
1780       if (*p != '\r') {
1781         *(p2++) = *p;
1782       }
1783     }
1784   }
1785 
1786   *p2 = '\0';
1787 
1788   free(buf); /* ghost uses regular malloc */
1789 
1790   *r_len = (p2 - newbuf);
1791 
1792   return newbuf;
1793 }
1794 
1795 /**
1796  * Return text from the clipboard.
1797  *
1798  * \note Caller needs to check for valid utf8 if this is a requirement.
1799  */
WM_clipboard_text_get(bool selection,int * r_len)1800 char *WM_clipboard_text_get(bool selection, int *r_len)
1801 {
1802   return wm_clipboard_text_get_ex(selection, r_len, false);
1803 }
1804 
1805 /**
1806  * Convenience function for pasting to areas of Blender which don't support newlines.
1807  */
WM_clipboard_text_get_firstline(bool selection,int * r_len)1808 char *WM_clipboard_text_get_firstline(bool selection, int *r_len)
1809 {
1810   return wm_clipboard_text_get_ex(selection, r_len, true);
1811 }
1812 
WM_clipboard_text_set(const char * buf,bool selection)1813 void WM_clipboard_text_set(const char *buf, bool selection)
1814 {
1815   if (!G.background) {
1816 #ifdef _WIN32
1817     /* do conversion from \n to \r\n on Windows */
1818     const char *p;
1819     char *p2, *newbuf;
1820     int newlen = 0;
1821 
1822     for (p = buf; *p; p++) {
1823       if (*p == '\n') {
1824         newlen += 2;
1825       }
1826       else {
1827         newlen++;
1828       }
1829     }
1830 
1831     newbuf = MEM_callocN(newlen + 1, "WM_clipboard_text_set");
1832 
1833     for (p = buf, p2 = newbuf; *p; p++, p2++) {
1834       if (*p == '\n') {
1835         *(p2++) = '\r';
1836         *p2 = '\n';
1837       }
1838       else {
1839         *p2 = *p;
1840       }
1841     }
1842     *p2 = '\0';
1843 
1844     GHOST_putClipboard((GHOST_TInt8 *)newbuf, selection);
1845     MEM_freeN(newbuf);
1846 #else
1847     GHOST_putClipboard((GHOST_TInt8 *)buf, selection);
1848 #endif
1849   }
1850 }
1851 
1852 /** \} */
1853 
1854 /* -------------------------------------------------------------------- */
1855 /** \name Progress Bar
1856  * \{ */
1857 
WM_progress_set(wmWindow * win,float progress)1858 void WM_progress_set(wmWindow *win, float progress)
1859 {
1860   /* In background mode we may have windows, but not actual GHOST windows. */
1861   if (win->ghostwin) {
1862     GHOST_SetProgressBar(win->ghostwin, progress);
1863   }
1864 }
1865 
WM_progress_clear(wmWindow * win)1866 void WM_progress_clear(wmWindow *win)
1867 {
1868   if (win->ghostwin) {
1869     GHOST_EndProgressBar(win->ghostwin);
1870   }
1871 }
1872 
1873 /** \} */
1874 
1875 /* -------------------------------------------------------------------- */
1876 /** \name Window Position/Size (internal)
1877  * \{ */
1878 
wm_window_get_position(wmWindow * win,int * r_pos_x,int * r_pos_y)1879 void wm_window_get_position(wmWindow *win, int *r_pos_x, int *r_pos_y)
1880 {
1881   *r_pos_x = win->posx;
1882   *r_pos_y = win->posy;
1883 }
1884 
wm_window_set_size(wmWindow * win,int width,int height)1885 void wm_window_set_size(wmWindow *win, int width, int height)
1886 {
1887   GHOST_SetClientSize(win->ghostwin, width, height);
1888 }
1889 
1890 /** \} */
1891 
1892 /* -------------------------------------------------------------------- */
1893 /** \name Window Depth (Raise/Lower)
1894  * \{ */
1895 
wm_window_lower(wmWindow * win)1896 void wm_window_lower(wmWindow *win)
1897 {
1898   GHOST_SetWindowOrder(win->ghostwin, GHOST_kWindowOrderBottom);
1899 }
1900 
wm_window_raise(wmWindow * win)1901 void wm_window_raise(wmWindow *win)
1902 {
1903   /* Restore window if minimized */
1904   if (GHOST_GetWindowState(win->ghostwin) == GHOST_kWindowStateMinimized) {
1905     GHOST_SetWindowState(win->ghostwin, GHOST_kWindowStateNormal);
1906   }
1907   GHOST_SetWindowOrder(win->ghostwin, GHOST_kWindowOrderTop);
1908 }
1909 
1910 /** \} */
1911 
1912 /* -------------------------------------------------------------------- */
1913 /** \name Window Buffers
1914  * \{ */
1915 
1916 /**
1917  * \brief Push rendered buffer to the screen.
1918  */
wm_window_swap_buffers(wmWindow * win)1919 void wm_window_swap_buffers(wmWindow *win)
1920 {
1921   GHOST_SwapWindowBuffers(win->ghostwin);
1922 }
1923 
wm_window_set_swap_interval(wmWindow * win,int interval)1924 void wm_window_set_swap_interval(wmWindow *win, int interval)
1925 {
1926   GHOST_SetSwapInterval(win->ghostwin, interval);
1927 }
1928 
wm_window_get_swap_interval(wmWindow * win,int * intervalOut)1929 bool wm_window_get_swap_interval(wmWindow *win, int *intervalOut)
1930 {
1931   return GHOST_GetSwapInterval(win->ghostwin, intervalOut);
1932 }
1933 
1934 /** \} */
1935 
1936 /* -------------------------------------------------------------------- */
1937 /** \name Find Window Utility
1938  *
1939  * \{ */
wm_window_desktop_pos_get(const wmWindow * win,const int screen_pos[2],int r_desk_pos[2])1940 static void wm_window_desktop_pos_get(const wmWindow *win,
1941                                       const int screen_pos[2],
1942                                       int r_desk_pos[2])
1943 {
1944   /* To desktop space. */
1945   r_desk_pos[0] = screen_pos[0] + (int)(U.pixelsize * win->posx);
1946   r_desk_pos[1] = screen_pos[1] + (int)(U.pixelsize * win->posy);
1947 }
1948 
wm_window_screen_pos_get(const wmWindow * win,const int desktop_pos[2],int r_scr_pos[2])1949 static void wm_window_screen_pos_get(const wmWindow *win,
1950                                      const int desktop_pos[2],
1951                                      int r_scr_pos[2])
1952 {
1953   /* To window space. */
1954   r_scr_pos[0] = desktop_pos[0] - (int)(U.pixelsize * win->posx);
1955   r_scr_pos[1] = desktop_pos[1] - (int)(U.pixelsize * win->posy);
1956 }
1957 
WM_window_find_under_cursor(const wmWindowManager * wm,const wmWindow * win_ignore,const wmWindow * win,const int mval[2],wmWindow ** r_win,int r_mval[2])1958 bool WM_window_find_under_cursor(const wmWindowManager *wm,
1959                                  const wmWindow *win_ignore,
1960                                  const wmWindow *win,
1961                                  const int mval[2],
1962                                  wmWindow **r_win,
1963                                  int r_mval[2])
1964 {
1965   int desk_pos[2];
1966   wm_window_desktop_pos_get(win, mval, desk_pos);
1967 
1968   /* TODO: This should follow the order of the activated windows.
1969    * The current solution is imperfect but usable in most cases. */
1970   LISTBASE_FOREACH (wmWindow *, win_iter, &wm->windows) {
1971     if (win_iter == win_ignore) {
1972       continue;
1973     }
1974 
1975     if (win_iter->windowstate == GHOST_kWindowStateMinimized) {
1976       continue;
1977     }
1978 
1979     int scr_pos[2];
1980     wm_window_screen_pos_get(win_iter, desk_pos, scr_pos);
1981 
1982     if (scr_pos[0] >= 0 && win_iter->posy >= 0 && scr_pos[0] <= WM_window_pixels_x(win_iter) &&
1983         scr_pos[1] <= WM_window_pixels_y(win_iter)) {
1984 
1985       *r_win = win_iter;
1986       copy_v2_v2_int(r_mval, scr_pos);
1987       return true;
1988     }
1989   }
1990 
1991   return false;
1992 }
1993 
WM_window_pixel_sample_read(const wmWindowManager * wm,const wmWindow * win,const int pos[2],float r_col[3])1994 void WM_window_pixel_sample_read(const wmWindowManager *wm,
1995                                  const wmWindow *win,
1996                                  const int pos[2],
1997                                  float r_col[3])
1998 {
1999   bool setup_context = wm->windrawable != win;
2000 
2001   if (setup_context) {
2002     GHOST_ActivateWindowDrawingContext(win->ghostwin);
2003     GPU_context_active_set(win->gpuctx);
2004   }
2005 
2006   GPU_frontbuffer_read_pixels(pos[0], pos[1], 1, 1, 3, GPU_DATA_FLOAT, r_col);
2007 
2008   if (setup_context) {
2009     if (wm->windrawable) {
2010       GHOST_ActivateWindowDrawingContext(wm->windrawable->ghostwin);
2011       GPU_context_active_set(wm->windrawable->gpuctx);
2012     }
2013   }
2014 }
2015 
2016 /** \} */
2017 
2018 /* -------------------------------------------------------------------- */
2019 /** \name Window Screen Shot Utility
2020  *
2021  * Include here since it can involve low level buffer switching.
2022  *
2023  * \{ */
2024 
WM_window_pixels_read(wmWindowManager * wm,wmWindow * win,int r_size[2])2025 uint *WM_window_pixels_read(wmWindowManager *wm, wmWindow *win, int r_size[2])
2026 {
2027   bool setup_context = wm->windrawable != win;
2028 
2029   if (setup_context) {
2030     GHOST_ActivateWindowDrawingContext(win->ghostwin);
2031     GPU_context_active_set(win->gpuctx);
2032   }
2033 
2034   r_size[0] = WM_window_pixels_x(win);
2035   r_size[1] = WM_window_pixels_y(win);
2036   const uint rect_len = r_size[0] * r_size[1];
2037   uint *rect = MEM_mallocN(sizeof(*rect) * rect_len, __func__);
2038 
2039   GPU_frontbuffer_read_pixels(0, 0, r_size[0], r_size[1], 4, GPU_DATA_UNSIGNED_BYTE, rect);
2040 
2041   if (setup_context) {
2042     if (wm->windrawable) {
2043       GHOST_ActivateWindowDrawingContext(wm->windrawable->ghostwin);
2044       GPU_context_active_set(wm->windrawable->gpuctx);
2045     }
2046   }
2047 
2048   /* Clear alpha, it is not set to a meaningful value in OpenGL. */
2049   uchar *cp = (uchar *)rect;
2050   uint i;
2051   for (i = 0, cp += 3; i < rect_len; i++, cp += 4) {
2052     *cp = 0xff;
2053   }
2054   return (uint *)rect;
2055 }
2056 
2057 /** \} */
2058 
2059 /* -------------------------------------------------------------------- */
2060 /** \name Initial Window State API
2061  * \{ */
2062 
2063 /* called whem no ghost system was initialized */
WM_init_state_size_set(int stax,int stay,int sizx,int sizy)2064 void WM_init_state_size_set(int stax, int stay, int sizx, int sizy)
2065 {
2066   wm_init_state.start_x = stax; /* left hand pos */
2067   wm_init_state.start_y = stay; /* bottom pos */
2068   wm_init_state.size_x = sizx < 640 ? 640 : sizx;
2069   wm_init_state.size_y = sizy < 480 ? 480 : sizy;
2070   wm_init_state.override_flag |= WIN_OVERRIDE_GEOM;
2071 }
2072 
2073 /* for borderless and border windows set from command-line */
WM_init_state_fullscreen_set(void)2074 void WM_init_state_fullscreen_set(void)
2075 {
2076   wm_init_state.windowstate = GHOST_kWindowStateFullScreen;
2077   wm_init_state.override_flag |= WIN_OVERRIDE_WINSTATE;
2078 }
2079 
WM_init_state_normal_set(void)2080 void WM_init_state_normal_set(void)
2081 {
2082   wm_init_state.windowstate = GHOST_kWindowStateNormal;
2083   wm_init_state.override_flag |= WIN_OVERRIDE_WINSTATE;
2084 }
2085 
WM_init_state_maximized_set(void)2086 void WM_init_state_maximized_set(void)
2087 {
2088   wm_init_state.windowstate = GHOST_kWindowStateMaximized;
2089   wm_init_state.override_flag |= WIN_OVERRIDE_WINSTATE;
2090 }
2091 
WM_init_window_focus_set(bool do_it)2092 void WM_init_window_focus_set(bool do_it)
2093 {
2094   wm_init_state.window_focus = do_it;
2095 }
2096 
WM_init_native_pixels(bool do_it)2097 void WM_init_native_pixels(bool do_it)
2098 {
2099   wm_init_state.native_pixels = do_it;
2100 }
2101 
2102 /** \} */
2103 
2104 /* -------------------------------------------------------------------- */
2105 /** \name Cursor API
2106  * \{ */
2107 
WM_init_tablet_api(void)2108 void WM_init_tablet_api(void)
2109 {
2110   if (g_system) {
2111     switch (U.tablet_api) {
2112       case USER_TABLET_NATIVE:
2113         GHOST_SetTabletAPI(g_system, GHOST_kTabletNative);
2114         break;
2115       case USER_TABLET_WINTAB:
2116         GHOST_SetTabletAPI(g_system, GHOST_kTabletWintab);
2117         break;
2118       case USER_TABLET_AUTOMATIC:
2119       default:
2120         GHOST_SetTabletAPI(g_system, GHOST_kTabletAutomatic);
2121         break;
2122     }
2123   }
2124 }
2125 
2126 /* This function requires access to the GHOST_SystemHandle (g_system) */
WM_cursor_warp(wmWindow * win,int x,int y)2127 void WM_cursor_warp(wmWindow *win, int x, int y)
2128 {
2129   if (win && win->ghostwin) {
2130     int oldx = x, oldy = y;
2131 
2132     wm_cursor_position_to_ghost(win, &x, &y);
2133     GHOST_SetCursorPosition(g_system, x, y);
2134 
2135     win->eventstate->prevx = oldx;
2136     win->eventstate->prevy = oldy;
2137 
2138     win->eventstate->x = oldx;
2139     win->eventstate->y = oldy;
2140   }
2141 }
2142 
2143 /**
2144  * Set x, y to values we can actually position the cursor to.
2145  */
WM_cursor_compatible_xy(wmWindow * win,int * x,int * y)2146 void WM_cursor_compatible_xy(wmWindow *win, int *x, int *y)
2147 {
2148   float f = GHOST_GetNativePixelSize(win->ghostwin);
2149   if (f != 1.0f) {
2150     *x = (int)(*x / f) * f;
2151     *y = (int)(*y / f) * f;
2152   }
2153 }
2154 
2155 /** \} */
2156 
2157 /* -------------------------------------------------------------------- */
2158 /** \name Window Size (public)
2159  * \{ */
2160 
2161 /**
2162  * Support for native pixel size
2163  *
2164  * \note macOS retina opens window in size X, but it has up to 2 x more pixels.
2165  */
WM_window_pixels_x(const wmWindow * win)2166 int WM_window_pixels_x(const wmWindow *win)
2167 {
2168   float f = GHOST_GetNativePixelSize(win->ghostwin);
2169 
2170   return (int)(f * (float)win->sizex);
2171 }
WM_window_pixels_y(const wmWindow * win)2172 int WM_window_pixels_y(const wmWindow *win)
2173 {
2174   float f = GHOST_GetNativePixelSize(win->ghostwin);
2175 
2176   return (int)(f * (float)win->sizey);
2177 }
2178 
2179 /**
2180  * Get boundaries usable by all window contents, including global areas.
2181  */
WM_window_rect_calc(const wmWindow * win,rcti * r_rect)2182 void WM_window_rect_calc(const wmWindow *win, rcti *r_rect)
2183 {
2184   BLI_rcti_init(r_rect, 0, WM_window_pixels_x(win), 0, WM_window_pixels_y(win));
2185 }
2186 /**
2187  * Get boundaries usable by screen-layouts, excluding global areas.
2188  * \note Depends on U.dpi_fac. Should that be outdated, call #WM_window_set_dpi first.
2189  */
WM_window_screen_rect_calc(const wmWindow * win,rcti * r_rect)2190 void WM_window_screen_rect_calc(const wmWindow *win, rcti *r_rect)
2191 {
2192   rcti window_rect, screen_rect;
2193 
2194   WM_window_rect_calc(win, &window_rect);
2195   screen_rect = window_rect;
2196 
2197   /* Subtract global areas from screen rectangle. */
2198   LISTBASE_FOREACH (ScrArea *, global_area, &win->global_areas.areabase) {
2199     int height = ED_area_global_size_y(global_area) - 1;
2200 
2201     if (global_area->global->flag & GLOBAL_AREA_IS_HIDDEN) {
2202       continue;
2203     }
2204 
2205     switch (global_area->global->align) {
2206       case GLOBAL_AREA_ALIGN_TOP:
2207         screen_rect.ymax -= height;
2208         break;
2209       case GLOBAL_AREA_ALIGN_BOTTOM:
2210         screen_rect.ymin += height;
2211         break;
2212       default:
2213         BLI_assert(0);
2214         break;
2215     }
2216   }
2217 
2218   BLI_assert(BLI_rcti_is_valid(&screen_rect));
2219 
2220   *r_rect = screen_rect;
2221 }
2222 
WM_window_is_fullscreen(const wmWindow * win)2223 bool WM_window_is_fullscreen(const wmWindow *win)
2224 {
2225   return win->windowstate == GHOST_kWindowStateFullScreen;
2226 }
2227 
WM_window_is_maximized(const wmWindow * win)2228 bool WM_window_is_maximized(const wmWindow *win)
2229 {
2230   return win->windowstate == GHOST_kWindowStateMaximized;
2231 }
2232 
2233 /** \} */
2234 
2235 /* -------------------------------------------------------------------- */
2236 /** \name Window Screen/Scene/WorkSpaceViewLayer API
2237  * \{ */
2238 
2239 /**
2240  * Some editor data may need to be synced with scene data (3D View camera and layers).
2241  * This function ensures data is synced for editors
2242  * in visible workspaces and their visible layouts.
2243  */
WM_windows_scene_data_sync(const ListBase * win_lb,Scene * scene)2244 void WM_windows_scene_data_sync(const ListBase *win_lb, Scene *scene)
2245 {
2246   LISTBASE_FOREACH (wmWindow *, win, win_lb) {
2247     if (WM_window_get_active_scene(win) == scene) {
2248       ED_workspace_scene_data_sync(win->workspace_hook, scene);
2249     }
2250   }
2251 }
2252 
WM_windows_scene_get_from_screen(const wmWindowManager * wm,const bScreen * screen)2253 Scene *WM_windows_scene_get_from_screen(const wmWindowManager *wm, const bScreen *screen)
2254 {
2255   LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
2256     if (WM_window_get_active_screen(win) == screen) {
2257       return WM_window_get_active_scene(win);
2258     }
2259   }
2260 
2261   return NULL;
2262 }
2263 
WM_windows_workspace_get_from_screen(const wmWindowManager * wm,const bScreen * screen)2264 WorkSpace *WM_windows_workspace_get_from_screen(const wmWindowManager *wm, const bScreen *screen)
2265 {
2266   LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
2267     if (WM_window_get_active_screen(win) == screen) {
2268       return WM_window_get_active_workspace(win);
2269     }
2270   }
2271   return NULL;
2272 }
2273 
WM_window_get_active_scene(const wmWindow * win)2274 Scene *WM_window_get_active_scene(const wmWindow *win)
2275 {
2276   return win->scene;
2277 }
2278 
2279 /**
2280  * \warning Only call outside of area/region loops
2281  */
WM_window_set_active_scene(Main * bmain,bContext * C,wmWindow * win,Scene * scene)2282 void WM_window_set_active_scene(Main *bmain, bContext *C, wmWindow *win, Scene *scene)
2283 {
2284   wmWindowManager *wm = CTX_wm_manager(C);
2285   wmWindow *win_parent = (win->parent) ? win->parent : win;
2286   bool changed = false;
2287 
2288   /* Set scene in parent and its child windows. */
2289   if (win_parent->scene != scene) {
2290     ED_screen_scene_change(C, win_parent, scene);
2291     changed = true;
2292   }
2293 
2294   LISTBASE_FOREACH (wmWindow *, win_child, &wm->windows) {
2295     if (win_child->parent == win_parent && win_child->scene != scene) {
2296       ED_screen_scene_change(C, win_child, scene);
2297       changed = true;
2298     }
2299   }
2300 
2301   if (changed) {
2302     /* Update depsgraph and renderers for scene change. */
2303     ViewLayer *view_layer = WM_window_get_active_view_layer(win_parent);
2304     ED_scene_change_update(bmain, scene, view_layer);
2305 
2306     /* Complete redraw. */
2307     WM_event_add_notifier(C, NC_WINDOW, NULL);
2308   }
2309 }
2310 
WM_window_get_active_view_layer(const wmWindow * win)2311 ViewLayer *WM_window_get_active_view_layer(const wmWindow *win)
2312 {
2313   Scene *scene = WM_window_get_active_scene(win);
2314   if (scene == NULL) {
2315     return NULL;
2316   }
2317 
2318   ViewLayer *view_layer = BKE_view_layer_find(scene, win->view_layer_name);
2319   if (view_layer) {
2320     return view_layer;
2321   }
2322 
2323   view_layer = BKE_view_layer_default_view(scene);
2324   if (view_layer) {
2325     WM_window_set_active_view_layer((wmWindow *)win, view_layer);
2326   }
2327 
2328   return view_layer;
2329 }
2330 
WM_window_set_active_view_layer(wmWindow * win,ViewLayer * view_layer)2331 void WM_window_set_active_view_layer(wmWindow *win, ViewLayer *view_layer)
2332 {
2333   BLI_assert(BKE_view_layer_find(WM_window_get_active_scene(win), view_layer->name) != NULL);
2334   Main *bmain = G_MAIN;
2335 
2336   wmWindowManager *wm = bmain->wm.first;
2337   wmWindow *win_parent = (win->parent) ? win->parent : win;
2338 
2339   /* Set view layer in parent and child windows. */
2340   LISTBASE_FOREACH (wmWindow *, win_iter, &wm->windows) {
2341     if ((win_iter == win_parent) || (win_iter->parent == win_parent)) {
2342       STRNCPY(win_iter->view_layer_name, view_layer->name);
2343       bScreen *screen = BKE_workspace_active_screen_get(win_iter->workspace_hook);
2344       ED_render_view_layer_changed(bmain, screen);
2345     }
2346   }
2347 }
2348 
WM_window_ensure_active_view_layer(wmWindow * win)2349 void WM_window_ensure_active_view_layer(wmWindow *win)
2350 {
2351   /* Update layer name is correct after scene changes, load without UI, etc. */
2352   Scene *scene = WM_window_get_active_scene(win);
2353 
2354   if (scene && BKE_view_layer_find(scene, win->view_layer_name) == NULL) {
2355     ViewLayer *view_layer = BKE_view_layer_default_view(scene);
2356     STRNCPY(win->view_layer_name, view_layer->name);
2357   }
2358 }
2359 
WM_window_get_active_workspace(const wmWindow * win)2360 WorkSpace *WM_window_get_active_workspace(const wmWindow *win)
2361 {
2362   return BKE_workspace_active_get(win->workspace_hook);
2363 }
2364 
WM_window_set_active_workspace(bContext * C,wmWindow * win,WorkSpace * workspace)2365 void WM_window_set_active_workspace(bContext *C, wmWindow *win, WorkSpace *workspace)
2366 {
2367   wmWindowManager *wm = CTX_wm_manager(C);
2368   wmWindow *win_parent = (win->parent) ? win->parent : win;
2369 
2370   ED_workspace_change(workspace, C, wm, win);
2371 
2372   LISTBASE_FOREACH (wmWindow *, win_child, &wm->windows) {
2373     if (win_child->parent == win_parent) {
2374       bScreen *screen = WM_window_get_active_screen(win_child);
2375       /* Don't change temporary screens, they only serve a single purpose. */
2376       if (screen->temp) {
2377         continue;
2378       }
2379       ED_workspace_change(workspace, C, wm, win_child);
2380     }
2381   }
2382 }
2383 
WM_window_get_active_layout(const wmWindow * win)2384 WorkSpaceLayout *WM_window_get_active_layout(const wmWindow *win)
2385 {
2386   const WorkSpace *workspace = WM_window_get_active_workspace(win);
2387   return (LIKELY(workspace != NULL) ? BKE_workspace_active_layout_get(win->workspace_hook) : NULL);
2388 }
WM_window_set_active_layout(wmWindow * win,WorkSpace * workspace,WorkSpaceLayout * layout)2389 void WM_window_set_active_layout(wmWindow *win, WorkSpace *workspace, WorkSpaceLayout *layout)
2390 {
2391   BKE_workspace_active_layout_set(win->workspace_hook, win->winid, workspace, layout);
2392 }
2393 
2394 /**
2395  * Get the active screen of the active workspace in \a win.
2396  */
WM_window_get_active_screen(const wmWindow * win)2397 bScreen *WM_window_get_active_screen(const wmWindow *win)
2398 {
2399   const WorkSpace *workspace = WM_window_get_active_workspace(win);
2400   /* May be NULL in rare cases like closing Blender */
2401   return (LIKELY(workspace != NULL) ? BKE_workspace_active_screen_get(win->workspace_hook) : NULL);
2402 }
WM_window_set_active_screen(wmWindow * win,WorkSpace * workspace,bScreen * screen)2403 void WM_window_set_active_screen(wmWindow *win, WorkSpace *workspace, bScreen *screen)
2404 {
2405   BKE_workspace_active_screen_set(win->workspace_hook, win->winid, workspace, screen);
2406 }
2407 
WM_window_is_temp_screen(const wmWindow * win)2408 bool WM_window_is_temp_screen(const wmWindow *win)
2409 {
2410   const bScreen *screen = WM_window_get_active_screen(win);
2411   return (screen && screen->temp != 0);
2412 }
2413 
2414 /** \} */
2415 
2416 /* -------------------------------------------------------------------- */
2417 /** \name Window IME API
2418  * \{ */
2419 
2420 #ifdef WITH_INPUT_IME
2421 /**
2422  * \note Keep in mind #wm_window_IME_begin is also used to reposition the IME window.
2423  */
wm_window_IME_begin(wmWindow * win,int x,int y,int w,int h,bool complete)2424 void wm_window_IME_begin(wmWindow *win, int x, int y, int w, int h, bool complete)
2425 {
2426   BLI_assert(win);
2427 
2428   GHOST_BeginIME(win->ghostwin, x, win->sizey - y, w, h, complete);
2429 }
2430 
wm_window_IME_end(wmWindow * win)2431 void wm_window_IME_end(wmWindow *win)
2432 {
2433   BLI_assert(win && win->ime_data);
2434 
2435   GHOST_EndIME(win->ghostwin);
2436   win->ime_data = NULL;
2437 }
2438 #endif /* WITH_INPUT_IME */
2439 
2440 /** \} */
2441 
2442 /* -------------------------------------------------------------------- */
2443 /** \name Direct OpenGL Context Management
2444  * \{ */
2445 
WM_opengl_context_create(void)2446 void *WM_opengl_context_create(void)
2447 {
2448   /* On Windows there is a problem creating contexts that share lists
2449    * from one context that is current in another thread.
2450    * So we should call this function only on the main thread.
2451    */
2452   BLI_assert(BLI_thread_is_main());
2453   BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get());
2454 
2455   GHOST_GLSettings glSettings = {0};
2456   if (G.debug & G_DEBUG_GPU) {
2457     glSettings.flags |= GHOST_glDebugContext;
2458   }
2459   return GHOST_CreateOpenGLContext(g_system, glSettings);
2460 }
2461 
WM_opengl_context_dispose(void * context)2462 void WM_opengl_context_dispose(void *context)
2463 {
2464   BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get());
2465   GHOST_DisposeOpenGLContext(g_system, (GHOST_ContextHandle)context);
2466 }
2467 
WM_opengl_context_activate(void * context)2468 void WM_opengl_context_activate(void *context)
2469 {
2470   BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get());
2471   GHOST_ActivateOpenGLContext((GHOST_ContextHandle)context);
2472 }
2473 
WM_opengl_context_release(void * context)2474 void WM_opengl_context_release(void *context)
2475 {
2476   BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get());
2477   GHOST_ReleaseOpenGLContext((GHOST_ContextHandle)context);
2478 }
2479 
WM_ghost_show_message_box(const char * title,const char * message,const char * help_label,const char * continue_label,const char * link,GHOST_DialogOptions dialog_options)2480 void WM_ghost_show_message_box(const char *title,
2481                                const char *message,
2482                                const char *help_label,
2483                                const char *continue_label,
2484                                const char *link,
2485                                GHOST_DialogOptions dialog_options)
2486 {
2487   BLI_assert(g_system);
2488   GHOST_ShowMessageBox(g_system, title, message, help_label, continue_label, link, dialog_options);
2489 }
2490 /** \} */
2491