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.
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.
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.
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.
24 * Window management, wrap GHOST.
32 #include "DNA_listBase.h"
33 #include "DNA_screen_types.h"
34 #include "DNA_windowmanager_types.h"
35 #include "DNA_workspace_types.h"
37 #include "MEM_guardedalloc.h"
39 #include "GHOST_C-api.h"
42 #include "BLI_blenlib.h"
43 #include "BLI_utildefines.h"
45 #include "BLT_translation.h"
47 #include "BKE_blender.h"
48 #include "BKE_context.h"
49 #include "BKE_global.h"
50 #include "BKE_icons.h"
51 #include "BKE_layer.h"
53 #include "BKE_screen.h"
54 #include "BKE_workspace.h"
56 #include "RNA_access.h"
57 #include "RNA_define.h"
58 #include "RNA_enum_types.h"
64 #include "wm_window.h"
65 #include "wm_event_system.h"
67 #include "ED_anim_api.h"
69 #include "ED_screen.h"
70 #include "ED_fileselect.h"
72 #include "UI_interface.h"
73 #include "UI_interface_icons.h"
77 #include "GPU_batch.h"
78 #include "GPU_batch_presets.h"
80 #include "GPU_extensions.h"
81 #include "GPU_framebuffer.h"
82 #include "GPU_init_exit.h"
83 #include "GPU_immediate.h"
84 #include "GPU_material.h"
85 #include "GPU_texture.h"
86 #include "GPU_context.h"
89 #include "UI_resources.h"
93 # include "BLI_threads.h"
96 /* the global to talk to ghost */
97 static GHOST_SystemHandle g_system = NULL;
99 typedef enum eWinOverrideFlag {
100 WIN_OVERRIDE_GEOM = (1 << 0),
101 WIN_OVERRIDE_WINSTATE = (1 << 1),
104 #define GHOST_WINDOW_STATE_DEFAULT GHOST_kWindowStateMaximized
107 * Override defaults or startup file when #eWinOverrideFlag is set.
108 * These values are typically set by command line arguments.
110 static struct WMInitStruct {
111 /* window geometry */
113 int start_x, start_y;
116 eWinOverrideFlag override_flag;
121 .windowstate = GHOST_WINDOW_STATE_DEFAULT,
122 .window_focus = true,
123 .native_pixels = true,
126 /* ******** win open & close ************ */
128 static void wm_window_set_drawable(wmWindowManager *wm, wmWindow *win, bool activate);
129 static int wm_window_timer(const bContext *C);
131 /* XXX this one should correctly check for apple top header...
132 * done for Cocoa : returns window contents (and not frame) max size*/
133 void wm_get_screensize(int *r_width, int *r_height)
135 unsigned int uiwidth;
136 unsigned int uiheight;
138 GHOST_GetMainDisplayDimensions(g_system, &uiwidth, &uiheight);
140 *r_height = uiheight;
143 /* size of all screens (desktop), useful since the mouse is bound by this */
144 void wm_get_desktopsize(int *r_width, int *r_height)
146 unsigned int uiwidth;
147 unsigned int uiheight;
149 GHOST_GetAllDisplayDimensions(g_system, &uiwidth, &uiheight);
151 *r_height = uiheight;
154 /* keeps offset and size within monitor bounds */
155 /* XXX solve dual screen... */
156 static void wm_window_check_position(rcti *rect)
158 int width, height, d;
160 wm_get_screensize(&width, &height);
162 if (rect->xmin < 0) {
163 rect->xmax -= rect->xmin;
166 if (rect->ymin < 0) {
167 rect->ymax -= rect->ymin;
170 if (rect->xmax > width) {
171 d = rect->xmax - width;
175 if (rect->ymax > height) {
176 d = rect->ymax - height;
181 if (rect->xmin < 0) rect->xmin = 0;
182 if (rect->ymin < 0) rect->ymin = 0;
185 static void wm_ghostwindow_destroy(wmWindowManager *wm, wmWindow *win)
188 /* Prevents non-drawable state of main windows (bugs #22967,
189 * #25071 and possibly #22477 too). Always clear it even if
190 * this window was not the drawable one, because we mess with
191 * drawing context to discard the GW context. */
192 wm_window_clear_drawable(wm);
194 if (win == wm->winactive) {
195 wm->winactive = NULL;
198 /* We need this window's opengl context active to discard it. */
199 GHOST_ActivateWindowDrawingContext(win->ghostwin);
200 GPU_context_active_set(win->gpuctx);
202 /* Delete local gpu context. */
203 GPU_context_discard(win->gpuctx);
205 GHOST_DisposeWindow(g_system, win->ghostwin);
206 win->ghostwin = NULL;
211 /* including window itself, C can be NULL.
212 * ED_screen_exit should have been called */
213 void wm_window_free(bContext *C, wmWindowManager *wm, wmWindow *win)
215 wmTimer *wt, *wtnext;
219 WM_event_remove_handlers(C, &win->handlers);
220 WM_event_remove_handlers(C, &win->modalhandlers);
222 if (CTX_wm_window(C) == win)
223 CTX_wm_window_set(C, NULL);
226 BKE_screen_area_map_free(&win->global_areas);
228 /* end running jobs, a job end also removes its timer */
229 for (wt = wm->timers.first; wt; wt = wtnext) {
231 if (wt->win == win && wt->event_type == TIMERJOBS)
232 wm_jobs_timer_ended(wm, wt);
235 /* timer removing, need to call this api function */
236 for (wt = wm->timers.first; wt; wt = wtnext) {
239 WM_event_remove_timer(wm, win, wt);
242 if (win->eventstate) MEM_freeN(win->eventstate);
244 if (win->cursor_keymap_status) {
245 MEM_freeN(win->cursor_keymap_status);
248 wm_event_free_all(win);
250 wm_ghostwindow_destroy(wm, win);
252 BKE_workspace_instance_hook_free(G_MAIN, win->workspace_hook);
253 MEM_freeN(win->stereo3d_format);
258 static int find_free_winid(wmWindowManager *wm)
263 for (win = wm->windows.first; win; win = win->next)
264 if (id <= win->winid)
270 /* don't change context itself */
271 wmWindow *wm_window_new(bContext *C, wmWindow *parent)
273 Main *bmain = CTX_data_main(C);
274 wmWindowManager *wm = CTX_wm_manager(C);
275 wmWindow *win = MEM_callocN(sizeof(wmWindow), "window");
277 BLI_addtail(&wm->windows, win);
278 win->winid = find_free_winid(wm);
280 win->parent = (parent && parent->parent) ? parent->parent : parent;
281 win->stereo3d_format = MEM_callocN(sizeof(Stereo3dFormat), "Stereo 3D Format (window)");
282 win->workspace_hook = BKE_workspace_instance_hook_create(bmain);
287 /* part of wm_window.c api */
288 wmWindow *wm_window_copy(bContext *C, wmWindow *win_src, const bool duplicate_layout, const bool child)
290 Main *bmain = CTX_data_main(C);
291 wmWindow *win_parent = (child) ? win_src : win_src->parent;
292 wmWindow *win_dst = wm_window_new(C, win_parent);
293 WorkSpace *workspace = WM_window_get_active_workspace(win_src);
294 WorkSpaceLayout *layout_old = WM_window_get_active_layout(win_src);
295 WorkSpaceLayout *layout_new;
297 win_dst->posx = win_src->posx + 10;
298 win_dst->posy = win_src->posy;
299 win_dst->sizex = win_src->sizex;
300 win_dst->sizey = win_src->sizey;
302 win_dst->scene = win_src->scene;
303 STRNCPY(win_dst->view_layer_name, win_src->view_layer_name);
304 BKE_workspace_active_set(win_dst->workspace_hook, workspace);
305 layout_new = duplicate_layout ? ED_workspace_layout_duplicate(bmain, workspace, layout_old, win_dst) : layout_old;
306 BKE_workspace_hook_layout_for_workspace_set(win_dst->workspace_hook, workspace, layout_new);
308 *win_dst->stereo3d_format = *win_src->stereo3d_format;
314 * A higher level version of copy that tests the new window can be added.
315 * (called from the operator directly)
317 wmWindow *wm_window_copy_test(bContext *C, wmWindow *win_src, const bool duplicate_layout, const bool child)
319 wmWindowManager *wm = CTX_wm_manager(C);
322 win_dst = wm_window_copy(C, win_src, duplicate_layout, child);
326 if (win_dst->ghostwin) {
327 WM_event_add_notifier(C, NC_WINDOW | NA_ADDED, NULL);
331 wm_window_close(C, wm, win_dst);
337 /* -------------------------------------------------------------------- */
338 /** \name Quit Confirmation Dialog
341 /** Cancel quitting and close the dialog */
342 static void wm_block_confirm_quit_cancel(bContext *C, void *arg_block, void *UNUSED(arg))
344 wmWindow *win = CTX_wm_window(C);
345 UI_popup_block_close(C, win, arg_block);
348 /** Discard the file changes and quit */
349 static void wm_block_confirm_quit_discard(bContext *C, void *arg_block, void *UNUSED(arg))
351 wmWindow *win = CTX_wm_window(C);
352 UI_popup_block_close(C, win, arg_block);
356 /* Save changes and quit */
357 static void wm_block_confirm_quit_save(bContext *C, void *arg_block, void *UNUSED(arg))
359 PointerRNA props_ptr;
360 wmWindow *win = CTX_wm_window(C);
362 UI_popup_block_close(C, win, arg_block);
364 wmOperatorType *ot = WM_operatortype_find("WM_OT_save_mainfile", false);
366 WM_operator_properties_create_ptr(&props_ptr, ot);
367 RNA_boolean_set(&props_ptr, "exit", true);
368 /* No need for second confirmation popup. */
369 RNA_boolean_set(&props_ptr, "check_existing", false);
370 WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr);
371 WM_operator_properties_free(&props_ptr);
375 /* Build the confirm dialog UI */
376 static uiBlock *block_create_confirm_quit(struct bContext *C, struct ARegion *ar, void *UNUSED(arg1))
378 Main *bmain = CTX_data_main(C);
380 uiStyle *style = UI_style_get();
381 uiBlock *block = UI_block_begin(C, ar, "confirm_quit_popup", UI_EMBOSS);
383 UI_block_flag_enable(block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_LOOP | UI_BLOCK_NO_WIN_CLIP | UI_BLOCK_NUMSELECT);
384 UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
385 UI_block_emboss_set(block, UI_EMBOSS);
387 uiLayout *layout = UI_block_layout(
388 block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 10, 2, U.widget_unit * 24, U.widget_unit * 6, 0, style);
390 /* Text and some vertical space */
393 if (BKE_main_blendfile_path(bmain)[0] == '\0') {
394 message = BLI_strdup(IFACE_("This file has not been saved yet. Save before closing?"));
397 const char *basename = BLI_path_basename(BKE_main_blendfile_path(bmain));
398 message = BLI_sprintfN(IFACE_("Save changes to \"%s\" before closing?"), basename);
400 uiItemL(layout, message, ICON_ERROR);
411 uiLayout *split = uiLayoutSplit(layout, 0.0f, true);
413 uiLayout *col = uiLayoutColumn(split, false);
415 but = uiDefIconTextBut(
416 block, UI_BTYPE_BUT, 0, ICON_SCREEN_BACK, IFACE_("Cancel"), 0, 0, 0, UI_UNIT_Y,
417 NULL, 0, 0, 0, 0, TIP_("Do not quit"));
418 UI_but_func_set(but, wm_block_confirm_quit_cancel, block, NULL);
420 /* empty space between buttons */
421 col = uiLayoutColumn(split, false);
424 col = uiLayoutColumn(split, 1);
425 but = uiDefIconTextBut(
426 block, UI_BTYPE_BUT, 0, ICON_CANCEL, IFACE_("Discard Changes"), 0, 0, 50, UI_UNIT_Y,
427 NULL, 0, 0, 0, 0, TIP_("Discard changes and quit"));
428 UI_but_func_set(but, wm_block_confirm_quit_discard, block, NULL);
430 col = uiLayoutColumn(split, 1);
431 but = uiDefIconTextBut(
432 block, UI_BTYPE_BUT, 0, ICON_FILE_TICK, IFACE_("Save & Quit"), 0, 0, 50, UI_UNIT_Y,
433 NULL, 0, 0, 0, 0, TIP_("Save and quit"));
434 UI_but_func_set(but, wm_block_confirm_quit_save, block, NULL);
436 UI_block_bounds_set_centered(block, 10);
443 * Call the confirm dialog on quitting. It's displayed in the context window so
444 * caller should set it as desired.
446 static void wm_confirm_quit(bContext *C)
448 wmWindow *win = CTX_wm_window(C);
450 if (GHOST_SupportsNativeDialogs() == 0) {
451 if (!UI_popup_block_name_exists(C, "confirm_quit_popup")) {
452 UI_popup_block_invoke(C, block_create_confirm_quit, NULL);
455 else if (GHOST_confirmQuit(win->ghostwin)) {
456 wm_exit_schedule_delayed(C);
461 * Call the quit confirmation prompt or exit directly if needed. The use can
462 * still cancel via the confirmation popup. Also, this may not quit Blender
463 * immediately, but rather schedule the closing.
465 * \param win: The window to show the confirmation popup/window in.
467 void wm_quit_with_optional_confirmation_prompt(bContext *C, wmWindow *win)
469 wmWindowManager *wm = CTX_wm_manager(C);
470 wmWindow *win_ctx = CTX_wm_window(C);
472 /* The popup will be displayed in the context window which may not be set
473 * here (this function gets called outside of normal event handling loop). */
474 CTX_wm_window_set(C, win);
476 if (U.uiflag & USER_SAVE_PROMPT) {
477 if (!wm->file_saved && !G.background) {
481 wm_exit_schedule_delayed(C);
485 wm_exit_schedule_delayed(C);
488 CTX_wm_window_set(C, win_ctx);
493 /* this is event from ghost, or exit-blender op */
494 void wm_window_close(bContext *C, wmWindowManager *wm, wmWindow *win)
496 /* First check if there is another main window remaining. */
498 for (win_other = wm->windows.first; win_other; win_other = win_other->next) {
499 if (win_other != win &&
500 win_other->parent == NULL &&
501 !WM_window_is_temp_screen(win_other))
507 if (win->parent == NULL && win_other == NULL) {
508 wm_quit_with_optional_confirmation_prompt(C, win);
512 /* close child windows */
513 for (wmWindow *win_child = wm->windows.first; win_child; win_child = win_child->next) {
514 if (win_child->parent == win) {
515 wm_window_close(C, wm, win_child);
519 bScreen *screen = WM_window_get_active_screen(win);
520 WorkSpace *workspace = WM_window_get_active_workspace(win);
521 WorkSpaceLayout *layout = BKE_workspace_active_layout_get(win->workspace_hook);
523 BLI_remlink(&wm->windows, win);
525 CTX_wm_window_set(C, win); /* needed by handlers */
526 WM_event_remove_handlers(C, &win->handlers);
527 WM_event_remove_handlers(C, &win->modalhandlers);
529 /* for regular use this will _never_ be NULL,
530 * however we may be freeing an improperly initialized window. */
532 ED_screen_exit(C, win, screen);
535 wm_window_free(C, wm, win);
537 /* if temp screen, delete it after window free (it stops jobs that can access it) */
538 if (screen && screen->temp) {
539 Main *bmain = CTX_data_main(C);
541 BLI_assert(BKE_workspace_layout_screen_get(layout) == screen);
542 BKE_workspace_layout_remove(bmain, workspace, layout);
546 void wm_window_title(wmWindowManager *wm, wmWindow *win)
548 if (WM_window_is_temp_screen(win)) {
549 /* nothing to do for 'temp' windows,
550 * because WM_window_open_temp always sets window title */
552 else if (win->ghostwin) {
553 /* this is set to 1 if you don't have startup.blend open */
554 if (G.save_over && BKE_main_blendfile_path_from_global()[0]) {
555 char str[sizeof(((Main *)NULL)->name) + 24];
556 BLI_snprintf(str, sizeof(str), "Blender%s [%s%s]", wm->file_saved ? "" : "*",
557 BKE_main_blendfile_path_from_global(),
558 G_MAIN->recovered ? " (Recovered)" : "");
559 GHOST_SetTitle(win->ghostwin, str);
562 GHOST_SetTitle(win->ghostwin, "Blender");
564 /* Informs GHOST of unsaved changes, to set window modified visual indicator (MAC OS X)
565 * and to give hint of unsaved changes for a user warning mechanism
566 * in case of OS application terminate request (e.g. OS Shortcut Alt+F4, Cmd+Q, (...), or session end) */
567 GHOST_SetWindowModifiedState(win->ghostwin, (GHOST_TUns8) !wm->file_saved);
572 void WM_window_set_dpi(wmWindow *win)
574 float auto_dpi = GHOST_GetDPIHint(win->ghostwin);
576 /* Clamp auto DPI to 96, since our font/interface drawing does not work well
577 * with lower sizes. The main case we are interested in supporting is higher
578 * DPI. If a smaller UI is desired it is still possible to adjust UI scale. */
579 auto_dpi = max_ff(auto_dpi, 96.0f);
581 /* Lazily init UI scale size, preserving backwards compatibility by
582 * computing UI scale from ratio of previous DPI and auto DPI */
583 if (U.ui_scale == 0) {
584 int virtual_pixel = (U.virtual_pixel == VIRTUAL_PIXEL_NATIVE) ? 1 : 2;
587 U.ui_scale = virtual_pixel;
590 U.ui_scale = (virtual_pixel * U.dpi * 96.0f) / (auto_dpi * 72.0f);
593 CLAMP(U.ui_scale, 0.25f, 4.0f);
596 /* Blender's UI drawing assumes DPI 72 as a good default following macOS
597 * while Windows and Linux use DPI 96. GHOST assumes a default 96 so we
598 * remap the DPI to Blender's convention. */
599 auto_dpi *= GHOST_GetNativePixelSize(win->ghostwin);
600 int dpi = auto_dpi * U.ui_scale * (72.0 / 96.0f);
602 /* Automatically set larger pixel size for high DPI. */
603 int pixelsize = max_ii(1, (int)(dpi / 64));
604 /* User adjustment for pixel size. */
605 pixelsize = max_ii(1, pixelsize + U.ui_line_width);
607 /* Set user preferences globals for drawing, and for forward compatibility. */
608 U.pixelsize = pixelsize;
609 U.dpi = dpi / pixelsize;
610 U.virtual_pixel = (pixelsize == 1) ? VIRTUAL_PIXEL_NATIVE : VIRTUAL_PIXEL_DOUBLE;
611 U.dpi_fac = ((U.pixelsize * (float)U.dpi) / 72.0f);
613 /* Set user preferences globals for drawing, and for forward compatibility. */
614 U.widget_unit = (U.pixelsize * U.dpi * 20 + 36) / 72;
615 /* If line thickness differs from scaling factor then adjustments need to be made */
616 U.widget_unit += 2 * ((int)U.pixelsize - (int)U.dpi_fac);
618 /* update font drawing */
619 BLF_default_dpi(U.pixelsize * U.dpi);
622 static void wm_window_ensure_eventstate(wmWindow *win)
624 if (win->eventstate) {
628 win->eventstate = MEM_callocN(sizeof(wmEvent), "window event state");
629 wm_get_cursor_position(win, &win->eventstate->x, &win->eventstate->y);
632 /* belongs to below */
633 static void wm_window_ghostwindow_add(wmWindowManager *wm, const char *title, wmWindow *win)
635 GHOST_WindowHandle ghostwin;
636 GHOST_GLSettings glSettings = {0};
637 int scr_w, scr_h, posy;
639 /* a new window is created when pageflip mode is required for a window */
640 if (win->stereo3d_format->display_mode == S3D_DISPLAY_PAGEFLIP)
641 glSettings.flags |= GHOST_glStereoVisual;
643 if (G.debug & G_DEBUG_GPU) {
644 glSettings.flags |= GHOST_glDebugContext;
647 wm_get_screensize(&scr_w, &scr_h);
648 posy = (scr_h - win->posy - win->sizey);
650 /* Clear drawable so we can set the new window. */
651 wmWindow *prev_windrawable = wm->windrawable;
652 wm_window_clear_drawable(wm);
654 ghostwin = GHOST_CreateWindow(g_system, title,
655 win->posx, posy, win->sizex, win->sizey,
656 (GHOST_TWindowState)win->windowstate,
657 GHOST_kDrawingContextTypeOpenGL,
661 GHOST_RectangleHandle bounds;
663 win->gpuctx = GPU_context_create();
665 /* needed so we can detect the graphics card below */
668 /* Set window as drawable upon creation. Note this has already been
669 * it has already been activated by GHOST_CreateWindow. */
670 wm_window_set_drawable(wm, win, false);
672 win->ghostwin = ghostwin;
673 GHOST_SetWindowUserData(ghostwin, win); /* pointer back */
675 wm_window_ensure_eventstate(win);
677 /* store actual window size in blender window */
678 bounds = GHOST_GetClientBounds(win->ghostwin);
680 /* win32: gives undefined window size when minimized */
681 if (GHOST_GetWindowState(win->ghostwin) != GHOST_kWindowStateMinimized) {
682 win->sizex = GHOST_GetWidthRectangle(bounds);
683 win->sizey = GHOST_GetHeightRectangle(bounds);
685 GHOST_DisposeRectangle(bounds);
688 /* set the state here, so minimized state comes up correct on windows */
689 if (wm_init_state.window_focus) {
690 GHOST_SetWindowState(ghostwin, (GHOST_TWindowState)win->windowstate);
693 /* until screens get drawn, make it nice gray */
694 glClearColor(0.55, 0.55, 0.55, 0.0);
695 /* Crash on OSS ATI: bugs.launchpad.net/ubuntu/+source/mesa/+bug/656100 */
696 if (!GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_UNIX, GPU_DRIVER_OPENSOURCE)) {
697 glClear(GL_COLOR_BUFFER_BIT);
700 /* needed here, because it's used before it reads userdef */
701 WM_window_set_dpi(win);
703 wm_window_swap_buffers(win);
705 //GHOST_SetWindowState(ghostwin, GHOST_kWindowStateModified);
707 /* standard state vars for window */
711 wm_window_set_drawable(wm, prev_windrawable, false);
716 * Initialize #wmWindow without ghostwin, open these and clear.
718 * window size is read from window, if 0 it uses prefsize
719 * called in #WM_check, also inits stuff after file read.
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.
728 void wm_window_ghostwindows_ensure(wmWindowManager *wm)
733 BLI_assert(G.background == false);
735 /* no commandline prefsize? then we set this.
736 * Note that these values will be used only
737 * when there is no startup.blend yet.
739 if (wm_init_state.size_x == 0) {
740 wm_get_screensize(&wm_init_state.size_x, &wm_init_state.size_y);
742 /* note!, this isnt quite correct, active screen maybe offset 1000s if PX,
743 * we'd need a wm_get_screensize like function that gives offset,
744 * in practice the window manager will likely move to the correct monitor */
745 wm_init_state.start_x = 0;
746 wm_init_state.start_y = 0;
748 #ifdef WITH_X11 /* X11 */
749 /* X11, don't start maximized because we can't figure out the dimensions
750 * of a single display yet if there are multiple, due to lack of Xinerama
751 * handling in GHOST. */
752 wm_init_state.size_x = min_ii(wm_init_state.size_x, WM_WIN_INIT_SIZE_X);
753 wm_init_state.size_y = min_ii(wm_init_state.size_y, WM_WIN_INIT_SIZE_Y);
755 wm_init_state.start_x = WM_WIN_INIT_PAD;
756 wm_init_state.start_y = WM_WIN_INIT_PAD;
757 wm_init_state.size_x -= WM_WIN_INIT_PAD * 2;
758 wm_init_state.size_y -= WM_WIN_INIT_PAD * 2;
762 for (win = wm->windows.first; win; win = win->next) {
763 if (win->ghostwin == NULL) {
764 if ((win->sizex == 0) || (wm_init_state.override_flag & WIN_OVERRIDE_GEOM)) {
765 win->posx = wm_init_state.start_x;
766 win->posy = wm_init_state.start_y;
767 win->sizex = wm_init_state.size_x;
768 win->sizey = wm_init_state.size_y;
770 if (wm_init_state.override_flag & WIN_OVERRIDE_GEOM) {
771 win->windowstate = GHOST_kWindowStateNormal;
772 wm_init_state.override_flag &= ~WIN_OVERRIDE_GEOM;
775 win->windowstate = GHOST_WINDOW_STATE_DEFAULT;
779 if (wm_init_state.override_flag & WIN_OVERRIDE_WINSTATE) {
780 win->windowstate = wm_init_state.windowstate;
781 wm_init_state.override_flag &= ~WIN_OVERRIDE_WINSTATE;
784 /* without this, cursor restore may fail, T45456 */
785 if (win->cursor == 0) {
786 win->cursor = CURSOR_STD;
789 wm_window_ghostwindow_add(wm, "Blender", win);
791 /* happens after fileread */
792 wm_window_ensure_eventstate(win);
794 /* add keymap handlers (1 handler for all keys in map!) */
795 keymap = WM_keymap_ensure(wm->defaultconf, "Window", 0, 0);
796 WM_event_add_keymap_handler(&win->handlers, keymap);
798 keymap = WM_keymap_ensure(wm->defaultconf, "Screen", 0, 0);
799 WM_event_add_keymap_handler(&win->handlers, keymap);
801 keymap = WM_keymap_ensure(wm->defaultconf, "Screen Editing", 0, 0);
802 WM_event_add_keymap_handler(&win->modalhandlers, keymap);
806 ListBase *lb = WM_dropboxmap_find("Window", 0, 0);
807 WM_event_add_dropbox_handler(&win->handlers, lb);
809 wm_window_title(wm, win);
812 ED_screen_global_areas_refresh(win);
817 * Call after #wm_window_ghostwindows_ensure or #WM_check
818 * (after loading a new file) in the unlikely event a window couldn't be created.
820 void wm_window_ghostwindows_remove_invalid(bContext *C, wmWindowManager *wm)
822 wmWindow *win, *win_next;
824 BLI_assert(G.background == false);
826 for (win = wm->windows.first; win; win = win_next) {
827 win_next = win->next;
828 if (win->ghostwin == NULL) {
829 wm_window_close(C, wm, win);
835 * new window, no screen yet, but we open ghostwindow for it,
836 * also gets the window level handlers
837 * \note area-rip calls this.
838 * \return the window or NULL.
840 wmWindow *WM_window_open(bContext *C, const rcti *rect)
842 wmWindow *win_prev = CTX_wm_window(C);
843 wmWindow *win = wm_window_new(C, win_prev);
845 win->posx = rect->xmin;
846 win->posy = rect->ymin;
847 win->sizex = BLI_rcti_size_x(rect);
848 win->sizey = BLI_rcti_size_y(rect);
856 wm_window_close(C, CTX_wm_manager(C), win);
857 CTX_wm_window_set(C, win_prev);
863 * Uses `screen->temp` tag to define what to do, currently it limits
864 * to only one "temp" window for render out, preferences, filewindow, etc...
866 * \param type: WM_WINDOW_RENDER, WM_WINDOW_USERPREFS...
867 * \return the window or NULL.
869 wmWindow *WM_window_open_temp(bContext *C, int x, int y, int sizex, int sizey, int type)
871 Main *bmain = CTX_data_main(C);
872 wmWindow *win_prev = CTX_wm_window(C);
876 Scene *scene = CTX_data_scene(C);
877 ViewLayer *view_layer = CTX_data_view_layer(C);
880 /* convert to native OS window coordinates */
881 const float native_pixel_size = GHOST_GetNativePixelSize(win_prev->ghostwin);
882 x /= native_pixel_size;
883 y /= native_pixel_size;
884 sizex /= native_pixel_size;
885 sizey /= native_pixel_size;
887 /* calculate position */
889 rect.xmin = x + win_prev->posx - sizex / 2;
890 rect.ymin = y + win_prev->posy - sizey / 2;
891 rect.xmax = rect.xmin + sizex;
892 rect.ymax = rect.ymin + sizey;
894 /* changes rect to fit within desktop */
895 wm_window_check_position(&rect);
897 /* test if we have a temp screen already */
898 for (win = CTX_wm_manager(C)->windows.first; win; win = win->next)
899 if (WM_window_is_temp_screen(win))
902 /* add new window? */
904 win = wm_window_new(C, win_prev);
906 win->posx = rect.xmin;
907 win->posy = rect.ymin;
910 screen = WM_window_get_active_screen(win);
912 win->sizex = BLI_rcti_size_x(&rect);
913 win->sizey = BLI_rcti_size_y(&rect);
916 wm_window_set_size(win, win->sizex, win->sizey);
917 wm_window_raise(win);
920 if (WM_window_get_active_workspace(win) == NULL) {
921 WorkSpace *workspace = WM_window_get_active_workspace(win_prev);
922 BKE_workspace_active_set(win->workspace_hook, workspace);
925 if (screen == NULL) {
926 /* add new screen layout */
927 WorkSpace *workspace = WM_window_get_active_workspace(win);
928 WorkSpaceLayout *layout = ED_workspace_layout_add(bmain, workspace, win, "temp");
930 screen = BKE_workspace_layout_screen_get(layout);
931 WM_window_set_active_layout(win, workspace, layout);
934 /* Set scene and view layer to match original window. */
935 STRNCPY(win->view_layer_name, view_layer->name);
936 if (WM_window_get_active_scene(win) != scene) {
937 ED_screen_scene_change(C, win, scene);
942 /* make window active, and validate/resize */
943 CTX_wm_window_set(C, win);
946 /* It's possible `win->ghostwin == NULL`.
947 * instead of attempting to cleanup here (in a half finished state),
948 * finish setting up the screen, then free it at the end of the function,
949 * to avoid having to take into account a partially-created window.
952 /* ensure it shows the right spacetype editor */
953 sa = screen->areabase.first;
954 CTX_wm_area_set(C, sa);
956 if (type == WM_WINDOW_RENDER) {
957 ED_area_newspace(C, sa, SPACE_IMAGE, false);
959 else if (type == WM_WINDOW_DRIVERS) {
960 ED_area_newspace(C, sa, SPACE_GRAPH, false);
963 ED_area_newspace(C, sa, SPACE_USERPREF, false);
966 ED_screen_change(C, screen);
967 ED_screen_refresh(CTX_wm_manager(C), win); /* test scale */
969 /* do additional setup for specific editor type */
970 if (type == WM_WINDOW_DRIVERS) {
971 ED_drivers_editor_init(C, sa);
974 if (sa->spacetype == SPACE_IMAGE)
975 title = IFACE_("Blender Render");
976 else if (ELEM(sa->spacetype, SPACE_OUTLINER, SPACE_USERPREF))
977 title = IFACE_("Blender Preferences");
978 else if (sa->spacetype == SPACE_FILE)
979 title = IFACE_("Blender File View");
980 else if (sa->spacetype == SPACE_GRAPH)
981 title = IFACE_("Blender Drivers Editor");
986 GHOST_SetTitle(win->ghostwin, title);
990 /* very unlikely! but opening a new window can fail */
991 wm_window_close(C, CTX_wm_manager(C), win);
992 CTX_wm_window_set(C, win_prev);
999 /* ****************** Operators ****************** */
1001 int wm_window_close_exec(bContext *C, wmOperator *UNUSED(op))
1003 wmWindowManager *wm = CTX_wm_manager(C);
1004 wmWindow *win = CTX_wm_window(C);
1005 wm_window_close(C, wm, win);
1006 return OPERATOR_FINISHED;
1009 int wm_window_new_exec(bContext *C, wmOperator *UNUSED(op))
1011 wmWindow *win_src = CTX_wm_window(C);
1014 ok = (wm_window_copy_test(C, win_src, true, true) != NULL);
1016 return ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
1019 int wm_window_new_main_exec(bContext *C, wmOperator *UNUSED(op))
1021 wmWindow *win_src = CTX_wm_window(C);
1024 ok = (wm_window_copy_test(C, win_src, true, false) != NULL);
1026 return ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
1029 /* fullscreen operator callback */
1030 int wm_window_fullscreen_toggle_exec(bContext *C, wmOperator *UNUSED(op))
1032 wmWindow *window = CTX_wm_window(C);
1033 GHOST_TWindowState state;
1036 return OPERATOR_CANCELLED;
1038 state = GHOST_GetWindowState(window->ghostwin);
1039 if (state != GHOST_kWindowStateFullScreen)
1040 GHOST_SetWindowState(window->ghostwin, GHOST_kWindowStateFullScreen);
1042 GHOST_SetWindowState(window->ghostwin, GHOST_kWindowStateNormal);
1044 return OPERATOR_FINISHED;
1049 /* ************ events *************** */
1051 void wm_cursor_position_from_ghost(wmWindow *win, int *x, int *y)
1053 float fac = GHOST_GetNativePixelSize(win->ghostwin);
1055 GHOST_ScreenToClient(win->ghostwin, *x, *y, x, y);
1058 *y = (win->sizey - 1) - *y;
1062 void wm_cursor_position_to_ghost(wmWindow *win, int *x, int *y)
1064 float fac = GHOST_GetNativePixelSize(win->ghostwin);
1068 *y = win->sizey - *y - 1;
1070 GHOST_ClientToScreen(win->ghostwin, *x, *y, x, y);
1073 void wm_get_cursor_position(wmWindow *win, int *x, int *y)
1075 if (UNLIKELY(G.f & G_FLAG_EVENT_SIMULATE)) {
1076 *x = win->eventstate->x;
1077 *y = win->eventstate->y;
1080 GHOST_GetCursorPosition(g_system, x, y);
1081 wm_cursor_position_from_ghost(win, x, y);
1091 /* check if specified modifier key type is pressed */
1092 static int query_qual(modifierKeyType qual)
1094 GHOST_TModifierKeyMask left, right;
1099 left = GHOST_kModifierKeyLeftShift;
1100 right = GHOST_kModifierKeyRightShift;
1103 left = GHOST_kModifierKeyLeftControl;
1104 right = GHOST_kModifierKeyRightControl;
1107 left = right = GHOST_kModifierKeyOS;
1111 left = GHOST_kModifierKeyLeftAlt;
1112 right = GHOST_kModifierKeyRightAlt;
1116 GHOST_GetModifierKeyState(g_system, left, &val);
1118 GHOST_GetModifierKeyState(g_system, right, &val);
1123 static void wm_window_set_drawable(wmWindowManager *wm, wmWindow *win, bool activate)
1125 BLI_assert(ELEM(wm->windrawable, NULL, win));
1127 wm->windrawable = win;
1129 GHOST_ActivateWindowDrawingContext(win->ghostwin);
1131 GPU_context_active_set(win->gpuctx);
1135 void wm_window_clear_drawable(wmWindowManager *wm)
1137 if (wm->windrawable) {
1139 gpu_batch_presets_reset();
1141 wm->windrawable = NULL;
1145 void wm_window_make_drawable(wmWindowManager *wm, wmWindow *win)
1147 BLI_assert(GPU_framebuffer_active_get() == NULL);
1149 if (win != wm->windrawable && win->ghostwin) {
1150 // win->lmbut = 0; /* keeps hanging when mousepressed while other window opened */
1151 wm_window_clear_drawable(wm);
1153 if (G.debug & G_DEBUG_EVENTS) {
1154 printf("%s: set drawable %d\n", __func__, win->winid);
1157 wm_window_set_drawable(wm, win, true);
1159 /* this can change per window */
1160 WM_window_set_dpi(win);
1164 /* Reset active the current window opengl drawing context. */
1165 void wm_window_reset_drawable(void)
1167 BLI_assert(BLI_thread_is_main());
1168 BLI_assert(GPU_framebuffer_active_get() == NULL);
1169 wmWindowManager *wm = G_MAIN->wm.first;
1174 wmWindow *win = wm->windrawable;
1176 if (win && win->ghostwin) {
1177 wm_window_clear_drawable(wm);
1178 wm_window_set_drawable(wm, win, true);
1182 /* called by ghost, here we handle events for windows themselves or send to event system */
1183 /* mouse coordinate converversion happens here */
1184 static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr)
1186 bContext *C = C_void_ptr;
1187 wmWindowManager *wm = CTX_wm_manager(C);
1188 GHOST_TEventType type = GHOST_GetEventType(evt);
1189 int time = GHOST_GetEventTime(evt);
1191 if (type == GHOST_kEventQuit) {
1195 GHOST_WindowHandle ghostwin = GHOST_GetEventWindow(evt);
1196 GHOST_TEventDataPtr data = GHOST_GetEventData(evt);
1199 /* Ghost now can call this function for life resizes, but it should return if WM didn't initialize yet.
1200 * Can happen on file read (especially full size window) */
1201 if ((wm->initialized & WM_WINDOW_IS_INITIALIZED) == 0) {
1205 /* XXX - should be checked, why are we getting an event here, and */
1207 puts("<!> event has no window");
1210 else if (!GHOST_ValidWindow(g_system, ghostwin)) {
1211 /* XXX - should be checked, why are we getting an event here, and */
1213 puts("<!> event has invalid window");
1217 win = GHOST_GetWindowUserData(ghostwin);
1221 case GHOST_kEventWindowDeactivate:
1222 wm_event_add_ghostevent(wm, win, type, time, data);
1223 win->active = 0; /* XXX */
1225 /* clear modifiers for inactive windows */
1226 win->eventstate->alt = 0;
1227 win->eventstate->ctrl = 0;
1228 win->eventstate->shift = 0;
1229 win->eventstate->oskey = 0;
1230 win->eventstate->keymodifier = 0;
1233 case GHOST_kEventWindowActivate:
1235 GHOST_TEventKeyData kdata;
1238 const int keymodifier = ((query_qual(SHIFT) ? KM_SHIFT : 0) |
1239 (query_qual(CONTROL) ? KM_CTRL : 0) |
1240 (query_qual(ALT) ? KM_ALT : 0) |
1241 (query_qual(OS) ? KM_OSKEY : 0));
1243 /* Win23/GHOST modifier bug, see T40317 */
1245 //# define USE_WIN_ACTIVATE
1248 wm->winactive = win; /* no context change! c->wm->windrawable is drawable, or for area queues */
1251 // window_handle(win, INPUTCHANGE, win->active);
1253 /* bad ghost support for modifier keys... so on activate we set the modifiers again */
1255 /* TODO: This is not correct since a modifier may be held when a window is activated...
1256 * better solve this at ghost level. attempted fix r54450 but it caused bug [#34255]
1258 * For now don't send GHOST_kEventKeyDown events, just set the 'eventstate'.
1261 kdata.utf8_buf[0] = '\0';
1263 if (win->eventstate->shift) {
1264 if ((keymodifier & KM_SHIFT) == 0) {
1265 kdata.key = GHOST_kKeyLeftShift;
1266 wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, time, &kdata);
1269 #ifdef USE_WIN_ACTIVATE
1271 if (keymodifier & KM_SHIFT) {
1272 win->eventstate->shift = KM_MOD_FIRST;
1276 if (win->eventstate->ctrl) {
1277 if ((keymodifier & KM_CTRL) == 0) {
1278 kdata.key = GHOST_kKeyLeftControl;
1279 wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, time, &kdata);
1282 #ifdef USE_WIN_ACTIVATE
1284 if (keymodifier & KM_CTRL) {
1285 win->eventstate->ctrl = KM_MOD_FIRST;
1289 if (win->eventstate->alt) {
1290 if ((keymodifier & KM_ALT) == 0) {
1291 kdata.key = GHOST_kKeyLeftAlt;
1292 wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, time, &kdata);
1295 #ifdef USE_WIN_ACTIVATE
1297 if (keymodifier & KM_ALT) {
1298 win->eventstate->alt = KM_MOD_FIRST;
1302 if (win->eventstate->oskey) {
1303 if ((keymodifier & KM_OSKEY) == 0) {
1304 kdata.key = GHOST_kKeyOS;
1305 wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, time, &kdata);
1308 #ifdef USE_WIN_ACTIVATE
1310 if (keymodifier & KM_OSKEY) {
1311 win->eventstate->oskey = KM_MOD_FIRST;
1316 #undef USE_WIN_ACTIVATE
1319 /* keymodifier zero, it hangs on hotkeys that open windows otherwise */
1320 win->eventstate->keymodifier = 0;
1322 /* entering window, update mouse pos. but no event */
1323 wm_get_cursor_position(win, &wx, &wy);
1325 win->eventstate->x = wx;
1326 win->eventstate->y = wy;
1328 win->addmousemove = 1; /* enables highlighted buttons */
1330 wm_window_make_drawable(wm, win);
1332 /* window might be focused by mouse click in configuration of window manager
1333 * when focus is not following mouse
1334 * click could have been done on a button and depending on window manager settings
1335 * click would be passed to blender or not, but in any case button under cursor
1336 * should be activated, so at max next click on button without moving mouse
1337 * would trigger it's handle function
1338 * currently it seems to be common practice to generate new event for, but probably
1339 * we'll need utility function for this? (sergey)
1341 wm_event_init_from_window(win, &event);
1342 event.type = MOUSEMOVE;
1343 event.prevx = event.x;
1344 event.prevy = event.y;
1346 wm_event_add(win, &event);
1350 case GHOST_kEventWindowClose:
1352 wm_window_close(C, wm, win);
1355 case GHOST_kEventWindowUpdate:
1357 if (G.debug & G_DEBUG_EVENTS) {
1358 printf("%s: ghost redraw %d\n", __func__, win->winid);
1361 wm_window_make_drawable(wm, win);
1362 WM_event_add_notifier(C, NC_WINDOW, NULL);
1366 case GHOST_kEventWindowSize:
1367 case GHOST_kEventWindowMove:
1369 GHOST_TWindowState state;
1370 state = GHOST_GetWindowState(win->ghostwin);
1371 win->windowstate = state;
1373 WM_window_set_dpi(win);
1375 /* win32: gives undefined window size when minimized */
1376 if (state != GHOST_kWindowStateMinimized) {
1377 GHOST_RectangleHandle client_rect;
1378 int l, t, r, b, scr_w, scr_h;
1379 int sizex, sizey, posx, posy;
1381 client_rect = GHOST_GetClientBounds(win->ghostwin);
1382 GHOST_GetRectangle(client_rect, &l, &t, &r, &b);
1384 GHOST_DisposeRectangle(client_rect);
1386 wm_get_desktopsize(&scr_w, &scr_h);
1390 posy = scr_h - t - win->sizey;
1393 * Ghost sometimes send size or move events when the window hasn't changed.
1394 * One case of this is using compiz on linux. To alleviate the problem
1395 * we ignore all such event here.
1397 * It might be good to eventually do that at Ghost level, but that is for
1400 if (win->sizex != sizex ||
1401 win->sizey != sizey ||
1402 win->posx != posx ||
1405 const bScreen *screen = WM_window_get_active_screen(win);
1413 if (G.debug & G_DEBUG_EVENTS) {
1414 const char *state_str;
1415 state = GHOST_GetWindowState(win->ghostwin);
1417 if (state == GHOST_kWindowStateNormal) {
1418 state_str = "normal";
1420 else if (state == GHOST_kWindowStateMinimized) {
1421 state_str = "minimized";
1423 else if (state == GHOST_kWindowStateMaximized) {
1424 state_str = "maximized";
1426 else if (state == GHOST_kWindowStateFullScreen) {
1427 state_str = "fullscreen";
1430 state_str = "<unknown>";
1433 printf("%s: window %d state = %s\n", __func__, win->winid, state_str);
1435 if (type != GHOST_kEventWindowSize) {
1436 printf("win move event pos %d %d size %d %d\n",
1437 win->posx, win->posy, win->sizex, win->sizey);
1441 wm_window_make_drawable(wm, win);
1442 BKE_icon_changed(screen->id.icon_id);
1443 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
1444 WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, NULL);
1446 #if defined(__APPLE__) || defined(WIN32)
1447 /* OSX and Win32 don't return to the mainloop while resize */
1449 wm_event_do_handlers(C);
1450 wm_event_do_notifiers(C);
1453 /* Warning! code above nulls 'C->wm.window', causing BGE to quit, see: T45699.
1454 * Further, its easier to match behavior across platforms, so restore the window. */
1455 CTX_wm_window_set(C, win);
1462 case GHOST_kEventWindowDPIHintChanged:
1464 WM_window_set_dpi(win);
1465 /* font's are stored at each DPI level, without this we can easy load 100's of fonts */
1468 WM_main_add_notifier(NC_WINDOW, NULL); /* full redraw */
1469 WM_main_add_notifier(NC_SCREEN | NA_EDITED, NULL); /* refresh region sizes */
1473 case GHOST_kEventOpenMainFile:
1475 PointerRNA props_ptr;
1476 wmWindow *oldWindow;
1477 const char *path = GHOST_GetEventData(evt);
1480 wmOperatorType *ot = WM_operatortype_find("WM_OT_open_mainfile", false);
1481 /* operator needs a valid window in context, ensures
1482 * it is correctly set */
1483 oldWindow = CTX_wm_window(C);
1484 CTX_wm_window_set(C, win);
1486 WM_operator_properties_create_ptr(&props_ptr, ot);
1487 RNA_string_set(&props_ptr, "filepath", path);
1488 WM_operator_name_call_ptr(C, ot, WM_OP_EXEC_DEFAULT, &props_ptr);
1489 WM_operator_properties_free(&props_ptr);
1491 CTX_wm_window_set(C, oldWindow);
1495 case GHOST_kEventDraggingDropDone:
1498 GHOST_TEventDragnDropData *ddd = GHOST_GetEventData(evt);
1501 /* entering window, update mouse pos */
1502 wm_get_cursor_position(win, &wx, &wy);
1503 win->eventstate->x = wx;
1504 win->eventstate->y = wy;
1506 wm_event_init_from_window(win, &event); /* copy last state, like mouse coords */
1508 /* activate region */
1509 event.type = MOUSEMOVE;
1510 event.prevx = event.x;
1511 event.prevy = event.y;
1513 wm->winactive = win; /* no context change! c->wm->windrawable is drawable, or for area queues */
1516 wm_event_add(win, &event);
1519 /* make blender drop event with custom data pointing to wm drags */
1520 event.type = EVT_DROP;
1521 event.val = KM_RELEASE;
1522 event.custom = EVT_DATA_DRAGDROP;
1523 event.customdata = &wm->drags;
1524 event.customdatafree = 1;
1526 wm_event_add(win, &event);
1528 /* printf("Drop detected\n"); */
1530 /* add drag data to wm for paths: */
1532 if (ddd->dataType == GHOST_kDragnDropTypeFilenames) {
1533 GHOST_TStringArray *stra = ddd->data;
1536 for (a = 0; a < stra->count; a++) {
1537 printf("drop file %s\n", stra->strings[a]);
1538 /* try to get icon type from extension */
1539 icon = ED_file_extension_icon((char *)stra->strings[a]);
1541 WM_event_start_drag(C, icon, WM_DRAG_PATH, stra->strings[a], 0.0, WM_DRAG_NOP);
1542 /* void poin should point to string, it makes a copy */
1543 break; /* only one drop element supported now */
1549 case GHOST_kEventNativeResolutionChange:
1551 // only update if the actual pixel size changes
1552 float prev_pixelsize = U.pixelsize;
1553 WM_window_set_dpi(win);
1555 if (U.pixelsize != prev_pixelsize) {
1556 BKE_icon_changed(WM_window_get_active_screen(win)->id.icon_id);
1558 // close all popups since they are positioned with the pixel
1559 // size baked in and it's difficult to correct them
1560 wmWindow *oldWindow = CTX_wm_window(C);
1561 CTX_wm_window_set(C, win);
1562 UI_popup_handlers_remove_all(C, &win->modalhandlers);
1563 CTX_wm_window_set(C, oldWindow);
1565 wm_window_make_drawable(wm, win);
1567 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
1568 WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, NULL);
1573 case GHOST_kEventTrackpad:
1575 GHOST_TEventTrackpadData *pd = data;
1577 wm_cursor_position_from_ghost(win, &pd->x, &pd->y);
1578 wm_event_add_ghostevent(wm, win, type, time, data);
1581 case GHOST_kEventCursorMove:
1583 GHOST_TEventCursorData *cd = data;
1585 wm_cursor_position_from_ghost(win, &cd->x, &cd->y);
1586 wm_event_add_ghostevent(wm, win, type, time, data);
1590 wm_event_add_ghostevent(wm, win, type, time, data);
1600 * This timer system only gives maximum 1 timer event per redraw cycle,
1601 * to prevent queues to get overloaded.
1602 * Timer handlers should check for delta to decide if they just update, or follow real time.
1603 * Timer handlers can also set duration to match frames passed
1605 static int wm_window_timer(const bContext *C)
1607 wmWindowManager *wm = CTX_wm_manager(C);
1608 wmTimer *wt, *wtnext;
1610 double time = PIL_check_seconds_timer();
1613 for (wt = wm->timers.first; wt; wt = wtnext) {
1614 wtnext = wt->next; /* in case timer gets removed */
1617 if (wt->sleep == 0) {
1618 if (time > wt->ntime) {
1619 wt->delta = time - wt->ltime;
1620 wt->duration += wt->delta;
1622 wt->ntime = wt->stime + wt->timestep * ceil(wt->duration / wt->timestep);
1624 if (wt->event_type == TIMERJOBS)
1625 wm_jobs_timer(C, wm, wt);
1626 else if (wt->event_type == TIMERAUTOSAVE)
1627 wm_autosave_timer(C, wm, wt);
1628 else if (wt->event_type == TIMERNOTIFIER)
1629 WM_main_add_notifier(POINTER_AS_UINT(wt->customdata), NULL);
1632 wm_event_init_from_window(win, &event);
1634 event.type = wt->event_type;
1635 event.val = KM_NOTHING;
1636 event.keymodifier = 0;
1637 event.custom = EVT_DATA_TIMER;
1638 event.customdata = wt;
1639 wm_event_add(win, &event);
1649 void wm_window_process_events(const bContext *C)
1653 BLI_assert(BLI_thread_is_main());
1655 hasevent = GHOST_ProcessEvents(g_system, 0); /* 0 is no wait */
1658 GHOST_DispatchEvents(g_system);
1660 hasevent |= wm_window_timer(C);
1662 /* no event, we sleep 5 milliseconds */
1667 void wm_window_process_events_nosleep(void)
1669 if (GHOST_ProcessEvents(g_system, 0))
1670 GHOST_DispatchEvents(g_system);
1673 /* exported as handle callback to bke blender.c */
1674 void wm_window_testbreak(void)
1676 static double ltime = 0;
1677 double curtime = PIL_check_seconds_timer();
1679 BLI_assert(BLI_thread_is_main());
1681 /* only check for breaks every 50 milliseconds
1682 * if we get called more often.
1684 if ((curtime - ltime) > 0.05) {
1685 int hasevent = GHOST_ProcessEvents(g_system, 0); /* 0 is no wait */
1688 GHOST_DispatchEvents(g_system);
1694 /* **************** init ********************** */
1696 /* bContext can be null in background mode because we don't
1697 * need to event handling. */
1698 void wm_ghost_init(bContext *C)
1701 GHOST_EventConsumerHandle consumer;
1704 consumer = GHOST_CreateEventConsumer(ghost_event_proc, C);
1707 g_system = GHOST_CreateSystem();
1710 GHOST_AddEventConsumer(g_system, consumer);
1713 if (wm_init_state.native_pixels) {
1714 GHOST_UseNativePixels();
1717 GHOST_UseWindowFocus(wm_init_state.window_focus);
1719 WM_init_tablet_api();
1723 void wm_ghost_exit(void)
1726 GHOST_DisposeSystem(g_system);
1731 /* **************** timer ********************** */
1733 /* to (de)activate running timers temporary */
1734 void WM_event_timer_sleep(wmWindowManager *wm, wmWindow *UNUSED(win), wmTimer *timer, bool do_sleep)
1738 for (wt = wm->timers.first; wt; wt = wt->next)
1743 wt->sleep = do_sleep;
1746 wmTimer *WM_event_add_timer(wmWindowManager *wm, wmWindow *win, int event_type, double timestep)
1748 wmTimer *wt = MEM_callocN(sizeof(wmTimer), "window timer");
1750 wt->event_type = event_type;
1751 wt->ltime = PIL_check_seconds_timer();
1752 wt->ntime = wt->ltime + timestep;
1753 wt->stime = wt->ltime;
1754 wt->timestep = timestep;
1757 BLI_addtail(&wm->timers, wt);
1762 wmTimer *WM_event_add_timer_notifier(wmWindowManager *wm, wmWindow *win, unsigned int type, double timestep)
1764 wmTimer *wt = MEM_callocN(sizeof(wmTimer), "window timer");
1766 wt->event_type = TIMERNOTIFIER;
1767 wt->ltime = PIL_check_seconds_timer();
1768 wt->ntime = wt->ltime + timestep;
1769 wt->stime = wt->ltime;
1770 wt->timestep = timestep;
1772 wt->customdata = POINTER_FROM_UINT(type);
1773 wt->flags |= WM_TIMER_NO_FREE_CUSTOM_DATA;
1775 BLI_addtail(&wm->timers, wt);
1780 void WM_event_remove_timer(wmWindowManager *wm, wmWindow *UNUSED(win), wmTimer *timer)
1784 /* extra security check */
1785 for (wt = wm->timers.first; wt; wt = wt->next)
1791 if (wm->reports.reporttimer == wt)
1792 wm->reports.reporttimer = NULL;
1794 BLI_remlink(&wm->timers, wt);
1795 if (wt->customdata != NULL && (wt->flags & WM_TIMER_NO_FREE_CUSTOM_DATA) == 0) {
1796 MEM_freeN(wt->customdata);
1800 /* there might be events in queue with this timer as customdata */
1801 for (win = wm->windows.first; win; win = win->next) {
1803 for (event = win->queue.first; event; event = event->next) {
1804 if (event->customdata == wt) {
1805 event->customdata = NULL;
1806 event->type = EVENT_NONE; /* timer users customdata, dont want NULL == NULL */
1813 void WM_event_remove_timer_notifier(wmWindowManager *wm, wmWindow *win, wmTimer *timer)
1815 timer->customdata = NULL;
1816 WM_event_remove_timer(wm, win, timer);
1819 /* ******************* clipboard **************** */
1821 static char *wm_clipboard_text_get_ex(bool selection, int *r_len,
1824 char *p, *p2, *buf, *newbuf;
1831 buf = (char *)GHOST_getClipboard(selection);
1837 /* always convert from \r\n to \n */
1838 p2 = newbuf = MEM_mallocN(strlen(buf) + 1, __func__);
1841 /* will return an over-alloc'ed value in the case there are newlines */
1842 for (p = buf; *p; p++) {
1843 if ((*p != '\n') && (*p != '\r')) {
1852 for (p = buf; *p; p++) {
1861 free(buf); /* ghost uses regular malloc */
1863 *r_len = (p2 - newbuf);
1869 * Return text from the clipboard.
1871 * \note Caller needs to check for valid utf8 if this is a requirement.
1873 char *WM_clipboard_text_get(bool selection, int *r_len)
1875 return wm_clipboard_text_get_ex(selection, r_len, false);
1879 * Convenience function for pasting to areas of Blender which don't support newlines.
1881 char *WM_clipboard_text_get_firstline(bool selection, int *r_len)
1883 return wm_clipboard_text_get_ex(selection, r_len, true);
1886 void WM_clipboard_text_set(const char *buf, bool selection)
1888 if (!G.background) {
1890 /* do conversion from \n to \r\n on Windows */
1895 for (p = buf; *p; p++) {
1902 newbuf = MEM_callocN(newlen + 1, "WM_clipboard_text_set");
1904 for (p = buf, p2 = newbuf; *p; p++, p2++) {
1906 *(p2++) = '\r'; *p2 = '\n';
1914 GHOST_putClipboard((GHOST_TInt8 *)newbuf, selection);
1917 GHOST_putClipboard((GHOST_TInt8 *)buf, selection);
1922 /* ******************* progress bar **************** */
1924 void WM_progress_set(wmWindow *win, float progress)
1926 GHOST_SetProgressBar(win->ghostwin, progress);
1929 void WM_progress_clear(wmWindow *win)
1931 GHOST_EndProgressBar(win->ghostwin);
1934 /* ************************************ */
1936 void wm_window_get_position(wmWindow *win, int *r_pos_x, int *r_pos_y)
1938 *r_pos_x = win->posx;
1939 *r_pos_y = win->posy;
1942 void wm_window_set_size(wmWindow *win, int width, int height)
1944 GHOST_SetClientSize(win->ghostwin, width, height);
1947 void wm_window_lower(wmWindow *win)
1949 GHOST_SetWindowOrder(win->ghostwin, GHOST_kWindowOrderBottom);
1952 void wm_window_raise(wmWindow *win)
1954 GHOST_SetWindowOrder(win->ghostwin, GHOST_kWindowOrderTop);
1957 void wm_window_swap_buffers(wmWindow *win)
1959 GHOST_SwapWindowBuffers(win->ghostwin);
1962 void wm_window_set_swap_interval (wmWindow *win, int interval)
1964 GHOST_SetSwapInterval(win->ghostwin, interval);
1967 bool wm_window_get_swap_interval(wmWindow *win, int *intervalOut)
1969 return GHOST_GetSwapInterval(win->ghostwin, intervalOut);
1973 /* ******************* exported api ***************** */
1976 /* called whem no ghost system was initialized */
1977 void WM_init_state_size_set(int stax, int stay, int sizx, int sizy)
1979 wm_init_state.start_x = stax; /* left hand pos */
1980 wm_init_state.start_y = stay; /* bottom pos */
1981 wm_init_state.size_x = sizx < 640 ? 640 : sizx;
1982 wm_init_state.size_y = sizy < 480 ? 480 : sizy;
1983 wm_init_state.override_flag |= WIN_OVERRIDE_GEOM;
1986 /* for borderless and border windows set from command-line */
1987 void WM_init_state_fullscreen_set(void)
1989 wm_init_state.windowstate = GHOST_kWindowStateFullScreen;
1990 wm_init_state.override_flag |= WIN_OVERRIDE_WINSTATE;
1993 void WM_init_state_normal_set(void)
1995 wm_init_state.windowstate = GHOST_kWindowStateNormal;
1996 wm_init_state.override_flag |= WIN_OVERRIDE_WINSTATE;
1999 void WM_init_state_maximized_set(void)
2001 wm_init_state.windowstate = GHOST_kWindowStateMaximized;
2002 wm_init_state.override_flag |= WIN_OVERRIDE_WINSTATE;
2005 void WM_init_window_focus_set(bool do_it)
2007 wm_init_state.window_focus = do_it;
2010 void WM_init_native_pixels(bool do_it)
2012 wm_init_state.native_pixels = do_it;
2015 void WM_init_tablet_api(void)
2018 switch (U.tablet_api) {
2019 case USER_TABLET_NATIVE:
2020 GHOST_SetTabletAPI(g_system, GHOST_kTabletNative);
2022 case USER_TABLET_WINTAB:
2023 GHOST_SetTabletAPI(g_system, GHOST_kTabletWintab);
2025 case USER_TABLET_AUTOMATIC:
2027 GHOST_SetTabletAPI(g_system, GHOST_kTabletAutomatic);
2033 /* This function requires access to the GHOST_SystemHandle (g_system) */
2034 void WM_cursor_warp(wmWindow *win, int x, int y)
2036 if (win && win->ghostwin) {
2037 int oldx = x, oldy = y;
2039 wm_cursor_position_to_ghost(win, &x, &y);
2040 GHOST_SetCursorPosition(g_system, x, y);
2042 win->eventstate->prevx = oldx;
2043 win->eventstate->prevy = oldy;
2045 win->eventstate->x = oldx;
2046 win->eventstate->y = oldy;
2051 * Set x, y to values we can actually position the cursor to.
2053 void WM_cursor_compatible_xy(wmWindow *win, int *x, int *y)
2055 float f = GHOST_GetNativePixelSize(win->ghostwin);
2057 *x = (int)(*x / f) * f;
2058 *y = (int)(*y / f) * f;
2063 * Get the cursor pressure, in most cases you'll want to use wmTabletData from the event
2065 float WM_cursor_pressure(const struct wmWindow *win)
2067 const GHOST_TabletData *td = GHOST_GetTabletData(win->ghostwin);
2068 /* if there's tablet data from an active tablet device then add it */
2069 if ((td != NULL) && td->Active != GHOST_kTabletModeNone) {
2070 return wm_pressure_curve(td->Pressure);
2077 /* support for native pixel size */
2078 /* mac retina opens window in size X, but it has up to 2 x more pixels */
2079 int WM_window_pixels_x(const wmWindow *win)
2081 float f = GHOST_GetNativePixelSize(win->ghostwin);
2083 return (int)(f * (float)win->sizex);
2085 int WM_window_pixels_y(const wmWindow *win)
2087 float f = GHOST_GetNativePixelSize(win->ghostwin);
2089 return (int)(f * (float)win->sizey);
2093 * Get boundaries usable by all window contents, including global areas.
2095 void WM_window_rect_calc(const wmWindow *win, rcti *r_rect)
2097 BLI_rcti_init(r_rect, 0, WM_window_pixels_x(win), 0, WM_window_pixels_y(win));
2100 * Get boundaries usable by screen-layouts, excluding global areas.
2101 * \note Depends on U.dpi_fac. Should that be outdated, call #WM_window_set_dpi first.
2103 void WM_window_screen_rect_calc(const wmWindow *win, rcti *r_rect)
2105 rcti window_rect, screen_rect;
2107 WM_window_rect_calc(win, &window_rect);
2108 screen_rect = window_rect;
2110 /* Subtract global areas from screen rectangle. */
2111 for (ScrArea *global_area = win->global_areas.areabase.first; global_area; global_area = global_area->next) {
2112 int height = ED_area_global_size_y(global_area) - 1;
2114 if (global_area->global->flag & GLOBAL_AREA_IS_HIDDEN) {
2118 switch (global_area->global->align) {
2119 case GLOBAL_AREA_ALIGN_TOP:
2120 if ((screen_rect.ymax - height) > window_rect.ymin) {
2121 height += U.pixelsize;
2123 if (screen_rect.ymax < (window_rect.ymax - 1)) {
2124 height += U.pixelsize;
2126 screen_rect.ymax -= height;
2128 case GLOBAL_AREA_ALIGN_BOTTOM:
2129 if (screen_rect.ymin > window_rect.ymin) {
2130 height += U.pixelsize;
2132 if ((screen_rect.ymin + height) < (window_rect.ymax - 1)) {
2133 height += U.pixelsize;
2135 screen_rect.ymin += height;
2143 BLI_assert(screen_rect.xmin < screen_rect.xmax);
2144 BLI_assert(screen_rect.ymin < screen_rect.ymax);
2145 *r_rect = screen_rect;
2148 bool WM_window_is_fullscreen(wmWindow *win)
2150 return win->windowstate == GHOST_kWindowStateFullScreen;
2154 * Some editor data may need to be synced with scene data (3D View camera and layers).
2155 * This function ensures data is synced for editors in visible workspaces and their visible layouts.
2157 void WM_windows_scene_data_sync(const ListBase *win_lb, Scene *scene)
2159 for (wmWindow *win = win_lb->first; win; win = win->next) {
2160 if (WM_window_get_active_scene(win) == scene) {
2161 ED_workspace_scene_data_sync(win->workspace_hook, scene);
2166 Scene *WM_windows_scene_get_from_screen(const wmWindowManager *wm, const bScreen *screen)
2168 for (wmWindow *win = wm->windows.first; win; win = win->next) {
2169 if (WM_window_get_active_screen(win) == screen) {
2170 return WM_window_get_active_scene(win);
2177 WorkSpace *WM_windows_workspace_get_from_screen(const wmWindowManager *wm, const bScreen *screen)
2179 for (wmWindow *win = wm->windows.first; win; win = win->next) {
2180 if (WM_window_get_active_screen(win) == screen) {
2181 return WM_window_get_active_workspace(win);
2187 Scene *WM_window_get_active_scene(const wmWindow *win)
2193 * \warning Only call outside of area/region loops
2195 void WM_window_set_active_scene(Main *bmain, bContext *C, wmWindow *win, Scene *scene)
2197 wmWindowManager *wm = CTX_wm_manager(C);
2198 wmWindow *win_parent = (win->parent) ? win->parent : win;
2199 bool changed = false;
2201 /* Set scene in parent and its child windows. */
2202 if (win_parent->scene != scene) {
2203 ED_screen_scene_change(C, win_parent, scene);
2207 for (wmWindow *win_child = wm->windows.first; win_child; win_child = win_child->next) {
2208 if (win_child->parent == win_parent && win_child->scene != scene) {
2209 ED_screen_scene_change(C, win_child, scene);
2215 /* Update depsgraph and renderers for scene change. */
2216 ViewLayer *view_layer = WM_window_get_active_view_layer(win_parent);
2217 ED_scene_change_update(bmain, scene, view_layer);
2219 /* Complete redraw. */
2220 WM_event_add_notifier(C, NC_WINDOW, NULL);
2224 ViewLayer *WM_window_get_active_view_layer(const wmWindow *win)
2226 Scene *scene = WM_window_get_active_scene(win);
2227 if (scene == NULL) {
2231 ViewLayer *view_layer = BKE_view_layer_find(scene, win->view_layer_name);
2236 view_layer = BKE_view_layer_default_view(scene);
2238 WM_window_set_active_view_layer((wmWindow *)win, view_layer);
2244 void WM_window_set_active_view_layer(wmWindow *win, ViewLayer *view_layer)
2246 BLI_assert(BKE_view_layer_find(WM_window_get_active_scene(win), view_layer->name) != NULL);
2248 wmWindowManager *wm = G_MAIN->wm.first;
2249 wmWindow *win_parent = (win->parent) ? win->parent : win;
2251 /* Set view layer in parent and child windows. */
2252 STRNCPY(win_parent->view_layer_name, view_layer->name);
2254 for (wmWindow *win_child = wm->windows.first; win_child; win_child = win_child->next) {
2255 if (win_child->parent == win_parent) {
2256 STRNCPY(win_child->view_layer_name, view_layer->name);
2261 void WM_window_ensure_active_view_layer(wmWindow *win)
2263 /* Update layer name is correct after scene changes, load without UI, etc. */
2264 Scene *scene = WM_window_get_active_scene(win);
2266 if (scene && BKE_view_layer_find(scene, win->view_layer_name) == NULL) {
2267 ViewLayer *view_layer = BKE_view_layer_default_view(scene);
2268 STRNCPY(win->view_layer_name, view_layer->name);
2272 WorkSpace *WM_window_get_active_workspace(const wmWindow *win)
2274 return BKE_workspace_active_get(win->workspace_hook);
2277 void WM_window_set_active_workspace(bContext *C, wmWindow *win, WorkSpace *workspace)
2279 wmWindowManager *wm = CTX_wm_manager(C);
2280 wmWindow *win_parent = (win->parent) ? win->parent : win;
2282 ED_workspace_change(workspace, C, wm, win);
2284 for (wmWindow *win_child = wm->windows.first; win_child; win_child = win_child->next) {
2285 if (win_child->parent == win_parent) {
2286 bScreen *screen = WM_window_get_active_screen(win_child);
2287 /* Don't change temporary screens, they only serve a single purpose. */
2291 ED_workspace_change(workspace, C, wm, win_child);
2296 WorkSpaceLayout *WM_window_get_active_layout(const wmWindow *win)
2298 const WorkSpace *workspace = WM_window_get_active_workspace(win);
2299 return (LIKELY(workspace != NULL) ? BKE_workspace_active_layout_get(win->workspace_hook) : NULL);
2301 void WM_window_set_active_layout(wmWindow *win, WorkSpace *workspace, WorkSpaceLayout *layout)
2303 BKE_workspace_hook_layout_for_workspace_set(win->workspace_hook, workspace, layout);
2307 * Get the active screen of the active workspace in \a win.
2309 bScreen *WM_window_get_active_screen(const wmWindow *win)
2311 const WorkSpace *workspace = WM_window_get_active_workspace(win);
2312 /* May be NULL in rare cases like closing Blender */
2313 return (LIKELY(workspace != NULL) ? BKE_workspace_active_screen_get(win->workspace_hook) : NULL);
2315 void WM_window_set_active_screen(wmWindow *win, WorkSpace *workspace, bScreen *screen)
2317 BKE_workspace_active_screen_set(win->workspace_hook, workspace, screen);
2320 bool WM_window_is_temp_screen(const wmWindow *win)
2322 const bScreen *screen = WM_window_get_active_screen(win);
2323 return (screen && screen->temp != 0);
2327 #ifdef WITH_INPUT_IME
2328 /* note: keep in mind wm_window_IME_begin is also used to reposition the IME window */
2329 void wm_window_IME_begin(wmWindow *win, int x, int y, int w, int h, bool complete)
2333 GHOST_BeginIME(win->ghostwin, x, win->sizey - y, w, h, complete);
2336 void wm_window_IME_end(wmWindow *win)
2338 BLI_assert(win && win->ime_data);
2340 GHOST_EndIME(win->ghostwin);
2341 win->ime_data = NULL;
2343 #endif /* WITH_INPUT_IME */
2345 /* ****** direct opengl context management ****** */
2347 void *WM_opengl_context_create(void)
2349 /* On Windows there is a problem creating contexts that share lists
2350 * from one context that is current in another thread.
2351 * So we should call this function only on the main thread.
2353 BLI_assert(BLI_thread_is_main());
2354 BLI_assert(GPU_framebuffer_active_get() == NULL);
2355 return GHOST_CreateOpenGLContext(g_system);
2358 void WM_opengl_context_dispose(void *context)
2360 BLI_assert(GPU_framebuffer_active_get() == NULL);
2361 GHOST_DisposeOpenGLContext(g_system, (GHOST_ContextHandle)context);
2364 void WM_opengl_context_activate(void *context)
2366 BLI_assert(GPU_framebuffer_active_get() == NULL);
2367 GHOST_ActivateOpenGLContext((GHOST_ContextHandle)context);
2370 void WM_opengl_context_release(void *context)
2372 BLI_assert(GPU_framebuffer_active_get() == NULL);
2373 GHOST_ReleaseOpenGLContext((GHOST_ContextHandle)context);