2 * ***** BEGIN GPL LICENSE BLOCK *****
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * The Original Code is Copyright (C) 2007 Blender Foundation but based
19 * on ghostwinlay.c (C) 2001-2002 by NaN Holding BV
20 * All rights reserved.
22 * Contributor(s): Blender Foundation, 2008
24 * ***** END GPL LICENSE BLOCK *****
27 /** \file blender/windowmanager/intern/wm_window.c
30 * Window management, wrap GHOST.
38 #include "DNA_listBase.h"
39 #include "DNA_screen_types.h"
40 #include "DNA_windowmanager_types.h"
41 #include "DNA_workspace_types.h"
43 #include "MEM_guardedalloc.h"
45 #include "GHOST_C-api.h"
48 #include "BLI_blenlib.h"
49 #include "BLI_utildefines.h"
51 #include "BLT_translation.h"
53 #include "BKE_blender.h"
54 #include "BKE_context.h"
55 #include "BKE_icons.h"
56 #include "BKE_library.h"
57 #include "BKE_global.h"
59 #include "BKE_screen.h"
60 #include "BKE_workspace.h"
63 #include "RNA_access.h"
64 #include "RNA_define.h"
70 #include "wm_window.h"
71 #include "wm_subwindow.h"
72 #include "wm_event_system.h"
75 #include "ED_screen.h"
76 #include "ED_fileselect.h"
78 #include "UI_interface.h"
79 #include "UI_interface_icons.h"
84 #include "GPU_extensions.h"
85 #include "GPU_init_exit.h"
86 #include "GPU_immediate.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 WinOverrideFlag {
100 WIN_OVERRIDE_GEOM = (1 << 0),
101 WIN_OVERRIDE_WINSTATE = (1 << 1)
104 /* set by commandline */
105 static struct WMInitStruct {
106 /* window geometry */
108 int start_x, start_y;
111 WinOverrideFlag override_flag;
114 } wm_init_state = {0, 0, 0, 0, GHOST_kWindowStateNormal, 0, true};
116 /* ******** win open & close ************ */
118 /* XXX this one should correctly check for apple top header...
119 * done for Cocoa : returns window contents (and not frame) max size*/
120 void wm_get_screensize(int *r_width, int *r_height)
122 unsigned int uiwidth;
123 unsigned int uiheight;
125 GHOST_GetMainDisplayDimensions(g_system, &uiwidth, &uiheight);
127 *r_height = uiheight;
130 /* size of all screens (desktop), useful since the mouse is bound by this */
131 void wm_get_desktopsize(int *r_width, int *r_height)
133 unsigned int uiwidth;
134 unsigned int uiheight;
136 GHOST_GetAllDisplayDimensions(g_system, &uiwidth, &uiheight);
138 *r_height = uiheight;
141 /* keeps offset and size within monitor bounds */
142 /* XXX solve dual screen... */
143 static void wm_window_check_position(rcti *rect)
145 int width, height, d;
147 wm_get_screensize(&width, &height);
149 if (rect->xmin < 0) {
150 rect->xmax -= rect->xmin;
153 if (rect->ymin < 0) {
154 rect->ymax -= rect->ymin;
157 if (rect->xmax > width) {
158 d = rect->xmax - width;
162 if (rect->ymax > height) {
163 d = rect->ymax - height;
168 if (rect->xmin < 0) rect->xmin = 0;
169 if (rect->ymin < 0) rect->ymin = 0;
173 static void wm_ghostwindow_destroy(wmWindow *win)
176 GHOST_DisposeWindow(g_system, win->ghostwin);
177 win->ghostwin = NULL;
178 win->multisamples = 0;
182 /* including window itself, C can be NULL.
183 * ED_screen_exit should have been called */
184 void wm_window_free(bContext *C, wmWindowManager *wm, wmWindow *win)
186 wmTimer *wt, *wtnext;
190 WM_event_remove_handlers(C, &win->handlers);
191 WM_event_remove_handlers(C, &win->modalhandlers);
193 if (CTX_wm_window(C) == win)
194 CTX_wm_window_set(C, NULL);
197 /* always set drawable and active to NULL,
198 * prevents non-drawable state of main windows (bugs #22967 and #25071, possibly #22477 too) */
199 wm->windrawable = NULL;
200 wm->winactive = NULL;
202 /* end running jobs, a job end also removes its timer */
203 for (wt = wm->timers.first; wt; wt = wtnext) {
205 if (wt->win == win && wt->event_type == TIMERJOBS)
206 wm_jobs_timer_ended(wm, wt);
209 /* timer removing, need to call this api function */
210 for (wt = wm->timers.first; wt; wt = wtnext) {
213 WM_event_remove_timer(wm, win, wt);
216 if (win->eventstate) MEM_freeN(win->eventstate);
218 wm_event_free_all(win);
219 wm_subwindows_free(win);
221 wm_draw_data_free(win);
223 wm_ghostwindow_destroy(win);
225 BKE_workspace_instance_hook_free(G.main, win->workspace_hook);
226 MEM_freeN(win->stereo3d_format);
231 static int find_free_winid(wmWindowManager *wm)
236 for (win = wm->windows.first; win; win = win->next)
237 if (id <= win->winid)
243 /* don't change context itself */
244 wmWindow *wm_window_new(bContext *C)
246 Main *bmain = CTX_data_main(C);
247 wmWindowManager *wm = CTX_wm_manager(C);
248 wmWindow *win = MEM_callocN(sizeof(wmWindow), "window");
250 BLI_addtail(&wm->windows, win);
251 win->winid = find_free_winid(wm);
253 win->stereo3d_format = MEM_callocN(sizeof(Stereo3dFormat), "Stereo 3D Format (window)");
254 win->workspace_hook = BKE_workspace_instance_hook_create(bmain);
260 * A higher level version of copy that tests the new window can be added.
262 static wmWindow *wm_window_new_test(bContext *C)
264 wmWindow *win = wm_window_new(C);
269 WM_event_add_notifier(C, NC_WINDOW | NA_ADDED, NULL);
273 wmWindowManager *wm = CTX_wm_manager(C);
274 wm_window_close(C, wm, win);
279 /* part of wm_window.c api */
280 wmWindow *wm_window_copy(bContext *C, wmWindow *win_src, const bool duplicate_layout)
282 wmWindow *win_dst = wm_window_new(C);
283 WorkSpace *workspace = WM_window_get_active_workspace(win_src);
284 WorkSpaceLayout *layout_old = WM_window_get_active_layout(win_src);
285 Scene *scene = WM_window_get_active_scene(win_src);
286 WorkSpaceLayout *layout_new;
289 win_dst->posx = win_src->posx + 10;
290 win_dst->posy = win_src->posy;
291 win_dst->sizex = win_src->sizex;
292 win_dst->sizey = win_src->sizey;
294 win_dst->scene = scene;
295 WM_window_set_active_workspace(win_dst, workspace);
296 layout_new = duplicate_layout ? ED_workspace_layout_duplicate(workspace, layout_old, win_dst) : layout_old;
297 WM_window_set_active_layout(win_dst, workspace, layout_new);
298 new_screen = WM_window_get_active_screen(win_dst);
299 BLI_strncpy(win_dst->screenname, new_screen->id.name + 2, sizeof(win_dst->screenname));
301 win_dst->drawmethod = U.wmdrawmethod;
303 BLI_listbase_clear(&win_dst->drawdata);
305 *win_dst->stereo3d_format = *win_src->stereo3d_format;
311 * A higher level version of copy that tests the new window can be added.
312 * (called from the operator directly)
314 wmWindow *wm_window_copy_test(bContext *C, wmWindow *win_src, const bool duplicate_layout)
316 wmWindowManager *wm = CTX_wm_manager(C);
319 win_dst = wm_window_copy(C, win_src, duplicate_layout);
323 if (win_dst->ghostwin) {
324 WM_event_add_notifier(C, NC_WINDOW | NA_ADDED, NULL);
328 wm_window_close(C, wm, win_dst);
333 /* this is event from ghost, or exit-blender op */
334 void wm_window_close(bContext *C, wmWindowManager *wm, wmWindow *win)
337 bool do_exit = false;
339 /* first check if we have to quit (there are non-temp remaining windows) */
340 for (tmpwin = wm->windows.first; tmpwin; tmpwin = tmpwin->next) {
343 if (WM_window_is_temp_screen(tmpwin) == false)
350 if ((U.uiflag & USER_QUIT_PROMPT) && !wm->file_saved && !G.background) {
352 if (!GHOST_confirmQuit(win->ghostwin))
357 /* let WM_exit do all freeing, for correct quit.blend save */
362 bScreen *screen = WM_window_get_active_screen(win);
363 WorkSpace *workspace = WM_window_get_active_workspace(win);
364 WorkSpaceLayout *layout = BKE_workspace_active_layout_get(win->workspace_hook);
366 BLI_remlink(&wm->windows, win);
368 wm_draw_window_clear(win);
370 CTX_wm_window_set(C, win); /* needed by handlers */
371 WM_event_remove_handlers(C, &win->handlers);
372 WM_event_remove_handlers(C, &win->modalhandlers);
374 /* for regular use this will _never_ be NULL,
375 * however we may be freeing an improperly initialized window. */
377 ED_screen_exit(C, win, screen);
380 wm_window_free(C, wm, win);
382 /* if temp screen, delete it after window free (it stops jobs that can access it) */
383 if (screen && screen->temp) {
384 Main *bmain = CTX_data_main(C);
386 BLI_assert(BKE_workspace_layout_screen_get(layout) == screen);
387 BKE_workspace_layout_remove(bmain, workspace, layout);
392 void wm_window_title(wmWindowManager *wm, wmWindow *win)
394 if (WM_window_is_temp_screen(win)) {
395 /* nothing to do for 'temp' windows,
396 * because WM_window_open_temp always sets window title */
398 else if (win->ghostwin) {
399 /* this is set to 1 if you don't have startup.blend open */
400 if (G.save_over && G.main->name[0]) {
401 char str[sizeof(G.main->name) + 24];
402 BLI_snprintf(str, sizeof(str), "Blender%s [%s%s]", wm->file_saved ? "" : "*", G.main->name,
403 G.main->recovered ? " (Recovered)" : "");
404 GHOST_SetTitle(win->ghostwin, str);
407 GHOST_SetTitle(win->ghostwin, "Blender");
409 /* Informs GHOST of unsaved changes, to set window modified visual indicator (MAC OS X)
410 * and to give hint of unsaved changes for a user warning mechanism
411 * in case of OS application terminate request (e.g. OS Shortcut Alt+F4, Cmd+Q, (...), or session end) */
412 GHOST_SetWindowModifiedState(win->ghostwin, (GHOST_TUns8) !wm->file_saved);
417 void WM_window_set_dpi(wmWindow *win)
419 int auto_dpi = GHOST_GetDPIHint(win->ghostwin);
421 /* Lazily init UI scale size, preserving backwards compatibility by
422 * computing UI scale from ratio of previous DPI and auto DPI */
423 if (U.ui_scale == 0) {
424 int virtual_pixel = (U.virtual_pixel == VIRTUAL_PIXEL_NATIVE) ? 1 : 2;
427 U.ui_scale = virtual_pixel;
430 U.ui_scale = (virtual_pixel * U.dpi * 96.0f) / (auto_dpi * 72.0f);
433 CLAMP(U.ui_scale, 0.25f, 4.0f);
436 /* Blender's UI drawing assumes DPI 72 as a good default following macOS
437 * while Windows and Linux use DPI 96. GHOST assumes a default 96 so we
438 * remap the DPI to Blender's convention. */
439 int dpi = auto_dpi * U.ui_scale * (72.0 / 96.0f);
441 /* Automatically set larger pixel size for high DPI. */
442 int pixelsize = MAX2(1, dpi / 54);
444 /* Set user preferences globals for drawing, and for forward compatibility. */
445 U.pixelsize = GHOST_GetNativePixelSize(win->ghostwin) * pixelsize;
446 U.dpi = dpi / pixelsize;
447 U.virtual_pixel = (pixelsize == 1) ? VIRTUAL_PIXEL_NATIVE : VIRTUAL_PIXEL_DOUBLE;
448 U.widget_unit = (U.pixelsize * U.dpi * 20 + 36) / 72;
450 /* update font drawing */
451 BLF_default_dpi(U.pixelsize * U.dpi);
454 /* belongs to below */
455 static void wm_window_ghostwindow_add(wmWindowManager *wm, const char *title, wmWindow *win)
457 GHOST_WindowHandle ghostwin;
458 GHOST_GLSettings glSettings = {0};
459 static int multisamples = -1;
460 int scr_w, scr_h, posy;
462 /* force setting multisamples only once, it requires restart - and you cannot
463 * mix it, either all windows have it, or none (tested in OSX opengl) */
464 if (multisamples == -1)
465 multisamples = U.ogl_multisamples;
467 glSettings.numOfAASamples = multisamples;
469 /* a new window is created when pageflip mode is required for a window */
470 if (win->stereo3d_format->display_mode == S3D_DISPLAY_PAGEFLIP)
471 glSettings.flags |= GHOST_glStereoVisual;
473 if (G.debug & G_DEBUG_GPU) {
474 glSettings.flags |= GHOST_glDebugContext;
477 wm_get_screensize(&scr_w, &scr_h);
478 posy = (scr_h - win->posy - win->sizey);
480 ghostwin = GHOST_CreateWindow(g_system, title,
481 win->posx, posy, win->sizex, win->sizey,
483 /* we agreed to not set any fullscreen or iconized state on startup */
484 GHOST_kWindowStateNormal,
486 (GHOST_TWindowState)win->windowstate,
488 GHOST_kDrawingContextTypeOpenGL,
492 GHOST_RectangleHandle bounds;
494 /* the new window has already been made drawable upon creation */
495 wm->windrawable = win;
497 /* needed so we can detect the graphics card below */
500 win->ghostwin = ghostwin;
501 GHOST_SetWindowUserData(ghostwin, win); /* pointer back */
503 if (win->eventstate == NULL)
504 win->eventstate = MEM_callocN(sizeof(wmEvent), "window event state");
506 /* store multisamples window was created with, in case user prefs change */
507 win->multisamples = multisamples;
509 /* store actual window size in blender window */
510 bounds = GHOST_GetClientBounds(win->ghostwin);
511 win->sizex = GHOST_GetWidthRectangle(bounds);
512 win->sizey = GHOST_GetHeightRectangle(bounds);
513 GHOST_DisposeRectangle(bounds);
516 /* set the state here, so minimized state comes up correct on windows */
517 GHOST_SetWindowState(ghostwin, (GHOST_TWindowState)win->windowstate);
519 /* until screens get drawn, make it nice gray */
520 glClearColor(0.55, 0.55, 0.55, 0.0);
521 /* Crash on OSS ATI: bugs.launchpad.net/ubuntu/+source/mesa/+bug/656100 */
522 if (!GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_UNIX, GPU_DRIVER_OPENSOURCE)) {
523 glClear(GL_COLOR_BUFFER_BIT);
526 /* needed here, because it's used before it reads userdef */
527 WM_window_set_dpi(win);
529 wm_window_swap_buffers(win);
531 //GHOST_SetWindowState(ghostwin, GHOST_kWindowStateModified);
533 /* standard state vars for window */
534 glEnable(GL_SCISSOR_TEST);
540 * Initialize #wmWindows without ghostwin, open these and clear.
542 * window size is read from window, if 0 it uses prefsize
543 * called in #WM_check, also inits stuff after file read.
546 * After running, 'win->ghostwin' can be NULL in rare cases
547 * (where OpenGL driver fails to create a context for eg).
548 * We could remove them with #wm_window_ghostwindows_remove_invalid
549 * but better not since caller may continue to use.
550 * Instead, caller needs to handle the error case and cleanup.
552 void wm_window_ghostwindows_ensure(wmWindowManager *wm)
557 BLI_assert(G.background == false);
559 /* no commandline prefsize? then we set this.
560 * Note that these values will be used only
561 * when there is no startup.blend yet.
563 if (wm_init_state.size_x == 0) {
564 wm_get_screensize(&wm_init_state.size_x, &wm_init_state.size_y);
566 /* note!, this isnt quite correct, active screen maybe offset 1000s if PX,
567 * we'd need a wm_get_screensize like function that gives offset,
568 * in practice the window manager will likely move to the correct monitor */
569 wm_init_state.start_x = 0;
570 wm_init_state.start_y = 0;
572 #ifdef WITH_X11 /* X11 */
573 /* X11, start maximized but use default sane size */
574 wm_init_state.size_x = min_ii(wm_init_state.size_x, WM_WIN_INIT_SIZE_X);
575 wm_init_state.size_y = min_ii(wm_init_state.size_y, WM_WIN_INIT_SIZE_Y);
577 wm_init_state.start_x = WM_WIN_INIT_PAD;
578 wm_init_state.start_y = WM_WIN_INIT_PAD;
579 wm_init_state.size_x -= WM_WIN_INIT_PAD * 2;
580 wm_init_state.size_y -= WM_WIN_INIT_PAD * 2;
584 for (win = wm->windows.first; win; win = win->next) {
585 if (win->ghostwin == NULL) {
586 if ((win->sizex == 0) || (wm_init_state.override_flag & WIN_OVERRIDE_GEOM)) {
587 win->posx = wm_init_state.start_x;
588 win->posy = wm_init_state.start_y;
589 win->sizex = wm_init_state.size_x;
590 win->sizey = wm_init_state.size_y;
592 win->windowstate = GHOST_kWindowStateNormal;
593 wm_init_state.override_flag &= ~WIN_OVERRIDE_GEOM;
596 if (wm_init_state.override_flag & WIN_OVERRIDE_WINSTATE) {
597 win->windowstate = wm_init_state.windowstate;
598 wm_init_state.override_flag &= ~WIN_OVERRIDE_WINSTATE;
601 /* without this, cursor restore may fail, T45456 */
602 if (win->cursor == 0) {
603 win->cursor = CURSOR_STD;
606 wm_window_ghostwindow_add(wm, "Blender", win);
608 /* happens after fileread */
609 if (win->eventstate == NULL)
610 win->eventstate = MEM_callocN(sizeof(wmEvent), "window event state");
612 /* add keymap handlers (1 handler for all keys in map!) */
613 keymap = WM_keymap_find(wm->defaultconf, "Window", 0, 0);
614 WM_event_add_keymap_handler(&win->handlers, keymap);
616 keymap = WM_keymap_find(wm->defaultconf, "Screen", 0, 0);
617 WM_event_add_keymap_handler(&win->handlers, keymap);
619 keymap = WM_keymap_find(wm->defaultconf, "Screen Editing", 0, 0);
620 WM_event_add_keymap_handler(&win->modalhandlers, keymap);
624 ListBase *lb = WM_dropboxmap_find("Window", 0, 0);
625 WM_event_add_dropbox_handler(&win->handlers, lb);
627 wm_window_title(wm, win);
632 * Call after #wm_window_ghostwindows_ensure or #WM_check
633 * (after loading a new file) in the unlikely event a window couldn't be created.
635 void wm_window_ghostwindows_remove_invalid(bContext *C, wmWindowManager *wm)
637 wmWindow *win, *win_next;
639 BLI_assert(G.background == false);
641 for (win = wm->windows.first; win; win = win_next) {
642 win_next = win->next;
643 if (win->ghostwin == NULL) {
644 wm_window_close(C, wm, win);
650 * new window, no screen yet, but we open ghostwindow for it,
651 * also gets the window level handlers
652 * \note area-rip calls this.
653 * \return the window or NULL.
655 wmWindow *WM_window_open(bContext *C, const rcti *rect)
657 wmWindow *win_prev = CTX_wm_window(C);
658 wmWindow *win = wm_window_new(C);
660 win->posx = rect->xmin;
661 win->posy = rect->ymin;
662 win->sizex = BLI_rcti_size_x(rect);
663 win->sizey = BLI_rcti_size_y(rect);
665 win->drawmethod = U.wmdrawmethod;
673 wm_window_close(C, CTX_wm_manager(C), win);
674 CTX_wm_window_set(C, win_prev);
680 * Uses `screen->temp` tag to define what to do, currently it limits
681 * to only one "temp" window for render out, preferences, filewindow, etc...
683 * \param type: WM_WINDOW_RENDER, WM_WINDOW_USERPREFS...
684 * \return the window or NULL.
686 wmWindow *WM_window_open_temp(bContext *C, int x, int y, int sizex, int sizey, int type)
688 Main *bmain = CTX_data_main(C);
689 wmWindow *win_prev = CTX_wm_window(C);
693 Scene *scene = CTX_data_scene(C);
696 /* convert to native OS window coordinates */
697 const float native_pixel_size = GHOST_GetNativePixelSize(win_prev->ghostwin);
698 x /= native_pixel_size;
699 y /= native_pixel_size;
700 sizex /= native_pixel_size;
701 sizey /= native_pixel_size;
703 /* calculate postition */
705 rect.xmin = x + win_prev->posx - sizex / 2;
706 rect.ymin = y + win_prev->posy - sizey / 2;
707 rect.xmax = rect.xmin + sizex;
708 rect.ymax = rect.ymin + sizey;
710 /* changes rect to fit within desktop */
711 wm_window_check_position(&rect);
713 /* test if we have a temp screen already */
714 for (win = CTX_wm_manager(C)->windows.first; win; win = win->next)
715 if (WM_window_is_temp_screen(win))
718 /* add new window? */
720 win = wm_window_new(C);
722 win->posx = rect.xmin;
723 win->posy = rect.ymin;
726 screen = WM_window_get_active_screen(win);
728 win->sizex = BLI_rcti_size_x(&rect);
729 win->sizey = BLI_rcti_size_y(&rect);
732 wm_window_set_size(win, win->sizex, win->sizey);
733 wm_window_raise(win);
736 if (WM_window_get_active_workspace(win) == NULL) {
737 WorkSpace *workspace = WM_window_get_active_workspace(win_prev);
738 WM_window_set_active_workspace(win, workspace);
741 if (screen == NULL) {
742 /* add new screen layout */
743 WorkSpace *workspace = WM_window_get_active_workspace(win);
744 WorkSpaceLayout *layout = ED_workspace_layout_add(workspace, win, "temp");
746 screen = BKE_workspace_layout_screen_get(layout);
747 WM_window_set_active_layout(win, workspace, layout);
750 if (WM_window_get_active_scene(win) != scene) {
751 WM_window_change_active_scene(bmain, C, win, scene);
756 /* make window active, and validate/resize */
757 CTX_wm_window_set(C, win);
760 /* It's possible `win->ghostwin == NULL`.
761 * instead of attempting to cleanup here (in a half finished state),
762 * finish setting up the screen, then free it at the end of the function,
763 * to avoid having to take into account a partially-created window.
766 /* ensure it shows the right spacetype editor */
767 sa = screen->areabase.first;
768 CTX_wm_area_set(C, sa);
770 if (type == WM_WINDOW_RENDER) {
771 ED_area_newspace(C, sa, SPACE_IMAGE, false);
774 ED_area_newspace(C, sa, SPACE_USERPREF, false);
777 ED_screen_change(C, screen);
778 ED_screen_refresh(CTX_wm_manager(C), win); /* test scale */
780 if (sa->spacetype == SPACE_IMAGE)
781 title = IFACE_("Blender Render");
782 else if (ELEM(sa->spacetype, SPACE_OUTLINER, SPACE_USERPREF))
783 title = IFACE_("Blender User Preferences");
784 else if (sa->spacetype == SPACE_FILE)
785 title = IFACE_("Blender File View");
790 GHOST_SetTitle(win->ghostwin, title);
794 /* very unlikely! but opening a new window can fail */
795 wm_window_close(C, CTX_wm_manager(C), win);
796 CTX_wm_window_set(C, win_prev);
803 /* ****************** Operators ****************** */
805 int wm_window_close_exec(bContext *C, wmOperator *UNUSED(op))
807 wmWindowManager *wm = CTX_wm_manager(C);
808 wmWindow *win = CTX_wm_window(C);
809 wm_window_close(C, wm, win);
810 return OPERATOR_FINISHED;
813 static WorkSpaceLayout *wm_window_new_find_layout(wmOperator *op, WorkSpace *workspace)
815 ListBase *listbase = BKE_workspace_layouts_get(workspace);
816 const int layout_id = RNA_enum_get(op->ptr, "screen");
819 for (WorkSpaceLayout *layout = listbase->first; layout; layout = layout->next) {
820 if (i++ == layout_id) {
829 /* new window operator callback */
830 int wm_window_new_exec(bContext *C, wmOperator *op)
832 wmWindow *win_src = CTX_wm_window(C);
833 WorkSpace *workspace = WM_window_get_active_workspace(win_src);
834 WorkSpaceLayout *layout_new = wm_window_new_find_layout(op, workspace);
835 bScreen *screen_new = BKE_workspace_layout_screen_get(layout_new);
838 if ((win_dst = wm_window_new_test(C))) {
839 if (screen_new->winid) {
840 /* layout/screen is already used, duplicate it */
841 layout_new = ED_workspace_layout_duplicate(workspace, layout_new, win_dst);
842 screen_new = BKE_workspace_layout_screen_get(layout_new);
844 /* New window with a different screen but same workspace */
845 WM_window_set_active_workspace(win_dst, workspace);
846 WM_window_set_active_screen(win_dst, workspace, screen_new);
847 win_dst->scene = win_src->scene;
848 screen_new->winid = win_dst->winid;
849 CTX_wm_window_set(C, win_dst);
850 ED_screen_refresh(CTX_wm_manager(C), win_dst);
853 return (win_dst != NULL) ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
856 int wm_window_new_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
858 wmWindow *win = CTX_wm_window(C);
859 WorkSpace *workspace = WM_window_get_active_workspace(win);
860 ListBase *listbase = BKE_workspace_layouts_get(workspace);
862 if (BLI_listbase_count_ex(listbase, 2) == 1) {
863 RNA_enum_set(op->ptr, "screen", 0);
864 return wm_window_new_exec(C, op);
867 return WM_enum_search_invoke_previews(C, op, 6, 2);
871 struct EnumPropertyItem *wm_window_new_screen_itemf(
872 bContext *C, struct PointerRNA *UNUSED(ptr), struct PropertyRNA *UNUSED(prop), bool *r_free)
874 wmWindow *win = CTX_wm_window(C);
875 WorkSpace *workspace = WM_window_get_active_workspace(win);
876 ListBase *listbase = BKE_workspace_layouts_get(workspace);
877 EnumPropertyItem *item = NULL;
878 EnumPropertyItem tmp = {0, "", 0, "", ""};
879 int value = 0, totitem = 0;
880 int count_act_screens = 0;
881 /* XXX setting max number of windows to 20. We'd need support
882 * for dynamic strings in EnumPropertyItem.name to avoid this. */
883 static char active_screens[20][MAX_NAME + 12];
885 for (WorkSpaceLayout *layout = listbase->first; layout; layout = layout->next) {
886 bScreen *screen = BKE_workspace_layout_screen_get(layout);
887 const char *layout_name = BKE_workspace_layout_name_get(layout);
890 BLI_snprintf(active_screens[count_act_screens], sizeof(*active_screens), "%s (Duplicate)", layout_name);
891 tmp.name = active_screens[count_act_screens++];
894 tmp.name = layout_name;
898 tmp.identifier = layout_name;
899 UI_id_icon_render(C, CTX_data_scene(C), &screen->id, true, false);
900 tmp.icon = BKE_icon_id_ensure(&screen->id);
902 RNA_enum_item_add(&item, &totitem, &tmp);
906 RNA_enum_item_end(&item, &totitem);
912 /* fullscreen operator callback */
913 int wm_window_fullscreen_toggle_exec(bContext *C, wmOperator *UNUSED(op))
915 wmWindow *window = CTX_wm_window(C);
916 GHOST_TWindowState state;
919 return OPERATOR_CANCELLED;
921 state = GHOST_GetWindowState(window->ghostwin);
922 if (state != GHOST_kWindowStateFullScreen)
923 GHOST_SetWindowState(window->ghostwin, GHOST_kWindowStateFullScreen);
925 GHOST_SetWindowState(window->ghostwin, GHOST_kWindowStateNormal);
927 return OPERATOR_FINISHED;
932 /* ************ events *************** */
934 void wm_cursor_position_from_ghost(wmWindow *win, int *x, int *y)
936 float fac = GHOST_GetNativePixelSize(win->ghostwin);
938 GHOST_ScreenToClient(win->ghostwin, *x, *y, x, y);
941 *y = (win->sizey - 1) - *y;
945 void wm_cursor_position_to_ghost(wmWindow *win, int *x, int *y)
947 float fac = GHOST_GetNativePixelSize(win->ghostwin);
951 *y = win->sizey - *y - 1;
953 GHOST_ClientToScreen(win->ghostwin, *x, *y, x, y);
956 void wm_get_cursor_position(wmWindow *win, int *x, int *y)
958 GHOST_GetCursorPosition(g_system, x, y);
959 wm_cursor_position_from_ghost(win, x, y);
969 /* check if specified modifier key type is pressed */
970 static int query_qual(modifierKeyType qual)
972 GHOST_TModifierKeyMask left, right;
977 left = GHOST_kModifierKeyLeftShift;
978 right = GHOST_kModifierKeyRightShift;
981 left = GHOST_kModifierKeyLeftControl;
982 right = GHOST_kModifierKeyRightControl;
985 left = right = GHOST_kModifierKeyOS;
989 left = GHOST_kModifierKeyLeftAlt;
990 right = GHOST_kModifierKeyRightAlt;
994 GHOST_GetModifierKeyState(g_system, left, &val);
996 GHOST_GetModifierKeyState(g_system, right, &val);
1001 void wm_window_make_drawable(wmWindowManager *wm, wmWindow *win)
1003 if (win != wm->windrawable && win->ghostwin) {
1004 // win->lmbut = 0; /* keeps hanging when mousepressed while other window opened */
1006 wm->windrawable = win;
1007 if (G.debug & G_DEBUG_EVENTS) {
1008 printf("%s: set drawable %d\n", __func__, win->winid);
1012 GHOST_ActivateWindowDrawingContext(win->ghostwin);
1015 /* this can change per window */
1016 WM_window_set_dpi(win);
1020 /* called by ghost, here we handle events for windows themselves or send to event system */
1021 /* mouse coordinate converversion happens here */
1022 static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr)
1024 bContext *C = C_void_ptr;
1025 wmWindowManager *wm = CTX_wm_manager(C);
1026 GHOST_TEventType type = GHOST_GetEventType(evt);
1027 int time = GHOST_GetEventTime(evt);
1029 if (type == GHOST_kEventQuit) {
1033 GHOST_WindowHandle ghostwin = GHOST_GetEventWindow(evt);
1034 GHOST_TEventDataPtr data = GHOST_GetEventData(evt);
1037 /* Ghost now can call this function for life resizes, but it should return if WM didn't initialize yet.
1038 * Can happen on file read (especially full size window) */
1039 if ((wm->initialized & WM_INIT_WINDOW) == 0) {
1043 /* XXX - should be checked, why are we getting an event here, and */
1045 puts("<!> event has no window");
1048 else if (!GHOST_ValidWindow(g_system, ghostwin)) {
1049 /* XXX - should be checked, why are we getting an event here, and */
1051 puts("<!> event has invalid window");
1055 win = GHOST_GetWindowUserData(ghostwin);
1059 case GHOST_kEventWindowDeactivate:
1060 wm_event_add_ghostevent(wm, win, type, time, data);
1061 win->active = 0; /* XXX */
1063 /* clear modifiers for inactive windows */
1064 win->eventstate->alt = 0;
1065 win->eventstate->ctrl = 0;
1066 win->eventstate->shift = 0;
1067 win->eventstate->oskey = 0;
1068 win->eventstate->keymodifier = 0;
1071 case GHOST_kEventWindowActivate:
1073 GHOST_TEventKeyData kdata;
1076 const int keymodifier = ((query_qual(SHIFT) ? KM_SHIFT : 0) |
1077 (query_qual(CONTROL) ? KM_CTRL : 0) |
1078 (query_qual(ALT) ? KM_ALT : 0) |
1079 (query_qual(OS) ? KM_OSKEY : 0));
1081 /* Win23/GHOST modifier bug, see T40317 */
1083 //# define USE_WIN_ACTIVATE
1086 wm->winactive = win; /* no context change! c->wm->windrawable is drawable, or for area queues */
1089 // window_handle(win, INPUTCHANGE, win->active);
1091 /* bad ghost support for modifier keys... so on activate we set the modifiers again */
1093 /* TODO: This is not correct since a modifier may be held when a window is activated...
1094 * better solve this at ghost level. attempted fix r54450 but it caused bug [#34255]
1096 * For now don't send GHOST_kEventKeyDown events, just set the 'eventstate'.
1099 kdata.utf8_buf[0] = '\0';
1101 if (win->eventstate->shift) {
1102 if ((keymodifier & KM_SHIFT) == 0) {
1103 kdata.key = GHOST_kKeyLeftShift;
1104 wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, time, &kdata);
1107 #ifdef USE_WIN_ACTIVATE
1109 if (keymodifier & KM_SHIFT) {
1110 win->eventstate->shift = KM_MOD_FIRST;
1114 if (win->eventstate->ctrl) {
1115 if ((keymodifier & KM_CTRL) == 0) {
1116 kdata.key = GHOST_kKeyLeftControl;
1117 wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, time, &kdata);
1120 #ifdef USE_WIN_ACTIVATE
1122 if (keymodifier & KM_CTRL) {
1123 win->eventstate->ctrl = KM_MOD_FIRST;
1127 if (win->eventstate->alt) {
1128 if ((keymodifier & KM_ALT) == 0) {
1129 kdata.key = GHOST_kKeyLeftAlt;
1130 wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, time, &kdata);
1133 #ifdef USE_WIN_ACTIVATE
1135 if (keymodifier & KM_ALT) {
1136 win->eventstate->alt = KM_MOD_FIRST;
1140 if (win->eventstate->oskey) {
1141 if ((keymodifier & KM_OSKEY) == 0) {
1142 kdata.key = GHOST_kKeyOS;
1143 wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, time, &kdata);
1146 #ifdef USE_WIN_ACTIVATE
1148 if (keymodifier & KM_OSKEY) {
1149 win->eventstate->oskey = KM_MOD_FIRST;
1154 #undef USE_WIN_ACTIVATE
1157 /* keymodifier zero, it hangs on hotkeys that open windows otherwise */
1158 win->eventstate->keymodifier = 0;
1160 /* entering window, update mouse pos. but no event */
1161 wm_get_cursor_position(win, &wx, &wy);
1163 win->eventstate->x = wx;
1164 win->eventstate->y = wy;
1166 win->addmousemove = 1; /* enables highlighted buttons */
1168 wm_window_make_drawable(wm, win);
1170 /* window might be focused by mouse click in configuration of window manager
1171 * when focus is not following mouse
1172 * click could have been done on a button and depending on window manager settings
1173 * click would be passed to blender or not, but in any case button under cursor
1174 * should be activated, so at max next click on button without moving mouse
1175 * would trigger it's handle function
1176 * currently it seems to be common practice to generate new event for, but probably
1177 * we'll need utility function for this? (sergey)
1179 wm_event_init_from_window(win, &event);
1180 event.type = MOUSEMOVE;
1181 event.prevx = event.x;
1182 event.prevy = event.y;
1184 wm_event_add(win, &event);
1188 case GHOST_kEventWindowClose:
1190 wm_window_close(C, wm, win);
1193 case GHOST_kEventWindowUpdate:
1195 if (G.debug & G_DEBUG_EVENTS) {
1196 printf("%s: ghost redraw %d\n", __func__, win->winid);
1199 wm_window_make_drawable(wm, win);
1200 WM_event_add_notifier(C, NC_WINDOW, NULL);
1204 case GHOST_kEventWindowSize:
1205 case GHOST_kEventWindowMove:
1207 GHOST_TWindowState state;
1208 state = GHOST_GetWindowState(win->ghostwin);
1209 win->windowstate = state;
1211 /* stop screencast if resize */
1212 if (type == GHOST_kEventWindowSize) {
1213 WM_jobs_stop(wm, WM_window_get_active_screen(win), NULL);
1216 WM_window_set_dpi(win);
1218 /* win32: gives undefined window size when minimized */
1219 if (state != GHOST_kWindowStateMinimized) {
1220 GHOST_RectangleHandle client_rect;
1221 int l, t, r, b, scr_w, scr_h;
1222 int sizex, sizey, posx, posy;
1224 client_rect = GHOST_GetClientBounds(win->ghostwin);
1225 GHOST_GetRectangle(client_rect, &l, &t, &r, &b);
1227 GHOST_DisposeRectangle(client_rect);
1229 wm_get_desktopsize(&scr_w, &scr_h);
1233 posy = scr_h - t - win->sizey;
1236 * Ghost sometimes send size or move events when the window hasn't changed.
1237 * One case of this is using compiz on linux. To alleviate the problem
1238 * we ignore all such event here.
1240 * It might be good to eventually do that at Ghost level, but that is for
1243 if (win->sizex != sizex ||
1244 win->sizey != sizey ||
1245 win->posx != posx ||
1248 const bScreen *screen = WM_window_get_active_screen(win);
1256 if (G.debug & G_DEBUG_EVENTS) {
1257 const char *state_str;
1258 state = GHOST_GetWindowState(win->ghostwin);
1260 if (state == GHOST_kWindowStateNormal) {
1261 state_str = "normal";
1263 else if (state == GHOST_kWindowStateMinimized) {
1264 state_str = "minimized";
1266 else if (state == GHOST_kWindowStateMaximized) {
1267 state_str = "maximized";
1269 else if (state == GHOST_kWindowStateFullScreen) {
1270 state_str = "fullscreen";
1273 state_str = "<unknown>";
1276 printf("%s: window %d state = %s\n", __func__, win->winid, state_str);
1278 if (type != GHOST_kEventWindowSize) {
1279 printf("win move event pos %d %d size %d %d\n",
1280 win->posx, win->posy, win->sizex, win->sizey);
1284 wm_window_make_drawable(wm, win);
1285 wm_draw_window_clear(win);
1286 BKE_icon_changed(screen->id.icon_id);
1287 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
1288 WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, NULL);
1290 #if defined(__APPLE__) || defined(WIN32)
1291 /* OSX and Win32 don't return to the mainloop while resize */
1292 wm_event_do_notifiers(C);
1295 /* Warning! code above nulls 'C->wm.window', causing BGE to quit, see: T45699.
1296 * Further, its easier to match behavior across platforms, so restore the window. */
1297 CTX_wm_window_set(C, win);
1304 case GHOST_kEventWindowDPIHintChanged:
1306 WM_window_set_dpi(win);
1307 /* font's are stored at each DPI level, without this we can easy load 100's of fonts */
1310 WM_main_add_notifier(NC_WINDOW, NULL); /* full redraw */
1311 WM_main_add_notifier(NC_SCREEN | NA_EDITED, NULL); /* refresh region sizes */
1315 case GHOST_kEventOpenMainFile:
1317 PointerRNA props_ptr;
1318 wmWindow *oldWindow;
1319 const char *path = GHOST_GetEventData(evt);
1322 wmOperatorType *ot = WM_operatortype_find("WM_OT_open_mainfile", false);
1323 /* operator needs a valid window in context, ensures
1324 * it is correctly set */
1325 oldWindow = CTX_wm_window(C);
1326 CTX_wm_window_set(C, win);
1328 WM_operator_properties_create_ptr(&props_ptr, ot);
1329 RNA_string_set(&props_ptr, "filepath", path);
1330 WM_operator_name_call_ptr(C, ot, WM_OP_EXEC_DEFAULT, &props_ptr);
1331 WM_operator_properties_free(&props_ptr);
1333 CTX_wm_window_set(C, oldWindow);
1337 case GHOST_kEventDraggingDropDone:
1340 GHOST_TEventDragnDropData *ddd = GHOST_GetEventData(evt);
1343 /* entering window, update mouse pos */
1344 wm_get_cursor_position(win, &wx, &wy);
1345 win->eventstate->x = wx;
1346 win->eventstate->y = wy;
1348 wm_event_init_from_window(win, &event); /* copy last state, like mouse coords */
1350 /* activate region */
1351 event.type = MOUSEMOVE;
1352 event.prevx = event.x;
1353 event.prevy = event.y;
1355 wm->winactive = win; /* no context change! c->wm->windrawable is drawable, or for area queues */
1358 wm_event_add(win, &event);
1361 /* make blender drop event with custom data pointing to wm drags */
1362 event.type = EVT_DROP;
1363 event.val = KM_RELEASE;
1364 event.custom = EVT_DATA_DRAGDROP;
1365 event.customdata = &wm->drags;
1366 event.customdatafree = 1;
1368 wm_event_add(win, &event);
1370 /* printf("Drop detected\n"); */
1372 /* add drag data to wm for paths: */
1374 if (ddd->dataType == GHOST_kDragnDropTypeFilenames) {
1375 GHOST_TStringArray *stra = ddd->data;
1378 for (a = 0; a < stra->count; a++) {
1379 printf("drop file %s\n", stra->strings[a]);
1380 /* try to get icon type from extension */
1381 icon = ED_file_extension_icon((char *)stra->strings[a]);
1383 WM_event_start_drag(C, icon, WM_DRAG_PATH, stra->strings[a], 0.0, WM_DRAG_NOP);
1384 /* void poin should point to string, it makes a copy */
1385 break; /* only one drop element supported now */
1391 case GHOST_kEventNativeResolutionChange:
1393 // only update if the actual pixel size changes
1394 float prev_pixelsize = U.pixelsize;
1395 WM_window_set_dpi(win);
1397 if (U.pixelsize != prev_pixelsize) {
1398 BKE_icon_changed(WM_window_get_active_screen(win)->id.icon_id);
1400 // close all popups since they are positioned with the pixel
1401 // size baked in and it's difficult to correct them
1402 wmWindow *oldWindow = CTX_wm_window(C);
1403 CTX_wm_window_set(C, win);
1404 UI_popup_handlers_remove_all(C, &win->modalhandlers);
1405 CTX_wm_window_set(C, oldWindow);
1407 wm_window_make_drawable(wm, win);
1408 wm_draw_window_clear(win);
1410 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
1411 WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, NULL);
1416 case GHOST_kEventTrackpad:
1418 GHOST_TEventTrackpadData *pd = data;
1420 wm_cursor_position_from_ghost(win, &pd->x, &pd->y);
1421 wm_event_add_ghostevent(wm, win, type, time, data);
1424 case GHOST_kEventCursorMove:
1426 GHOST_TEventCursorData *cd = data;
1428 wm_cursor_position_from_ghost(win, &cd->x, &cd->y);
1429 wm_event_add_ghostevent(wm, win, type, time, data);
1433 wm_event_add_ghostevent(wm, win, type, time, data);
1443 * This timer system only gives maximum 1 timer event per redraw cycle,
1444 * to prevent queues to get overloaded.
1445 * Timer handlers should check for delta to decide if they just update, or follow real time.
1446 * Timer handlers can also set duration to match frames passed
1448 static int wm_window_timer(const bContext *C)
1450 wmWindowManager *wm = CTX_wm_manager(C);
1451 wmTimer *wt, *wtnext;
1453 double time = PIL_check_seconds_timer();
1456 for (wt = wm->timers.first; wt; wt = wtnext) {
1457 wtnext = wt->next; /* in case timer gets removed */
1460 if (wt->sleep == 0) {
1461 if (time > wt->ntime) {
1462 wt->delta = time - wt->ltime;
1463 wt->duration += wt->delta;
1465 wt->ntime = wt->stime + wt->timestep * ceil(wt->duration / wt->timestep);
1467 if (wt->event_type == TIMERJOBS)
1468 wm_jobs_timer(C, wm, wt);
1469 else if (wt->event_type == TIMERAUTOSAVE)
1470 wm_autosave_timer(C, wm, wt);
1471 else if (wt->event_type == TIMERNOTIFIER)
1472 WM_main_add_notifier(GET_UINT_FROM_POINTER(wt->customdata), NULL);
1475 wm_event_init_from_window(win, &event);
1477 event.type = wt->event_type;
1478 event.val = KM_NOTHING;
1479 event.keymodifier = 0;
1480 event.custom = EVT_DATA_TIMER;
1481 event.customdata = wt;
1482 wm_event_add(win, &event);
1492 void wm_window_process_events(const bContext *C)
1496 BLI_assert(BLI_thread_is_main());
1498 hasevent = GHOST_ProcessEvents(g_system, 0); /* 0 is no wait */
1501 GHOST_DispatchEvents(g_system);
1503 hasevent |= wm_window_timer(C);
1505 /* no event, we sleep 5 milliseconds */
1510 void wm_window_process_events_nosleep(void)
1512 if (GHOST_ProcessEvents(g_system, 0))
1513 GHOST_DispatchEvents(g_system);
1516 /* exported as handle callback to bke blender.c */
1517 void wm_window_testbreak(void)
1519 static double ltime = 0;
1520 double curtime = PIL_check_seconds_timer();
1522 BLI_assert(BLI_thread_is_main());
1524 /* only check for breaks every 50 milliseconds
1525 * if we get called more often.
1527 if ((curtime - ltime) > 0.05) {
1528 int hasevent = GHOST_ProcessEvents(g_system, 0); /* 0 is no wait */
1531 GHOST_DispatchEvents(g_system);
1537 /* **************** init ********************** */
1539 void wm_ghost_init(bContext *C)
1542 GHOST_EventConsumerHandle consumer = GHOST_CreateEventConsumer(ghost_event_proc, C);
1544 g_system = GHOST_CreateSystem();
1545 GHOST_AddEventConsumer(g_system, consumer);
1547 if (wm_init_state.native_pixels) {
1548 GHOST_UseNativePixels();
1553 void wm_ghost_exit(void)
1556 GHOST_DisposeSystem(g_system);
1561 /* **************** timer ********************** */
1563 /* to (de)activate running timers temporary */
1564 void WM_event_timer_sleep(wmWindowManager *wm, wmWindow *UNUSED(win), wmTimer *timer, bool do_sleep)
1568 for (wt = wm->timers.first; wt; wt = wt->next)
1573 wt->sleep = do_sleep;
1576 wmTimer *WM_event_add_timer(wmWindowManager *wm, wmWindow *win, int event_type, double timestep)
1578 wmTimer *wt = MEM_callocN(sizeof(wmTimer), "window timer");
1580 wt->event_type = event_type;
1581 wt->ltime = PIL_check_seconds_timer();
1582 wt->ntime = wt->ltime + timestep;
1583 wt->stime = wt->ltime;
1584 wt->timestep = timestep;
1587 BLI_addtail(&wm->timers, wt);
1592 wmTimer *WM_event_add_timer_notifier(wmWindowManager *wm, wmWindow *win, unsigned int type, double timestep)
1594 wmTimer *wt = MEM_callocN(sizeof(wmTimer), "window timer");
1596 wt->event_type = TIMERNOTIFIER;
1597 wt->ltime = PIL_check_seconds_timer();
1598 wt->ntime = wt->ltime + timestep;
1599 wt->stime = wt->ltime;
1600 wt->timestep = timestep;
1602 wt->customdata = SET_UINT_IN_POINTER(type);
1604 BLI_addtail(&wm->timers, wt);
1609 void WM_event_remove_timer(wmWindowManager *wm, wmWindow *UNUSED(win), wmTimer *timer)
1613 /* extra security check */
1614 for (wt = wm->timers.first; wt; wt = wt->next)
1620 if (wm->reports.reporttimer == wt)
1621 wm->reports.reporttimer = NULL;
1623 BLI_remlink(&wm->timers, wt);
1625 MEM_freeN(wt->customdata);
1628 /* there might be events in queue with this timer as customdata */
1629 for (win = wm->windows.first; win; win = win->next) {
1631 for (event = win->queue.first; event; event = event->next) {
1632 if (event->customdata == wt) {
1633 event->customdata = NULL;
1634 event->type = EVENT_NONE; /* timer users customdata, dont want NULL == NULL */
1641 void WM_event_remove_timer_notifier(wmWindowManager *wm, wmWindow *win, wmTimer *timer)
1643 timer->customdata = NULL;
1644 WM_event_remove_timer(wm, win, timer);
1647 /* ******************* clipboard **************** */
1649 static char *wm_clipboard_text_get_ex(bool selection, int *r_len,
1652 char *p, *p2, *buf, *newbuf;
1659 buf = (char *)GHOST_getClipboard(selection);
1665 /* always convert from \r\n to \n */
1666 p2 = newbuf = MEM_mallocN(strlen(buf) + 1, __func__);
1669 /* will return an over-alloc'ed value in the case there are newlines */
1670 for (p = buf; *p; p++) {
1671 if ((*p != '\n') && (*p != '\r')) {
1680 for (p = buf; *p; p++) {
1689 free(buf); /* ghost uses regular malloc */
1691 *r_len = (p2 - newbuf);
1697 * Return text from the clipboard.
1699 * \note Caller needs to check for valid utf8 if this is a requirement.
1701 char *WM_clipboard_text_get(bool selection, int *r_len)
1703 return wm_clipboard_text_get_ex(selection, r_len, false);
1707 * Convenience function for pasting to areas of Blender which don't support newlines.
1709 char *WM_clipboard_text_get_firstline(bool selection, int *r_len)
1711 return wm_clipboard_text_get_ex(selection, r_len, true);
1714 void WM_clipboard_text_set(const char *buf, bool selection)
1716 if (!G.background) {
1718 /* do conversion from \n to \r\n on Windows */
1723 for (p = buf; *p; p++) {
1730 newbuf = MEM_callocN(newlen + 1, "WM_clipboard_text_set");
1732 for (p = buf, p2 = newbuf; *p; p++, p2++) {
1734 *(p2++) = '\r'; *p2 = '\n';
1742 GHOST_putClipboard((GHOST_TInt8 *)newbuf, selection);
1745 GHOST_putClipboard((GHOST_TInt8 *)buf, selection);
1750 /* ******************* progress bar **************** */
1752 void WM_progress_set(wmWindow *win, float progress)
1754 GHOST_SetProgressBar(win->ghostwin, progress);
1757 void WM_progress_clear(wmWindow *win)
1759 GHOST_EndProgressBar(win->ghostwin);
1762 /* ************************************ */
1764 void wm_window_get_position(wmWindow *win, int *r_pos_x, int *r_pos_y)
1766 *r_pos_x = win->posx;
1767 *r_pos_y = win->posy;
1770 void wm_window_set_size(wmWindow *win, int width, int height)
1772 GHOST_SetClientSize(win->ghostwin, width, height);
1775 void wm_window_lower(wmWindow *win)
1777 GHOST_SetWindowOrder(win->ghostwin, GHOST_kWindowOrderBottom);
1780 void wm_window_raise(wmWindow *win)
1782 GHOST_SetWindowOrder(win->ghostwin, GHOST_kWindowOrderTop);
1785 void wm_window_swap_buffers(wmWindow *win)
1789 glDisable(GL_SCISSOR_TEST);
1790 GHOST_SwapWindowBuffers(win->ghostwin);
1791 glEnable(GL_SCISSOR_TEST);
1793 GHOST_SwapWindowBuffers(win->ghostwin);
1797 void wm_window_set_swap_interval (wmWindow *win, int interval)
1799 GHOST_SetSwapInterval(win->ghostwin, interval);
1802 bool wm_window_get_swap_interval(wmWindow *win, int *intervalOut)
1804 return GHOST_GetSwapInterval(win->ghostwin, intervalOut);
1808 /* ******************* exported api ***************** */
1811 /* called whem no ghost system was initialized */
1812 void WM_init_state_size_set(int stax, int stay, int sizx, int sizy)
1814 wm_init_state.start_x = stax; /* left hand pos */
1815 wm_init_state.start_y = stay; /* bottom pos */
1816 wm_init_state.size_x = sizx < 640 ? 640 : sizx;
1817 wm_init_state.size_y = sizy < 480 ? 480 : sizy;
1818 wm_init_state.override_flag |= WIN_OVERRIDE_GEOM;
1821 /* for borderless and border windows set from command-line */
1822 void WM_init_state_fullscreen_set(void)
1824 wm_init_state.windowstate = GHOST_kWindowStateFullScreen;
1825 wm_init_state.override_flag |= WIN_OVERRIDE_WINSTATE;
1828 void WM_init_state_normal_set(void)
1830 wm_init_state.windowstate = GHOST_kWindowStateNormal;
1831 wm_init_state.override_flag |= WIN_OVERRIDE_WINSTATE;
1834 void WM_init_native_pixels(bool do_it)
1836 wm_init_state.native_pixels = do_it;
1839 /* This function requires access to the GHOST_SystemHandle (g_system) */
1840 void WM_cursor_warp(wmWindow *win, int x, int y)
1842 if (win && win->ghostwin) {
1843 int oldx = x, oldy = y;
1845 wm_cursor_position_to_ghost(win, &x, &y);
1846 GHOST_SetCursorPosition(g_system, x, y);
1848 win->eventstate->prevx = oldx;
1849 win->eventstate->prevy = oldy;
1851 win->eventstate->x = oldx;
1852 win->eventstate->y = oldy;
1857 * Set x, y to values we can actually position the cursor to.
1859 void WM_cursor_compatible_xy(wmWindow *win, int *x, int *y)
1861 float f = GHOST_GetNativePixelSize(win->ghostwin);
1863 *x = (int)(*x / f) * f;
1864 *y = (int)(*y / f) * f;
1869 * Get the cursor pressure, in most cases you'll want to use wmTabletData from the event
1871 float WM_cursor_pressure(const struct wmWindow *win)
1873 const GHOST_TabletData *td = GHOST_GetTabletData(win->ghostwin);
1874 /* if there's tablet data from an active tablet device then add it */
1875 if ((td != NULL) && td->Active != GHOST_kTabletModeNone) {
1876 return td->Pressure;
1883 /* support for native pixel size */
1884 /* mac retina opens window in size X, but it has up to 2 x more pixels */
1885 int WM_window_pixels_x(wmWindow *win)
1887 float f = GHOST_GetNativePixelSize(win->ghostwin);
1889 return (int)(f * (float)win->sizex);
1892 int WM_window_pixels_y(wmWindow *win)
1894 float f = GHOST_GetNativePixelSize(win->ghostwin);
1896 return (int)(f * (float)win->sizey);
1900 bool WM_window_is_fullscreen(wmWindow *win)
1902 return win->windowstate == GHOST_kWindowStateFullScreen;
1906 * Some editor data may need to be synced with scene data (3D View camera and layers).
1907 * This function ensures data is synced for editors in visible workspaces and their visible layouts.
1909 void WM_windows_scene_data_sync(const ListBase *win_lb, Scene *scene)
1911 for (wmWindow *win = win_lb->first; win; win = win->next) {
1912 if (WM_window_get_active_scene(win) == scene) {
1913 ED_workspace_scene_data_sync(win->workspace_hook, scene);
1918 Scene *WM_windows_scene_get_from_screen(const wmWindowManager *wm, const bScreen *screen)
1920 for (wmWindow *win = wm->windows.first; win; win = win->next) {
1921 if (WM_window_get_active_screen(win) == screen) {
1922 return WM_window_get_active_scene(win);
1929 Scene *WM_window_get_active_scene(const wmWindow *win)
1935 * \warning Only call outside of area/region loops
1937 void WM_window_change_active_scene(Main *bmain, bContext *C, wmWindow *win, Scene *scene_new)
1939 const bScreen *screen = WM_window_get_active_screen(win);
1942 win->scene = scene_new;
1943 ED_scene_changed_update(bmain, C, scene_new, screen);
1946 WorkSpace *WM_window_get_active_workspace(const wmWindow *win)
1948 return BKE_workspace_active_get(win->workspace_hook);
1950 void WM_window_set_active_workspace(wmWindow *win, WorkSpace *workspace)
1952 BKE_workspace_active_set(win->workspace_hook, workspace);
1955 WorkSpaceLayout *WM_window_get_active_layout(const wmWindow *win)
1957 const WorkSpace *workspace = WM_window_get_active_workspace(win);
1958 return (LIKELY(workspace != NULL) ? BKE_workspace_active_layout_get(win->workspace_hook): NULL);
1960 void WM_window_set_active_layout(wmWindow *win, WorkSpace *workspace, WorkSpaceLayout *layout)
1962 BKE_workspace_hook_layout_for_workspace_set(win->workspace_hook, workspace, layout);
1966 * Get the active screen of the active workspace in \a win.
1968 bScreen *WM_window_get_active_screen(const wmWindow *win)
1970 const WorkSpace *workspace = WM_window_get_active_workspace(win);
1971 /* May be NULL in rare cases like closing Blender */
1972 return (LIKELY(workspace != NULL) ? BKE_workspace_active_screen_get(win->workspace_hook) : NULL);
1974 void WM_window_set_active_screen(wmWindow *win, WorkSpace *workspace, bScreen *screen)
1976 BKE_workspace_active_screen_set(win->workspace_hook, workspace, screen);
1979 bool WM_window_is_temp_screen(const wmWindow *win)
1981 const bScreen *screen = WM_window_get_active_screen(win);
1982 return (screen && screen->temp != 0);
1986 #ifdef WITH_INPUT_IME
1987 /* note: keep in mind wm_window_IME_begin is also used to reposition the IME window */
1988 void wm_window_IME_begin(wmWindow *win, int x, int y, int w, int h, bool complete)
1992 GHOST_BeginIME(win->ghostwin, x, win->sizey - y, w, h, complete);
1995 void wm_window_IME_end(wmWindow *win)
1997 BLI_assert(win && win->ime_data);
1999 GHOST_EndIME(win->ghostwin);
2000 win->ime_data = NULL;
2002 #endif /* WITH_INPUT_IME */