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