This adds a vulkan backend to GHOST. The code was extracted from the tmp-vulkan branch. The main difference with the original code is that GHOST isn't responsible for fallback. For Metal backend there is already an idea that the GPU module is responsible for the fallback, not the system. For Blender we target Vulkan 1.2 at the time of this patch. MoltenVK (needed to convert Vulkan calls to Metal) has been added as a separate package. This patch isn't useful for end-users, currently when starting blender with `--gpu-backend vulkan` it would crash as the `VBBackend` doesn't initialize the expected global structs in the GPU module. Validated to be working on Windows and Apple. Linux still needs to be tested. Reviewed By: fclem Differential Revision: https://developer.blender.org/D13155
2439 lines
69 KiB
C
2439 lines
69 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later
|
|
* Copyright 2001-2002 NaN Holding BV. All rights reserved. 2007 Blender Foundation. */
|
|
|
|
/** \file
|
|
* \ingroup wm
|
|
*
|
|
* Window management, wrap GHOST.
|
|
*/
|
|
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "DNA_listBase.h"
|
|
#include "DNA_screen_types.h"
|
|
#include "DNA_windowmanager_types.h"
|
|
#include "DNA_workspace_types.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "GHOST_C-api.h"
|
|
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_math.h"
|
|
#include "BLI_system.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
#include "BKE_context.h"
|
|
#include "BKE_global.h"
|
|
#include "BKE_icons.h"
|
|
#include "BKE_layer.h"
|
|
#include "BKE_main.h"
|
|
#include "BKE_report.h"
|
|
#include "BKE_screen.h"
|
|
#include "BKE_workspace.h"
|
|
|
|
#include "RNA_access.h"
|
|
#include "RNA_define.h"
|
|
#include "RNA_enum_types.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
#include "wm.h"
|
|
#include "wm_draw.h"
|
|
#include "wm_event_system.h"
|
|
#include "wm_files.h"
|
|
#include "wm_platform_support.h"
|
|
#include "wm_window.h"
|
|
#include "wm_window_private.h"
|
|
#ifdef WITH_XR_OPENXR
|
|
# include "wm_xr.h"
|
|
#endif
|
|
|
|
#include "ED_anim_api.h"
|
|
#include "ED_fileselect.h"
|
|
#include "ED_render.h"
|
|
#include "ED_scene.h"
|
|
#include "ED_screen.h"
|
|
|
|
#include "UI_interface.h"
|
|
#include "UI_interface_icons.h"
|
|
|
|
#include "PIL_time.h"
|
|
|
|
#include "BLF_api.h"
|
|
#include "GPU_batch.h"
|
|
#include "GPU_batch_presets.h"
|
|
#include "GPU_context.h"
|
|
#include "GPU_framebuffer.h"
|
|
#include "GPU_immediate.h"
|
|
#include "GPU_init_exit.h"
|
|
#include "GPU_platform.h"
|
|
#include "GPU_state.h"
|
|
#include "GPU_texture.h"
|
|
|
|
#include "UI_resources.h"
|
|
|
|
/* for assert */
|
|
#ifndef NDEBUG
|
|
# include "BLI_threads.h"
|
|
#endif
|
|
|
|
/**
|
|
* When windows are activated, simulate modifier press/release to match the current state of
|
|
* held modifier keys, see T40317.
|
|
*/
|
|
#define USE_WIN_ACTIVATE
|
|
|
|
/* the global to talk to ghost */
|
|
static GHOST_SystemHandle g_system = NULL;
|
|
#if !(defined(WIN32) || defined(__APPLE__))
|
|
static const char *g_system_backend_id = NULL;
|
|
#endif
|
|
|
|
typedef enum eWinOverrideFlag {
|
|
WIN_OVERRIDE_GEOM = (1 << 0),
|
|
WIN_OVERRIDE_WINSTATE = (1 << 1),
|
|
} eWinOverrideFlag;
|
|
|
|
#define GHOST_WINDOW_STATE_DEFAULT GHOST_kWindowStateMaximized
|
|
|
|
/**
|
|
* Override defaults or startup file when #eWinOverrideFlag is set.
|
|
* These values are typically set by command line arguments.
|
|
*/
|
|
static struct WMInitStruct {
|
|
/* window geometry */
|
|
int size_x, size_y;
|
|
int start_x, start_y;
|
|
|
|
int windowstate;
|
|
eWinOverrideFlag override_flag;
|
|
|
|
bool window_focus;
|
|
bool native_pixels;
|
|
} wm_init_state = {
|
|
.windowstate = GHOST_WINDOW_STATE_DEFAULT,
|
|
.window_focus = true,
|
|
.native_pixels = true,
|
|
};
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Modifier Constants
|
|
* \{ */
|
|
|
|
static const struct {
|
|
uint8_t flag;
|
|
GHOST_TKey ghost_key_pair[2];
|
|
GHOST_TModifierKey ghost_mask_pair[2];
|
|
} g_modifier_table[] = {
|
|
{KM_SHIFT,
|
|
{GHOST_kKeyLeftShift, GHOST_kKeyRightShift},
|
|
{GHOST_kModifierKeyLeftShift, GHOST_kModifierKeyRightShift}},
|
|
{KM_CTRL,
|
|
{GHOST_kKeyLeftControl, GHOST_kKeyRightControl},
|
|
{GHOST_kModifierKeyLeftControl, GHOST_kModifierKeyRightControl}},
|
|
{KM_ALT,
|
|
{GHOST_kKeyLeftAlt, GHOST_kKeyRightAlt},
|
|
{GHOST_kModifierKeyLeftAlt, GHOST_kModifierKeyRightAlt}},
|
|
{KM_OSKEY,
|
|
{GHOST_kKeyLeftOS, GHOST_kKeyRightOS},
|
|
{GHOST_kModifierKeyLeftOS, GHOST_kModifierKeyRightOS}},
|
|
};
|
|
|
|
enum ModSide {
|
|
MOD_SIDE_LEFT = 0,
|
|
MOD_SIDE_RIGHT = 1,
|
|
};
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Window Open & Close
|
|
* \{ */
|
|
|
|
static void wm_window_set_drawable(wmWindowManager *wm, wmWindow *win, bool activate);
|
|
static bool wm_window_timer(const bContext *C);
|
|
|
|
void wm_get_screensize(int *r_width, int *r_height)
|
|
{
|
|
uint uiwidth;
|
|
uint uiheight;
|
|
|
|
GHOST_GetMainDisplayDimensions(g_system, &uiwidth, &uiheight);
|
|
*r_width = uiwidth;
|
|
*r_height = uiheight;
|
|
}
|
|
|
|
void wm_get_desktopsize(int *r_width, int *r_height)
|
|
{
|
|
uint uiwidth;
|
|
uint uiheight;
|
|
|
|
GHOST_GetAllDisplayDimensions(g_system, &uiwidth, &uiheight);
|
|
*r_width = uiwidth;
|
|
*r_height = uiheight;
|
|
}
|
|
|
|
/* keeps size within monitor bounds */
|
|
static void wm_window_check_size(rcti *rect)
|
|
{
|
|
int width, height;
|
|
wm_get_screensize(&width, &height);
|
|
if (BLI_rcti_size_x(rect) > width) {
|
|
BLI_rcti_resize_x(rect, width);
|
|
}
|
|
if (BLI_rcti_size_y(rect) > height) {
|
|
BLI_rcti_resize_y(rect, height);
|
|
}
|
|
}
|
|
|
|
static void wm_ghostwindow_destroy(wmWindowManager *wm, wmWindow *win)
|
|
{
|
|
if (UNLIKELY(!win->ghostwin)) {
|
|
return;
|
|
}
|
|
|
|
/* Prevents non-drawable state of main windows (bugs T22967,
|
|
* T25071 and possibly T22477 too). Always clear it even if
|
|
* this window was not the drawable one, because we mess with
|
|
* drawing context to discard the GW context. */
|
|
wm_window_clear_drawable(wm);
|
|
|
|
if (win == wm->winactive) {
|
|
wm->winactive = NULL;
|
|
}
|
|
|
|
/* We need this window's opengl context active to discard it. */
|
|
GHOST_ActivateWindowDrawingContext(win->ghostwin);
|
|
GPU_context_active_set(win->gpuctx);
|
|
|
|
/* Delete local GPU context. */
|
|
GPU_context_discard(win->gpuctx);
|
|
|
|
GHOST_DisposeWindow(g_system, win->ghostwin);
|
|
win->ghostwin = NULL;
|
|
win->gpuctx = NULL;
|
|
}
|
|
|
|
void wm_window_free(bContext *C, wmWindowManager *wm, wmWindow *win)
|
|
{
|
|
/* update context */
|
|
if (C) {
|
|
WM_event_remove_handlers(C, &win->handlers);
|
|
WM_event_remove_handlers(C, &win->modalhandlers);
|
|
|
|
if (CTX_wm_window(C) == win) {
|
|
CTX_wm_window_set(C, NULL);
|
|
}
|
|
}
|
|
|
|
BKE_screen_area_map_free(&win->global_areas);
|
|
|
|
/* end running jobs, a job end also removes its timer */
|
|
LISTBASE_FOREACH_MUTABLE (wmTimer *, wt, &wm->timers) {
|
|
if (wt->win == win && wt->event_type == TIMERJOBS) {
|
|
wm_jobs_timer_end(wm, wt);
|
|
}
|
|
}
|
|
|
|
/* timer removing, need to call this api function */
|
|
LISTBASE_FOREACH_MUTABLE (wmTimer *, wt, &wm->timers) {
|
|
if (wt->win == win) {
|
|
WM_event_remove_timer(wm, win, wt);
|
|
}
|
|
}
|
|
|
|
if (win->eventstate) {
|
|
MEM_freeN(win->eventstate);
|
|
}
|
|
if (win->event_last_handled) {
|
|
MEM_freeN(win->event_last_handled);
|
|
}
|
|
|
|
if (win->cursor_keymap_status) {
|
|
MEM_freeN(win->cursor_keymap_status);
|
|
}
|
|
|
|
WM_gestures_free_all(win);
|
|
|
|
wm_event_free_all(win);
|
|
|
|
wm_ghostwindow_destroy(wm, win);
|
|
|
|
BKE_workspace_instance_hook_free(G_MAIN, win->workspace_hook);
|
|
MEM_freeN(win->stereo3d_format);
|
|
|
|
MEM_freeN(win);
|
|
}
|
|
|
|
static int find_free_winid(wmWindowManager *wm)
|
|
{
|
|
int id = 1;
|
|
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
|
if (id <= win->winid) {
|
|
id = win->winid + 1;
|
|
}
|
|
}
|
|
return id;
|
|
}
|
|
|
|
wmWindow *wm_window_new(const Main *bmain, wmWindowManager *wm, wmWindow *parent, bool dialog)
|
|
{
|
|
wmWindow *win = MEM_callocN(sizeof(wmWindow), "window");
|
|
|
|
BLI_addtail(&wm->windows, win);
|
|
win->winid = find_free_winid(wm);
|
|
|
|
/* Dialogs may have a child window as parent. Otherwise, a child must not be a parent too. */
|
|
win->parent = (!dialog && parent && parent->parent) ? parent->parent : parent;
|
|
win->stereo3d_format = MEM_callocN(sizeof(Stereo3dFormat), "Stereo 3D Format (window)");
|
|
win->workspace_hook = BKE_workspace_instance_hook_create(bmain, win->winid);
|
|
|
|
return win;
|
|
}
|
|
|
|
wmWindow *wm_window_copy(Main *bmain,
|
|
wmWindowManager *wm,
|
|
wmWindow *win_src,
|
|
const bool duplicate_layout,
|
|
const bool child)
|
|
{
|
|
const bool is_dialog = GHOST_IsDialogWindow(win_src->ghostwin);
|
|
wmWindow *win_parent = (child) ? win_src : win_src->parent;
|
|
wmWindow *win_dst = wm_window_new(bmain, wm, win_parent, is_dialog);
|
|
WorkSpace *workspace = WM_window_get_active_workspace(win_src);
|
|
WorkSpaceLayout *layout_old = WM_window_get_active_layout(win_src);
|
|
|
|
win_dst->posx = win_src->posx + 10;
|
|
win_dst->posy = win_src->posy;
|
|
win_dst->sizex = win_src->sizex;
|
|
win_dst->sizey = win_src->sizey;
|
|
|
|
win_dst->scene = win_src->scene;
|
|
STRNCPY(win_dst->view_layer_name, win_src->view_layer_name);
|
|
BKE_workspace_active_set(win_dst->workspace_hook, workspace);
|
|
WorkSpaceLayout *layout_new = duplicate_layout ? ED_workspace_layout_duplicate(
|
|
bmain, workspace, layout_old, win_dst) :
|
|
layout_old;
|
|
BKE_workspace_active_layout_set(win_dst->workspace_hook, win_dst->winid, workspace, layout_new);
|
|
|
|
*win_dst->stereo3d_format = *win_src->stereo3d_format;
|
|
|
|
return win_dst;
|
|
}
|
|
|
|
wmWindow *wm_window_copy_test(bContext *C,
|
|
wmWindow *win_src,
|
|
const bool duplicate_layout,
|
|
const bool child)
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
|
|
wmWindow *win_dst = wm_window_copy(bmain, wm, win_src, duplicate_layout, child);
|
|
|
|
WM_check(C);
|
|
|
|
if (win_dst->ghostwin) {
|
|
WM_event_add_notifier_ex(wm, CTX_wm_window(C), NC_WINDOW | NA_ADDED, NULL);
|
|
return win_dst;
|
|
}
|
|
wm_window_close(C, wm, win_dst);
|
|
return NULL;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Quit Confirmation Dialog
|
|
* \{ */
|
|
|
|
static void wm_save_file_on_quit_dialog_callback(bContext *C, void *UNUSED(user_data))
|
|
{
|
|
wm_exit_schedule_delayed(C);
|
|
}
|
|
|
|
/**
|
|
* Call the confirm dialog on quitting. It's displayed in the context window so
|
|
* caller should set it as desired.
|
|
*/
|
|
static void wm_confirm_quit(bContext *C)
|
|
{
|
|
wmGenericCallback *action = MEM_callocN(sizeof(*action), __func__);
|
|
action->exec = wm_save_file_on_quit_dialog_callback;
|
|
wm_close_file_dialog(C, action);
|
|
}
|
|
|
|
void wm_quit_with_optional_confirmation_prompt(bContext *C, wmWindow *win)
|
|
{
|
|
wmWindow *win_ctx = CTX_wm_window(C);
|
|
|
|
/* The popup will be displayed in the context window which may not be set
|
|
* here (this function gets called outside of normal event handling loop). */
|
|
CTX_wm_window_set(C, win);
|
|
|
|
if (U.uiflag & USER_SAVE_PROMPT) {
|
|
if (wm_file_or_session_data_has_unsaved_changes(CTX_data_main(C), CTX_wm_manager(C)) &&
|
|
!G.background) {
|
|
wm_window_raise(win);
|
|
wm_confirm_quit(C);
|
|
}
|
|
else {
|
|
wm_exit_schedule_delayed(C);
|
|
}
|
|
}
|
|
else {
|
|
wm_exit_schedule_delayed(C);
|
|
}
|
|
|
|
CTX_wm_window_set(C, win_ctx);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
void wm_window_close(bContext *C, wmWindowManager *wm, wmWindow *win)
|
|
{
|
|
wmWindow *win_other;
|
|
|
|
/* First check if there is another main window remaining. */
|
|
for (win_other = wm->windows.first; win_other; win_other = win_other->next) {
|
|
if (win_other != win && win_other->parent == NULL && !WM_window_is_temp_screen(win_other)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (win->parent == NULL && win_other == NULL) {
|
|
wm_quit_with_optional_confirmation_prompt(C, win);
|
|
return;
|
|
}
|
|
|
|
/* Close child windows */
|
|
LISTBASE_FOREACH_MUTABLE (wmWindow *, iter_win, &wm->windows) {
|
|
if (iter_win->parent == win) {
|
|
wm_window_close(C, wm, iter_win);
|
|
}
|
|
}
|
|
|
|
bScreen *screen = WM_window_get_active_screen(win);
|
|
WorkSpace *workspace = WM_window_get_active_workspace(win);
|
|
WorkSpaceLayout *layout = BKE_workspace_active_layout_get(win->workspace_hook);
|
|
|
|
BLI_remlink(&wm->windows, win);
|
|
|
|
CTX_wm_window_set(C, win); /* needed by handlers */
|
|
WM_event_remove_handlers(C, &win->handlers);
|
|
WM_event_remove_handlers(C, &win->modalhandlers);
|
|
|
|
/* for regular use this will _never_ be NULL,
|
|
* however we may be freeing an improperly initialized window. */
|
|
if (screen) {
|
|
ED_screen_exit(C, win, screen);
|
|
}
|
|
|
|
wm_window_free(C, wm, win);
|
|
|
|
/* if temp screen, delete it after window free (it stops jobs that can access it) */
|
|
if (screen && screen->temp) {
|
|
Main *bmain = CTX_data_main(C);
|
|
|
|
BLI_assert(BKE_workspace_layout_screen_get(layout) == screen);
|
|
BKE_workspace_layout_remove(bmain, workspace, layout);
|
|
WM_event_add_notifier(C, NC_SCREEN | ND_LAYOUTDELETE, NULL);
|
|
}
|
|
}
|
|
|
|
void wm_window_title(wmWindowManager *wm, wmWindow *win)
|
|
{
|
|
if (WM_window_is_temp_screen(win)) {
|
|
/* Nothing to do for 'temp' windows,
|
|
* because #WM_window_open always sets window title. */
|
|
}
|
|
else if (win->ghostwin) {
|
|
/* this is set to 1 if you don't have startup.blend open */
|
|
const char *blendfile_path = BKE_main_blendfile_path_from_global();
|
|
if (blendfile_path[0] != '\0') {
|
|
char str[sizeof(((Main *)NULL)->filepath) + 24];
|
|
BLI_snprintf(str,
|
|
sizeof(str),
|
|
"Blender%s [%s%s]",
|
|
wm->file_saved ? "" : "*",
|
|
blendfile_path,
|
|
G_MAIN->recovered ? " (Recovered)" : "");
|
|
GHOST_SetTitle(win->ghostwin, str);
|
|
}
|
|
else {
|
|
GHOST_SetTitle(win->ghostwin, "Blender");
|
|
}
|
|
|
|
/* Informs GHOST of unsaved changes, to set window modified visual indicator (macOS)
|
|
* and to give hint of unsaved changes for a user warning mechanism in case of OS application
|
|
* terminate request (e.g. OS Shortcut Alt+F4, Command+Q, (...), or session end). */
|
|
GHOST_SetWindowModifiedState(win->ghostwin, (bool)!wm->file_saved);
|
|
}
|
|
}
|
|
|
|
void WM_window_set_dpi(const wmWindow *win)
|
|
{
|
|
float auto_dpi = GHOST_GetDPIHint(win->ghostwin);
|
|
|
|
/* Clamp auto DPI to 96, since our font/interface drawing does not work well
|
|
* with lower sizes. The main case we are interested in supporting is higher
|
|
* DPI. If a smaller UI is desired it is still possible to adjust UI scale. */
|
|
auto_dpi = max_ff(auto_dpi, 96.0f);
|
|
|
|
/* Lazily init UI scale size, preserving backwards compatibility by
|
|
* computing UI scale from ratio of previous DPI and auto DPI */
|
|
if (U.ui_scale == 0) {
|
|
int virtual_pixel = (U.virtual_pixel == VIRTUAL_PIXEL_NATIVE) ? 1 : 2;
|
|
|
|
if (U.dpi == 0) {
|
|
U.ui_scale = virtual_pixel;
|
|
}
|
|
else {
|
|
U.ui_scale = (virtual_pixel * U.dpi * 96.0f) / (auto_dpi * 72.0f);
|
|
}
|
|
|
|
CLAMP(U.ui_scale, 0.25f, 4.0f);
|
|
}
|
|
|
|
/* Blender's UI drawing assumes DPI 72 as a good default following macOS
|
|
* while Windows and Linux use DPI 96. GHOST assumes a default 96 so we
|
|
* remap the DPI to Blender's convention. */
|
|
auto_dpi *= GHOST_GetNativePixelSize(win->ghostwin);
|
|
U.dpi = auto_dpi * U.ui_scale * (72.0 / 96.0f);
|
|
|
|
/* Automatically set larger pixel size for high DPI. */
|
|
int pixelsize = max_ii(1, (int)(U.dpi / 64));
|
|
/* User adjustment for pixel size. */
|
|
pixelsize = max_ii(1, pixelsize + U.ui_line_width);
|
|
|
|
/* Set user preferences globals for drawing, and for forward compatibility. */
|
|
U.pixelsize = pixelsize;
|
|
U.virtual_pixel = (pixelsize == 1) ? VIRTUAL_PIXEL_NATIVE : VIRTUAL_PIXEL_DOUBLE;
|
|
U.dpi_fac = U.dpi / 72.0f;
|
|
U.inv_dpi_fac = 1.0f / U.dpi_fac;
|
|
|
|
/* Widget unit is 20 pixels at 1X scale. This consists of 18 user-scaled units plus
|
|
* left and right borders of line-width (pixelsize). */
|
|
U.widget_unit = (int)roundf(18.0f * U.dpi_fac) + (2 * pixelsize);
|
|
}
|
|
|
|
static void wm_window_update_eventstate(wmWindow *win)
|
|
{
|
|
/* Update mouse position when a window is activated. */
|
|
wm_cursor_position_get(win, &win->eventstate->xy[0], &win->eventstate->xy[1]);
|
|
}
|
|
|
|
static void wm_window_ensure_eventstate(wmWindow *win)
|
|
{
|
|
if (win->eventstate) {
|
|
return;
|
|
}
|
|
|
|
win->eventstate = MEM_callocN(sizeof(wmEvent), "window event state");
|
|
wm_window_update_eventstate(win);
|
|
}
|
|
|
|
/* belongs to below */
|
|
static void wm_window_ghostwindow_add(wmWindowManager *wm,
|
|
const char *title,
|
|
wmWindow *win,
|
|
bool is_dialog)
|
|
{
|
|
/* A new window is created when page-flip mode is required for a window. */
|
|
GHOST_GLSettings glSettings = {0};
|
|
if (win->stereo3d_format->display_mode == S3D_DISPLAY_PAGEFLIP) {
|
|
glSettings.flags |= GHOST_glStereoVisual;
|
|
}
|
|
|
|
if (G.debug & G_DEBUG_GPU) {
|
|
glSettings.flags |= GHOST_glDebugContext;
|
|
}
|
|
|
|
eGPUBackendType gpu_backend = GPU_backend_type_selection_get();
|
|
glSettings.context_type = wm_ghost_drawing_context_type(gpu_backend);
|
|
|
|
int scr_w, scr_h;
|
|
wm_get_desktopsize(&scr_w, &scr_h);
|
|
int posy = (scr_h - win->posy - win->sizey);
|
|
|
|
/* Clear drawable so we can set the new window. */
|
|
wmWindow *prev_windrawable = wm->windrawable;
|
|
wm_window_clear_drawable(wm);
|
|
|
|
GHOST_WindowHandle ghostwin = GHOST_CreateWindow(g_system,
|
|
(win->parent) ? win->parent->ghostwin : NULL,
|
|
title,
|
|
win->posx,
|
|
posy,
|
|
win->sizex,
|
|
win->sizey,
|
|
(GHOST_TWindowState)win->windowstate,
|
|
is_dialog,
|
|
glSettings);
|
|
|
|
if (ghostwin) {
|
|
win->gpuctx = GPU_context_create(ghostwin, NULL);
|
|
GPU_render_begin();
|
|
|
|
/* needed so we can detect the graphics card below */
|
|
GPU_init();
|
|
|
|
/* Set window as drawable upon creation. Note this has already been
|
|
* it has already been activated by GHOST_CreateWindow. */
|
|
wm_window_set_drawable(wm, win, false);
|
|
|
|
win->ghostwin = ghostwin;
|
|
GHOST_SetWindowUserData(ghostwin, win); /* pointer back */
|
|
|
|
wm_window_ensure_eventstate(win);
|
|
|
|
/* store actual window size in blender window */
|
|
GHOST_RectangleHandle bounds = GHOST_GetClientBounds(win->ghostwin);
|
|
|
|
/* win32: gives undefined window size when minimized */
|
|
if (GHOST_GetWindowState(win->ghostwin) != GHOST_kWindowStateMinimized) {
|
|
win->sizex = GHOST_GetWidthRectangle(bounds);
|
|
win->sizey = GHOST_GetHeightRectangle(bounds);
|
|
}
|
|
GHOST_DisposeRectangle(bounds);
|
|
|
|
#ifndef __APPLE__
|
|
/* set the state here, so minimized state comes up correct on windows */
|
|
if (wm_init_state.window_focus) {
|
|
GHOST_SetWindowState(ghostwin, (GHOST_TWindowState)win->windowstate);
|
|
}
|
|
#endif
|
|
/* until screens get drawn, make it nice gray */
|
|
GPU_clear_color(0.55f, 0.55f, 0.55f, 1.0f);
|
|
|
|
/* needed here, because it's used before it reads userdef */
|
|
WM_window_set_dpi(win);
|
|
|
|
wm_window_swap_buffers(win);
|
|
|
|
/* Clear double buffer to avoids flickering of new windows on certain drivers. (See T97600) */
|
|
GPU_clear_color(0.55f, 0.55f, 0.55f, 1.0f);
|
|
|
|
// GHOST_SetWindowState(ghostwin, GHOST_kWindowStateModified);
|
|
GPU_render_end();
|
|
}
|
|
else {
|
|
wm_window_set_drawable(wm, prev_windrawable, false);
|
|
}
|
|
}
|
|
|
|
static void wm_window_ghostwindow_ensure(wmWindowManager *wm, wmWindow *win, bool is_dialog)
|
|
{
|
|
if (win->ghostwin == NULL) {
|
|
if ((win->sizex == 0) || (wm_init_state.override_flag & WIN_OVERRIDE_GEOM)) {
|
|
win->posx = wm_init_state.start_x;
|
|
win->posy = wm_init_state.start_y;
|
|
win->sizex = wm_init_state.size_x;
|
|
win->sizey = wm_init_state.size_y;
|
|
|
|
if (wm_init_state.override_flag & WIN_OVERRIDE_GEOM) {
|
|
win->windowstate = GHOST_kWindowStateNormal;
|
|
wm_init_state.override_flag &= ~WIN_OVERRIDE_GEOM;
|
|
}
|
|
else {
|
|
win->windowstate = GHOST_WINDOW_STATE_DEFAULT;
|
|
}
|
|
}
|
|
|
|
if (wm_init_state.override_flag & WIN_OVERRIDE_WINSTATE) {
|
|
win->windowstate = wm_init_state.windowstate;
|
|
wm_init_state.override_flag &= ~WIN_OVERRIDE_WINSTATE;
|
|
}
|
|
|
|
/* without this, cursor restore may fail, T45456 */
|
|
if (win->cursor == 0) {
|
|
win->cursor = WM_CURSOR_DEFAULT;
|
|
}
|
|
|
|
wm_window_ghostwindow_add(wm, "Blender", win, is_dialog);
|
|
}
|
|
|
|
if (win->ghostwin != NULL) {
|
|
/* If we have no `ghostwin` this is a buggy window that should be removed.
|
|
* However we still need to initialize it correctly so the screen doesn't hang. */
|
|
|
|
/* Happens after file-read. */
|
|
wm_window_ensure_eventstate(win);
|
|
|
|
WM_window_set_dpi(win);
|
|
}
|
|
|
|
/* add keymap handlers (1 handler for all keys in map!) */
|
|
wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "Window", 0, 0);
|
|
WM_event_add_keymap_handler(&win->handlers, keymap);
|
|
|
|
keymap = WM_keymap_ensure(wm->defaultconf, "Screen", 0, 0);
|
|
WM_event_add_keymap_handler(&win->handlers, keymap);
|
|
|
|
keymap = WM_keymap_ensure(wm->defaultconf, "Screen Editing", 0, 0);
|
|
WM_event_add_keymap_handler(&win->modalhandlers, keymap);
|
|
|
|
/* add drop boxes */
|
|
{
|
|
ListBase *lb = WM_dropboxmap_find("Window", 0, 0);
|
|
WM_event_add_dropbox_handler(&win->handlers, lb);
|
|
}
|
|
wm_window_title(wm, win);
|
|
|
|
/* add topbar */
|
|
ED_screen_global_areas_refresh(win);
|
|
}
|
|
|
|
void wm_window_ghostwindows_ensure(wmWindowManager *wm)
|
|
{
|
|
BLI_assert(G.background == false);
|
|
|
|
/* No command-line prefsize? then we set this.
|
|
* Note that these values will be used only
|
|
* when there is no startup.blend yet.
|
|
*/
|
|
if (wm_init_state.size_x == 0) {
|
|
wm_get_screensize(&wm_init_state.size_x, &wm_init_state.size_y);
|
|
|
|
/* NOTE: this isn't quite correct, active screen maybe offset 1000s if PX,
|
|
* we'd need a #wm_get_screensize like function that gives offset,
|
|
* in practice the window manager will likely move to the correct monitor */
|
|
wm_init_state.start_x = 0;
|
|
wm_init_state.start_y = 0;
|
|
}
|
|
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
|
wm_window_ghostwindow_ensure(wm, win, false);
|
|
}
|
|
}
|
|
|
|
void wm_window_ghostwindows_remove_invalid(bContext *C, wmWindowManager *wm)
|
|
{
|
|
BLI_assert(G.background == false);
|
|
|
|
LISTBASE_FOREACH_MUTABLE (wmWindow *, win, &wm->windows) {
|
|
if (win->ghostwin == NULL) {
|
|
wm_window_close(C, wm, win);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Update window size and position based on data from GHOST window. */
|
|
static bool wm_window_update_size_position(wmWindow *win)
|
|
{
|
|
GHOST_RectangleHandle client_rect = GHOST_GetClientBounds(win->ghostwin);
|
|
int l, t, r, b;
|
|
GHOST_GetRectangle(client_rect, &l, &t, &r, &b);
|
|
|
|
GHOST_DisposeRectangle(client_rect);
|
|
|
|
int scr_w, scr_h;
|
|
wm_get_desktopsize(&scr_w, &scr_h);
|
|
int sizex = r - l;
|
|
int sizey = b - t;
|
|
int posx = l;
|
|
int posy = scr_h - t - win->sizey;
|
|
|
|
if (win->sizex != sizex || win->sizey != sizey || win->posx != posx || win->posy != posy) {
|
|
win->sizex = sizex;
|
|
win->sizey = sizey;
|
|
win->posx = posx;
|
|
win->posy = posy;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
wmWindow *WM_window_open(bContext *C,
|
|
const char *title,
|
|
int x,
|
|
int y,
|
|
int sizex,
|
|
int sizey,
|
|
int space_type,
|
|
bool toplevel,
|
|
bool dialog,
|
|
bool temp,
|
|
eWindowAlignment alignment)
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
wmWindow *win_prev = CTX_wm_window(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
ViewLayer *view_layer = CTX_data_view_layer(C);
|
|
rcti rect;
|
|
|
|
const float native_pixel_size = GHOST_GetNativePixelSize(win_prev->ghostwin);
|
|
/* convert to native OS window coordinates */
|
|
rect.xmin = win_prev->posx + (x / native_pixel_size);
|
|
rect.ymin = win_prev->posy + (y / native_pixel_size);
|
|
sizex /= native_pixel_size;
|
|
sizey /= native_pixel_size;
|
|
|
|
if (alignment == WIN_ALIGN_LOCATION_CENTER) {
|
|
/* Window centered around x,y location. */
|
|
rect.xmin -= sizex / 2;
|
|
rect.ymin -= sizey / 2;
|
|
}
|
|
else if (alignment == WIN_ALIGN_PARENT_CENTER) {
|
|
/* Centered within parent. X,Y as offsets from there. */
|
|
rect.xmin += (win_prev->sizex - sizex) / 2;
|
|
rect.ymin += (win_prev->sizey - sizey) / 2;
|
|
}
|
|
else {
|
|
/* Positioned absolutely within parent bounds. */
|
|
}
|
|
|
|
rect.xmax = rect.xmin + sizex;
|
|
rect.ymax = rect.ymin + sizey;
|
|
|
|
/* changes rect to fit within desktop */
|
|
wm_window_check_size(&rect);
|
|
|
|
/* Reuse temporary windows when they share the same single area. */
|
|
wmWindow *win = NULL;
|
|
if (temp) {
|
|
LISTBASE_FOREACH (wmWindow *, win_iter, &wm->windows) {
|
|
const bScreen *screen = WM_window_get_active_screen(win_iter);
|
|
if (screen && screen->temp && BLI_listbase_is_single(&screen->areabase)) {
|
|
ScrArea *area = screen->areabase.first;
|
|
if (space_type == (area->butspacetype ? area->butspacetype : area->spacetype)) {
|
|
win = win_iter;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* add new window? */
|
|
if (win == NULL) {
|
|
win = wm_window_new(bmain, wm, toplevel ? NULL : win_prev, dialog);
|
|
win->posx = rect.xmin;
|
|
win->posy = rect.ymin;
|
|
win->sizex = BLI_rcti_size_x(&rect);
|
|
win->sizey = BLI_rcti_size_y(&rect);
|
|
*win->stereo3d_format = *win_prev->stereo3d_format;
|
|
}
|
|
|
|
bScreen *screen = WM_window_get_active_screen(win);
|
|
|
|
if (WM_window_get_active_workspace(win) == NULL) {
|
|
WorkSpace *workspace = WM_window_get_active_workspace(win_prev);
|
|
BKE_workspace_active_set(win->workspace_hook, workspace);
|
|
}
|
|
|
|
if (screen == NULL) {
|
|
/* add new screen layout */
|
|
WorkSpace *workspace = WM_window_get_active_workspace(win);
|
|
WorkSpaceLayout *layout = ED_workspace_layout_add(bmain, workspace, win, "temp");
|
|
|
|
screen = BKE_workspace_layout_screen_get(layout);
|
|
WM_window_set_active_layout(win, workspace, layout);
|
|
}
|
|
|
|
/* Set scene and view layer to match original window. */
|
|
STRNCPY(win->view_layer_name, view_layer->name);
|
|
if (WM_window_get_active_scene(win) != scene) {
|
|
/* No need to refresh the tool-system as the window has not yet finished being setup. */
|
|
ED_screen_scene_change(C, win, scene, false);
|
|
}
|
|
|
|
screen->temp = temp;
|
|
|
|
/* make window active, and validate/resize */
|
|
CTX_wm_window_set(C, win);
|
|
const bool new_window = (win->ghostwin == NULL);
|
|
if (new_window) {
|
|
wm_window_ghostwindow_ensure(wm, win, dialog);
|
|
}
|
|
WM_check(C);
|
|
|
|
/* It's possible `win->ghostwin == NULL`.
|
|
* instead of attempting to cleanup here (in a half finished state),
|
|
* finish setting up the screen, then free it at the end of the function,
|
|
* to avoid having to take into account a partially-created window.
|
|
*/
|
|
|
|
/* ensure it shows the right spacetype editor */
|
|
if (space_type != SPACE_EMPTY) {
|
|
ScrArea *area = screen->areabase.first;
|
|
CTX_wm_area_set(C, area);
|
|
ED_area_newspace(C, area, space_type, false);
|
|
}
|
|
|
|
ED_screen_change(C, screen);
|
|
|
|
if (!new_window) {
|
|
/* Set size in GHOST window and then update size and position from GHOST,
|
|
* in case they where changed by GHOST to fit the monitor/screen. */
|
|
wm_window_set_size(win, win->sizex, win->sizey);
|
|
wm_window_update_size_position(win);
|
|
}
|
|
|
|
/* Refresh screen dimensions, after the effective window size is known. */
|
|
ED_screen_refresh(wm, win);
|
|
|
|
if (win->ghostwin) {
|
|
wm_window_raise(win);
|
|
GHOST_SetTitle(win->ghostwin, title);
|
|
return win;
|
|
}
|
|
|
|
/* very unlikely! but opening a new window can fail */
|
|
wm_window_close(C, wm, win);
|
|
CTX_wm_window_set(C, win_prev);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* ****************** Operators ****************** */
|
|
|
|
int wm_window_close_exec(bContext *C, wmOperator *UNUSED(op))
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
wmWindow *win = CTX_wm_window(C);
|
|
wm_window_close(C, wm, win);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
int wm_window_new_exec(bContext *C, wmOperator *op)
|
|
{
|
|
wmWindow *win_src = CTX_wm_window(C);
|
|
ScrArea *area = BKE_screen_find_big_area(CTX_wm_screen(C), SPACE_TYPE_ANY, 0);
|
|
|
|
bool ok = (WM_window_open(C,
|
|
IFACE_("Blender"),
|
|
0,
|
|
0,
|
|
win_src->sizex * 0.95f,
|
|
win_src->sizey * 0.9f,
|
|
area->spacetype,
|
|
false,
|
|
false,
|
|
false,
|
|
WIN_ALIGN_PARENT_CENTER) != NULL);
|
|
|
|
if (!ok) {
|
|
BKE_report(op->reports, RPT_ERROR, "Failed to create window");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
int wm_window_new_main_exec(bContext *C, wmOperator *op)
|
|
{
|
|
wmWindow *win_src = CTX_wm_window(C);
|
|
|
|
bool ok = (wm_window_copy_test(C, win_src, true, false) != NULL);
|
|
if (!ok) {
|
|
BKE_report(op->reports, RPT_ERROR, "Failed to create window");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
int wm_window_fullscreen_toggle_exec(bContext *C, wmOperator *UNUSED(op))
|
|
{
|
|
wmWindow *window = CTX_wm_window(C);
|
|
|
|
if (G.background) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
GHOST_TWindowState state = GHOST_GetWindowState(window->ghostwin);
|
|
if (state != GHOST_kWindowStateFullScreen) {
|
|
GHOST_SetWindowState(window->ghostwin, GHOST_kWindowStateFullScreen);
|
|
}
|
|
else {
|
|
GHOST_SetWindowState(window->ghostwin, GHOST_kWindowStateNormal);
|
|
}
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
/* ************ events *************** */
|
|
|
|
void wm_cursor_position_from_ghost_client_coords(wmWindow *win, int *x, int *y)
|
|
{
|
|
float fac = GHOST_GetNativePixelSize(win->ghostwin);
|
|
*x *= fac;
|
|
|
|
*y = (win->sizey - 1) - *y;
|
|
*y *= fac;
|
|
}
|
|
|
|
void wm_cursor_position_to_ghost_client_coords(wmWindow *win, int *x, int *y)
|
|
{
|
|
float fac = GHOST_GetNativePixelSize(win->ghostwin);
|
|
|
|
*x /= fac;
|
|
*y /= fac;
|
|
*y = win->sizey - *y - 1;
|
|
}
|
|
|
|
void wm_cursor_position_from_ghost_screen_coords(wmWindow *win, int *x, int *y)
|
|
{
|
|
GHOST_ScreenToClient(win->ghostwin, *x, *y, x, y);
|
|
wm_cursor_position_from_ghost_client_coords(win, x, y);
|
|
}
|
|
|
|
void wm_cursor_position_to_ghost_screen_coords(wmWindow *win, int *x, int *y)
|
|
{
|
|
wm_cursor_position_to_ghost_client_coords(win, x, y);
|
|
GHOST_ClientToScreen(win->ghostwin, *x, *y, x, y);
|
|
}
|
|
|
|
void wm_cursor_position_get(wmWindow *win, int *r_x, int *r_y)
|
|
{
|
|
if (UNLIKELY(G.f & G_FLAG_EVENT_SIMULATE)) {
|
|
*r_x = win->eventstate->xy[0];
|
|
*r_y = win->eventstate->xy[1];
|
|
return;
|
|
}
|
|
GHOST_GetCursorPosition(g_system, win->ghostwin, r_x, r_y);
|
|
wm_cursor_position_from_ghost_client_coords(win, r_x, r_y);
|
|
}
|
|
|
|
/** Check if specified modifier key type is pressed. */
|
|
static uint8_t wm_ghost_modifier_query(const enum ModSide side)
|
|
{
|
|
uint8_t result = 0;
|
|
for (int i = 0; i < ARRAY_SIZE(g_modifier_table); i++) {
|
|
bool val = false;
|
|
GHOST_GetModifierKeyState(g_system, g_modifier_table[i].ghost_mask_pair[side], &val);
|
|
if (val) {
|
|
result |= g_modifier_table[i].flag;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void wm_window_set_drawable(wmWindowManager *wm, wmWindow *win, bool activate)
|
|
{
|
|
BLI_assert(ELEM(wm->windrawable, NULL, win));
|
|
|
|
wm->windrawable = win;
|
|
if (activate) {
|
|
GHOST_ActivateWindowDrawingContext(win->ghostwin);
|
|
}
|
|
GPU_context_active_set(win->gpuctx);
|
|
}
|
|
|
|
void wm_window_clear_drawable(wmWindowManager *wm)
|
|
{
|
|
if (wm->windrawable) {
|
|
wm->windrawable = NULL;
|
|
}
|
|
}
|
|
|
|
void wm_window_make_drawable(wmWindowManager *wm, wmWindow *win)
|
|
{
|
|
BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get());
|
|
|
|
if (win != wm->windrawable && win->ghostwin) {
|
|
// win->lmbut = 0; /* Keeps hanging when mouse-pressed while other window opened. */
|
|
wm_window_clear_drawable(wm);
|
|
|
|
if (G.debug & G_DEBUG_EVENTS) {
|
|
printf("%s: set drawable %d\n", __func__, win->winid);
|
|
}
|
|
|
|
wm_window_set_drawable(wm, win, true);
|
|
}
|
|
|
|
if (win->ghostwin) {
|
|
/* this can change per window */
|
|
WM_window_set_dpi(win);
|
|
}
|
|
}
|
|
|
|
void wm_window_reset_drawable(void)
|
|
{
|
|
BLI_assert(BLI_thread_is_main());
|
|
BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get());
|
|
wmWindowManager *wm = G_MAIN->wm.first;
|
|
|
|
if (wm == NULL) {
|
|
return;
|
|
}
|
|
wmWindow *win = wm->windrawable;
|
|
|
|
if (win && win->ghostwin) {
|
|
wm_window_clear_drawable(wm);
|
|
wm_window_set_drawable(wm, win, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by ghost, here we handle events for windows themselves or send to event system.
|
|
*
|
|
* Mouse coordinate conversion happens here.
|
|
*/
|
|
static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr)
|
|
{
|
|
bContext *C = C_void_ptr;
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
GHOST_TEventType type = GHOST_GetEventType(evt);
|
|
#if 0
|
|
/* We may want to use time from ghost, currently `PIL_check_seconds_timer` is used instead. */
|
|
uint64_t time = GHOST_GetEventTime(evt);
|
|
#endif
|
|
|
|
if (type == GHOST_kEventQuitRequest) {
|
|
/* Find an active window to display quit dialog in. */
|
|
GHOST_WindowHandle ghostwin = GHOST_GetEventWindow(evt);
|
|
|
|
wmWindow *win;
|
|
if (ghostwin && GHOST_ValidWindow(g_system, ghostwin)) {
|
|
win = GHOST_GetWindowUserData(ghostwin);
|
|
}
|
|
else {
|
|
win = wm->winactive;
|
|
}
|
|
|
|
/* Display quit dialog or quit immediately. */
|
|
if (win) {
|
|
wm_quit_with_optional_confirmation_prompt(C, win);
|
|
}
|
|
else {
|
|
wm_exit_schedule_delayed(C);
|
|
}
|
|
}
|
|
else {
|
|
GHOST_WindowHandle ghostwin = GHOST_GetEventWindow(evt);
|
|
GHOST_TEventDataPtr data = GHOST_GetEventData(evt);
|
|
|
|
/* Ghost now can call this function for life resizes,
|
|
* but it should return if WM didn't initialize yet.
|
|
* Can happen on file read (especially full size window). */
|
|
if ((wm->initialized & WM_WINDOW_IS_INIT) == 0) {
|
|
return true;
|
|
}
|
|
if (!ghostwin) {
|
|
/* XXX: should be checked, why are we getting an event here, and what is it? */
|
|
puts("<!> event has no window");
|
|
return true;
|
|
}
|
|
if (!GHOST_ValidWindow(g_system, ghostwin)) {
|
|
/* XXX: should be checked, why are we getting an event here, and what is it? */
|
|
puts("<!> event has invalid window");
|
|
return true;
|
|
}
|
|
wmWindow *win = GHOST_GetWindowUserData(ghostwin);
|
|
|
|
switch (type) {
|
|
case GHOST_kEventWindowDeactivate:
|
|
wm_event_add_ghostevent(wm, win, type, data);
|
|
win->active = 0; /* XXX */
|
|
break;
|
|
case GHOST_kEventWindowActivate: {
|
|
|
|
/* No context change! C->wm->windrawable is drawable, or for area queues. */
|
|
wm->winactive = win;
|
|
|
|
win->active = 1;
|
|
|
|
/* bad ghost support for modifier keys... so on activate we set the modifiers again */
|
|
|
|
const uint8_t keymodifier_sided[2] = {
|
|
wm_ghost_modifier_query(MOD_SIDE_LEFT),
|
|
wm_ghost_modifier_query(MOD_SIDE_RIGHT),
|
|
};
|
|
const uint8_t keymodifier = keymodifier_sided[0] | keymodifier_sided[1];
|
|
const uint8_t keymodifier_eventstate = win->eventstate->modifier;
|
|
if (keymodifier != keymodifier_eventstate) {
|
|
GHOST_TEventKeyData kdata = {
|
|
.key = GHOST_kKeyUnknown,
|
|
.utf8_buf = {'\0'},
|
|
.is_repeat = false,
|
|
};
|
|
for (int i = 0; i < ARRAY_SIZE(g_modifier_table); i++) {
|
|
if (keymodifier_eventstate & g_modifier_table[i].flag) {
|
|
if ((keymodifier & g_modifier_table[i].flag) == 0) {
|
|
for (int side = 0; side < 2; side++) {
|
|
if ((keymodifier_sided[side] & g_modifier_table[i].flag) == 0) {
|
|
kdata.key = g_modifier_table[i].ghost_key_pair[side];
|
|
wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata);
|
|
/* Only ever send one release event
|
|
* (currently releasing multiple isn't needed and only confuses logic). */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#ifdef USE_WIN_ACTIVATE
|
|
else {
|
|
if (keymodifier & g_modifier_table[i].flag) {
|
|
for (int side = 0; side < 2; side++) {
|
|
if (keymodifier_sided[side] & g_modifier_table[i].flag) {
|
|
kdata.key = g_modifier_table[i].ghost_key_pair[side];
|
|
wm_event_add_ghostevent(wm, win, GHOST_kEventKeyDown, &kdata);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#undef USE_WIN_ACTIVATE
|
|
}
|
|
}
|
|
|
|
/* keymodifier zero, it hangs on hotkeys that open windows otherwise */
|
|
win->eventstate->keymodifier = 0;
|
|
|
|
/* entering window, update mouse pos. but no event */
|
|
wm_window_update_eventstate(win);
|
|
|
|
win->addmousemove = 1; /* enables highlighted buttons */
|
|
|
|
wm_window_make_drawable(wm, win);
|
|
|
|
/* window might be focused by mouse click in configuration of window manager
|
|
* when focus is not following mouse
|
|
* click could have been done on a button and depending on window manager settings
|
|
* click would be passed to blender or not, but in any case button under cursor
|
|
* should be activated, so at max next click on button without moving mouse
|
|
* would trigger its handle function
|
|
* currently it seems to be common practice to generate new event for, but probably
|
|
* we'll need utility function for this? (sergey)
|
|
*/
|
|
wmEvent event;
|
|
wm_event_init_from_window(win, &event);
|
|
event.type = MOUSEMOVE;
|
|
event.val = KM_NOTHING;
|
|
copy_v2_v2_int(event.prev_xy, event.xy);
|
|
event.flag = 0;
|
|
|
|
wm_event_add(win, &event);
|
|
|
|
break;
|
|
}
|
|
case GHOST_kEventWindowClose: {
|
|
wm_window_close(C, wm, win);
|
|
break;
|
|
}
|
|
case GHOST_kEventWindowUpdate: {
|
|
if (G.debug & G_DEBUG_EVENTS) {
|
|
printf("%s: ghost redraw %d\n", __func__, win->winid);
|
|
}
|
|
|
|
wm_window_make_drawable(wm, win);
|
|
WM_event_add_notifier(C, NC_WINDOW, NULL);
|
|
|
|
break;
|
|
}
|
|
case GHOST_kEventWindowSize:
|
|
case GHOST_kEventWindowMove: {
|
|
GHOST_TWindowState state = GHOST_GetWindowState(win->ghostwin);
|
|
win->windowstate = state;
|
|
|
|
WM_window_set_dpi(win);
|
|
|
|
/* win32: gives undefined window size when minimized */
|
|
if (state != GHOST_kWindowStateMinimized) {
|
|
/*
|
|
* Ghost sometimes send size or move events when the window hasn't changed.
|
|
* One case of this is using compiz on linux. To alleviate the problem
|
|
* we ignore all such event here.
|
|
*
|
|
* It might be good to eventually do that at Ghost level, but that is for
|
|
* another time.
|
|
*/
|
|
if (wm_window_update_size_position(win)) {
|
|
const bScreen *screen = WM_window_get_active_screen(win);
|
|
|
|
/* debug prints */
|
|
if (G.debug & G_DEBUG_EVENTS) {
|
|
const char *state_str;
|
|
state = GHOST_GetWindowState(win->ghostwin);
|
|
|
|
if (state == GHOST_kWindowStateNormal) {
|
|
state_str = "normal";
|
|
}
|
|
else if (state == GHOST_kWindowStateMinimized) {
|
|
state_str = "minimized";
|
|
}
|
|
else if (state == GHOST_kWindowStateMaximized) {
|
|
state_str = "maximized";
|
|
}
|
|
else if (state == GHOST_kWindowStateFullScreen) {
|
|
state_str = "full-screen";
|
|
}
|
|
else {
|
|
state_str = "<unknown>";
|
|
}
|
|
|
|
printf("%s: window %d state = %s\n", __func__, win->winid, state_str);
|
|
|
|
if (type != GHOST_kEventWindowSize) {
|
|
printf("win move event pos %d %d size %d %d\n",
|
|
win->posx,
|
|
win->posy,
|
|
win->sizex,
|
|
win->sizey);
|
|
}
|
|
}
|
|
|
|
wm_window_make_drawable(wm, win);
|
|
BKE_icon_changed(screen->id.icon_id);
|
|
WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
|
|
WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, NULL);
|
|
|
|
#if defined(__APPLE__) || defined(WIN32)
|
|
/* OSX and Win32 don't return to the mainloop while resize */
|
|
wm_window_timer(C);
|
|
wm_event_do_handlers(C);
|
|
wm_event_do_notifiers(C);
|
|
wm_draw_update(C);
|
|
#endif
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case GHOST_kEventWindowDPIHintChanged: {
|
|
WM_window_set_dpi(win);
|
|
/* font's are stored at each DPI level, without this we can easy load 100's of fonts */
|
|
BLF_cache_clear();
|
|
|
|
WM_main_add_notifier(NC_WINDOW, NULL); /* full redraw */
|
|
WM_main_add_notifier(NC_SCREEN | NA_EDITED, NULL); /* refresh region sizes */
|
|
break;
|
|
}
|
|
|
|
case GHOST_kEventOpenMainFile: {
|
|
const char *path = GHOST_GetEventData(evt);
|
|
|
|
if (path) {
|
|
wmOperatorType *ot = WM_operatortype_find("WM_OT_open_mainfile", false);
|
|
/* operator needs a valid window in context, ensures
|
|
* it is correctly set */
|
|
CTX_wm_window_set(C, win);
|
|
|
|
PointerRNA props_ptr;
|
|
WM_operator_properties_create_ptr(&props_ptr, ot);
|
|
RNA_string_set(&props_ptr, "filepath", path);
|
|
RNA_boolean_set(&props_ptr, "display_file_selector", false);
|
|
WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr, NULL);
|
|
WM_operator_properties_free(&props_ptr);
|
|
|
|
CTX_wm_window_set(C, NULL);
|
|
}
|
|
break;
|
|
}
|
|
case GHOST_kEventDraggingDropDone: {
|
|
GHOST_TEventDragnDropData *ddd = GHOST_GetEventData(evt);
|
|
|
|
/* entering window, update mouse pos */
|
|
wm_window_update_eventstate(win);
|
|
|
|
wmEvent event;
|
|
wm_event_init_from_window(win, &event); /* copy last state, like mouse coords */
|
|
|
|
/* activate region */
|
|
event.type = MOUSEMOVE;
|
|
event.val = KM_NOTHING;
|
|
copy_v2_v2_int(event.prev_xy, event.xy);
|
|
|
|
wm_cursor_position_from_ghost_screen_coords(win, &ddd->x, &ddd->y);
|
|
event.xy[0] = ddd->x;
|
|
event.xy[1] = ddd->y;
|
|
|
|
/* The values from #wm_window_update_eventstate may not match (under WAYLAND they don't)
|
|
* Write this into the event state. */
|
|
copy_v2_v2_int(win->eventstate->xy, event.xy);
|
|
|
|
event.flag = 0;
|
|
|
|
/* No context change! C->wm->windrawable is drawable, or for area queues. */
|
|
wm->winactive = win;
|
|
|
|
win->active = 1;
|
|
|
|
wm_event_add(win, &event);
|
|
|
|
/* make blender drop event with custom data pointing to wm drags */
|
|
event.type = EVT_DROP;
|
|
event.val = KM_RELEASE;
|
|
event.custom = EVT_DATA_DRAGDROP;
|
|
event.customdata = &wm->drags;
|
|
event.customdata_free = true;
|
|
|
|
wm_event_add(win, &event);
|
|
|
|
// printf("Drop detected\n");
|
|
|
|
/* add drag data to wm for paths: */
|
|
|
|
if (ddd->dataType == GHOST_kDragnDropTypeFilenames) {
|
|
GHOST_TStringArray *stra = ddd->data;
|
|
|
|
for (int a = 0; a < stra->count; a++) {
|
|
printf("drop file %s\n", stra->strings[a]);
|
|
/* try to get icon type from extension */
|
|
int icon = ED_file_extension_icon((char *)stra->strings[a]);
|
|
|
|
WM_event_start_drag(C, icon, WM_DRAG_PATH, stra->strings[a], 0.0, WM_DRAG_NOP);
|
|
/* void poin should point to string, it makes a copy */
|
|
break; /* only one drop element supported now */
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case GHOST_kEventNativeResolutionChange: {
|
|
/* Only update if the actual pixel size changes. */
|
|
float prev_pixelsize = U.pixelsize;
|
|
WM_window_set_dpi(win);
|
|
|
|
if (U.pixelsize != prev_pixelsize) {
|
|
BKE_icon_changed(WM_window_get_active_screen(win)->id.icon_id);
|
|
|
|
/* Close all popups since they are positioned with the pixel
|
|
* size baked in and it's difficult to correct them. */
|
|
CTX_wm_window_set(C, win);
|
|
UI_popup_handlers_remove_all(C, &win->modalhandlers);
|
|
CTX_wm_window_set(C, NULL);
|
|
|
|
wm_window_make_drawable(wm, win);
|
|
|
|
WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
|
|
WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, NULL);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case GHOST_kEventTrackpad: {
|
|
GHOST_TEventTrackpadData *pd = data;
|
|
|
|
wm_cursor_position_from_ghost_screen_coords(win, &pd->x, &pd->y);
|
|
wm_event_add_ghostevent(wm, win, type, data);
|
|
break;
|
|
}
|
|
case GHOST_kEventCursorMove: {
|
|
GHOST_TEventCursorData *cd = data;
|
|
|
|
wm_cursor_position_from_ghost_screen_coords(win, &cd->x, &cd->y);
|
|
wm_event_add_ghostevent(wm, win, type, data);
|
|
break;
|
|
}
|
|
case GHOST_kEventButtonDown:
|
|
case GHOST_kEventButtonUp: {
|
|
if (win->active == 0) {
|
|
/* Entering window, update cursor and tablet state.
|
|
* (ghost sends win-activate *after* the mouse-click in window!) */
|
|
wm_window_update_eventstate(win);
|
|
}
|
|
|
|
wm_event_add_ghostevent(wm, win, type, data);
|
|
break;
|
|
}
|
|
default: {
|
|
wm_event_add_ghostevent(wm, win, type, data);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* This timer system only gives maximum 1 timer event per redraw cycle,
|
|
* to prevent queues to get overloaded.
|
|
* Timer handlers should check for delta to decide if they just update, or follow real time.
|
|
* Timer handlers can also set duration to match frames passed
|
|
*/
|
|
static bool wm_window_timer(const bContext *C)
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
double time = PIL_check_seconds_timer();
|
|
bool has_event = false;
|
|
|
|
/* Mutable in case the timer gets removed. */
|
|
LISTBASE_FOREACH_MUTABLE (wmTimer *, wt, &wm->timers) {
|
|
wmWindow *win = wt->win;
|
|
|
|
if (wt->sleep != 0) {
|
|
continue;
|
|
}
|
|
|
|
if (time > wt->ntime) {
|
|
wt->delta = time - wt->ltime;
|
|
wt->duration += wt->delta;
|
|
wt->ltime = time;
|
|
|
|
wt->ntime = wt->stime;
|
|
if (wt->timestep != 0.0f) {
|
|
wt->ntime += wt->timestep * ceil(wt->duration / wt->timestep);
|
|
}
|
|
|
|
if (wt->event_type == TIMERJOBS) {
|
|
wm_jobs_timer(wm, wt);
|
|
}
|
|
else if (wt->event_type == TIMERAUTOSAVE) {
|
|
wm_autosave_timer(bmain, wm, wt);
|
|
}
|
|
else if (wt->event_type == TIMERNOTIFIER) {
|
|
WM_main_add_notifier(POINTER_AS_UINT(wt->customdata), NULL);
|
|
}
|
|
else if (win) {
|
|
wmEvent event;
|
|
wm_event_init_from_window(win, &event);
|
|
|
|
event.type = wt->event_type;
|
|
event.val = KM_NOTHING;
|
|
event.keymodifier = 0;
|
|
event.flag = 0;
|
|
event.custom = EVT_DATA_TIMER;
|
|
event.customdata = wt;
|
|
wm_event_add(win, &event);
|
|
|
|
has_event = true;
|
|
}
|
|
}
|
|
}
|
|
return has_event;
|
|
}
|
|
|
|
void wm_window_process_events(const bContext *C)
|
|
{
|
|
BLI_assert(BLI_thread_is_main());
|
|
GPU_render_begin();
|
|
|
|
bool has_event = GHOST_ProcessEvents(g_system, false); /* `false` is no wait. */
|
|
|
|
if (has_event) {
|
|
GHOST_DispatchEvents(g_system);
|
|
}
|
|
has_event |= wm_window_timer(C);
|
|
#ifdef WITH_XR_OPENXR
|
|
/* XR events don't use the regular window queues. So here we don't only trigger
|
|
* processing/dispatching but also handling. */
|
|
has_event |= wm_xr_events_handle(CTX_wm_manager(C));
|
|
#endif
|
|
GPU_render_end();
|
|
|
|
/* When there is no event, sleep 5 milliseconds not to use too much CPU when idle.
|
|
*
|
|
* Skip sleeping when simulating events so tests don't idle unnecessarily as simulated
|
|
* events are typically generated from a timer that runs in the main loop. */
|
|
if ((has_event == false) && !(G.f & G_FLAG_EVENT_SIMULATE)) {
|
|
PIL_sleep_ms(5);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Ghost Init/Exit
|
|
* \{ */
|
|
|
|
void wm_ghost_init(bContext *C)
|
|
{
|
|
if (g_system) {
|
|
return;
|
|
}
|
|
|
|
BLI_assert(C != NULL);
|
|
BLI_assert_msg(!G.background, "Use wm_ghost_init_background instead");
|
|
|
|
GHOST_EventConsumerHandle consumer;
|
|
|
|
consumer = GHOST_CreateEventConsumer(ghost_event_proc, C);
|
|
|
|
GHOST_SetBacktraceHandler((GHOST_TBacktraceFn)BLI_system_backtrace);
|
|
|
|
g_system = GHOST_CreateSystem();
|
|
|
|
if (UNLIKELY(g_system == NULL)) {
|
|
/* GHOST will have reported the back-ends that failed to load. */
|
|
fprintf(stderr, "GHOST: unable to initialize, exiting!\n");
|
|
/* This will leak memory, it's preferable to crashing. */
|
|
exit(1);
|
|
}
|
|
#if !(defined(WIN32) || defined(__APPLE__))
|
|
g_system_backend_id = GHOST_SystemBackend();
|
|
#endif
|
|
|
|
GHOST_Debug debug = {0};
|
|
if (G.debug & G_DEBUG_GHOST) {
|
|
debug.flags |= GHOST_kDebugDefault;
|
|
}
|
|
if (G.debug & G_DEBUG_WINTAB) {
|
|
debug.flags |= GHOST_kDebugWintab;
|
|
}
|
|
GHOST_SystemInitDebug(g_system, debug);
|
|
|
|
GHOST_AddEventConsumer(g_system, consumer);
|
|
|
|
if (wm_init_state.native_pixels) {
|
|
GHOST_UseNativePixels();
|
|
}
|
|
|
|
GHOST_UseWindowFocus(wm_init_state.window_focus);
|
|
}
|
|
|
|
/* TODO move this to wm_init_exit.c. */
|
|
void wm_ghost_init_background(void)
|
|
{
|
|
if (g_system) {
|
|
return;
|
|
}
|
|
|
|
GHOST_SetBacktraceHandler((GHOST_TBacktraceFn)BLI_system_backtrace);
|
|
|
|
g_system = GHOST_CreateSystemBackground();
|
|
|
|
GHOST_Debug debug = {0};
|
|
if (G.debug & G_DEBUG_GHOST) {
|
|
debug.flags |= GHOST_kDebugDefault;
|
|
}
|
|
GHOST_SystemInitDebug(g_system, debug);
|
|
}
|
|
|
|
void wm_ghost_exit(void)
|
|
{
|
|
if (g_system) {
|
|
GHOST_DisposeSystem(g_system);
|
|
}
|
|
g_system = NULL;
|
|
}
|
|
|
|
const char *WM_ghost_backend(void)
|
|
{
|
|
#if !(defined(WIN32) || defined(__APPLE__))
|
|
return g_system_backend_id ? g_system_backend_id : "NONE";
|
|
#else
|
|
/* While this could be supported, at the moment it's only needed with GHOST X11/WAYLAND
|
|
* to check which was selected and the API call may be removed after that's no longer needed.
|
|
* Use dummy values to prevent this being used on other systems. */
|
|
return g_system ? "DEFAULT" : "NONE";
|
|
#endif
|
|
}
|
|
|
|
GHOST_TDrawingContextType wm_ghost_drawing_context_type(const eGPUBackendType gpu_backend)
|
|
{
|
|
switch (gpu_backend) {
|
|
case GPU_BACKEND_NONE:
|
|
return GHOST_kDrawingContextTypeNone;
|
|
case GPU_BACKEND_ANY:
|
|
case GPU_BACKEND_OPENGL:
|
|
return GHOST_kDrawingContextTypeOpenGL;
|
|
case GPU_BACKEND_VULKAN:
|
|
#ifdef WITH_VULKAN_BACKEND
|
|
return GHOST_kDrawingContextTypeVulkan;
|
|
#endif
|
|
BLI_assert_unreachable();
|
|
return GHOST_kDrawingContextTypeNone;
|
|
case GPU_BACKEND_METAL:
|
|
#ifdef WITH_METAL_BACKEND
|
|
return GHOST_kDrawingContextTypeMetal;
|
|
#else
|
|
BLI_assert_unreachable();
|
|
return GHOST_kDrawingContextTypeNone;
|
|
#endif
|
|
}
|
|
|
|
/* Avoid control reaches end of non-void function compilation warning, which could be promoted
|
|
* to error. */
|
|
BLI_assert_unreachable();
|
|
return GHOST_kDrawingContextTypeNone;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Event Timer
|
|
* \{ */
|
|
|
|
void WM_event_timer_sleep(wmWindowManager *wm,
|
|
wmWindow *UNUSED(win),
|
|
wmTimer *timer,
|
|
bool do_sleep)
|
|
{
|
|
LISTBASE_FOREACH (wmTimer *, wt, &wm->timers) {
|
|
if (wt == timer) {
|
|
wt->sleep = do_sleep;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
wmTimer *WM_event_add_timer(wmWindowManager *wm, wmWindow *win, int event_type, double timestep)
|
|
{
|
|
wmTimer *wt = MEM_callocN(sizeof(wmTimer), "window timer");
|
|
BLI_assert(timestep >= 0.0f);
|
|
|
|
wt->event_type = event_type;
|
|
wt->ltime = PIL_check_seconds_timer();
|
|
wt->ntime = wt->ltime + timestep;
|
|
wt->stime = wt->ltime;
|
|
wt->timestep = timestep;
|
|
wt->win = win;
|
|
|
|
BLI_addtail(&wm->timers, wt);
|
|
|
|
return wt;
|
|
}
|
|
|
|
wmTimer *WM_event_add_timer_notifier(wmWindowManager *wm,
|
|
wmWindow *win,
|
|
uint type,
|
|
double timestep)
|
|
{
|
|
wmTimer *wt = MEM_callocN(sizeof(wmTimer), "window timer");
|
|
BLI_assert(timestep >= 0.0f);
|
|
|
|
wt->event_type = TIMERNOTIFIER;
|
|
wt->ltime = PIL_check_seconds_timer();
|
|
wt->ntime = wt->ltime + timestep;
|
|
wt->stime = wt->ltime;
|
|
wt->timestep = timestep;
|
|
wt->win = win;
|
|
wt->customdata = POINTER_FROM_UINT(type);
|
|
wt->flags |= WM_TIMER_NO_FREE_CUSTOM_DATA;
|
|
|
|
BLI_addtail(&wm->timers, wt);
|
|
|
|
return wt;
|
|
}
|
|
|
|
void WM_event_remove_timer(wmWindowManager *wm, wmWindow *UNUSED(win), wmTimer *timer)
|
|
{
|
|
/* extra security check */
|
|
wmTimer *wt = NULL;
|
|
LISTBASE_FOREACH (wmTimer *, timer_iter, &wm->timers) {
|
|
if (timer_iter == timer) {
|
|
wt = timer_iter;
|
|
}
|
|
}
|
|
if (wt == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (wm->reports.reporttimer == wt) {
|
|
wm->reports.reporttimer = NULL;
|
|
}
|
|
|
|
BLI_remlink(&wm->timers, wt);
|
|
if (wt->customdata != NULL && (wt->flags & WM_TIMER_NO_FREE_CUSTOM_DATA) == 0) {
|
|
MEM_freeN(wt->customdata);
|
|
}
|
|
MEM_freeN(wt);
|
|
|
|
/* there might be events in queue with this timer as customdata */
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
|
LISTBASE_FOREACH (wmEvent *, event, &win->event_queue) {
|
|
if (event->customdata == wt) {
|
|
event->customdata = NULL;
|
|
event->type = EVENT_NONE; /* Timer users customdata, don't want `NULL == NULL`. */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WM_event_remove_timer_notifier(wmWindowManager *wm, wmWindow *win, wmTimer *timer)
|
|
{
|
|
timer->customdata = NULL;
|
|
WM_event_remove_timer(wm, win, timer);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Clipboard
|
|
* \{ */
|
|
|
|
static char *wm_clipboard_text_get_ex(bool selection, int *r_len, bool firstline)
|
|
{
|
|
if (G.background) {
|
|
*r_len = 0;
|
|
return NULL;
|
|
}
|
|
|
|
char *buf = GHOST_getClipboard(selection);
|
|
if (!buf) {
|
|
*r_len = 0;
|
|
return NULL;
|
|
}
|
|
|
|
/* always convert from \r\n to \n */
|
|
char *newbuf = MEM_mallocN(strlen(buf) + 1, __func__);
|
|
char *p2 = newbuf;
|
|
|
|
if (firstline) {
|
|
/* will return an over-alloc'ed value in the case there are newlines */
|
|
for (char *p = buf; *p; p++) {
|
|
if (!ELEM(*p, '\n', '\r')) {
|
|
*(p2++) = *p;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (char *p = buf; *p; p++) {
|
|
if (*p != '\r') {
|
|
*(p2++) = *p;
|
|
}
|
|
}
|
|
}
|
|
|
|
*p2 = '\0';
|
|
|
|
free(buf); /* ghost uses regular malloc */
|
|
|
|
*r_len = (p2 - newbuf);
|
|
|
|
return newbuf;
|
|
}
|
|
|
|
char *WM_clipboard_text_get(bool selection, int *r_len)
|
|
{
|
|
return wm_clipboard_text_get_ex(selection, r_len, false);
|
|
}
|
|
|
|
char *WM_clipboard_text_get_firstline(bool selection, int *r_len)
|
|
{
|
|
return wm_clipboard_text_get_ex(selection, r_len, true);
|
|
}
|
|
|
|
void WM_clipboard_text_set(const char *buf, bool selection)
|
|
{
|
|
if (!G.background) {
|
|
#ifdef _WIN32
|
|
/* do conversion from \n to \r\n on Windows */
|
|
const char *p;
|
|
char *p2, *newbuf;
|
|
int newlen = 0;
|
|
|
|
for (p = buf; *p; p++) {
|
|
if (*p == '\n') {
|
|
newlen += 2;
|
|
}
|
|
else {
|
|
newlen++;
|
|
}
|
|
}
|
|
|
|
newbuf = MEM_callocN(newlen + 1, "WM_clipboard_text_set");
|
|
|
|
for (p = buf, p2 = newbuf; *p; p++, p2++) {
|
|
if (*p == '\n') {
|
|
*(p2++) = '\r';
|
|
*p2 = '\n';
|
|
}
|
|
else {
|
|
*p2 = *p;
|
|
}
|
|
}
|
|
*p2 = '\0';
|
|
|
|
GHOST_putClipboard(newbuf, selection);
|
|
MEM_freeN(newbuf);
|
|
#else
|
|
GHOST_putClipboard(buf, selection);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Progress Bar
|
|
* \{ */
|
|
|
|
void WM_progress_set(wmWindow *win, float progress)
|
|
{
|
|
/* In background mode we may have windows, but not actual GHOST windows. */
|
|
if (win->ghostwin) {
|
|
GHOST_SetProgressBar(win->ghostwin, progress);
|
|
}
|
|
}
|
|
|
|
void WM_progress_clear(wmWindow *win)
|
|
{
|
|
if (win->ghostwin) {
|
|
GHOST_EndProgressBar(win->ghostwin);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Window Position/Size (internal)
|
|
* \{ */
|
|
|
|
void wm_window_get_position(wmWindow *win, int *r_pos_x, int *r_pos_y)
|
|
{
|
|
*r_pos_x = win->posx;
|
|
*r_pos_y = win->posy;
|
|
}
|
|
|
|
void wm_window_set_size(wmWindow *win, int width, int height)
|
|
{
|
|
GHOST_SetClientSize(win->ghostwin, width, height);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Window Depth (Raise/Lower)
|
|
* \{ */
|
|
|
|
void wm_window_lower(wmWindow *win)
|
|
{
|
|
GHOST_SetWindowOrder(win->ghostwin, GHOST_kWindowOrderBottom);
|
|
}
|
|
|
|
void wm_window_raise(wmWindow *win)
|
|
{
|
|
/* Restore window if minimized */
|
|
if (GHOST_GetWindowState(win->ghostwin) == GHOST_kWindowStateMinimized) {
|
|
GHOST_SetWindowState(win->ghostwin, GHOST_kWindowStateNormal);
|
|
}
|
|
GHOST_SetWindowOrder(win->ghostwin, GHOST_kWindowOrderTop);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Window Buffers
|
|
* \{ */
|
|
|
|
void wm_window_swap_buffers(wmWindow *win)
|
|
{
|
|
GHOST_SwapWindowBuffers(win->ghostwin);
|
|
}
|
|
|
|
void wm_window_set_swap_interval(wmWindow *win, int interval)
|
|
{
|
|
GHOST_SetSwapInterval(win->ghostwin, interval);
|
|
}
|
|
|
|
bool wm_window_get_swap_interval(wmWindow *win, int *intervalOut)
|
|
{
|
|
return GHOST_GetSwapInterval(win->ghostwin, intervalOut);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Find Window Utility
|
|
* \{ */
|
|
|
|
wmWindow *WM_window_find_under_cursor(wmWindow *win, const int mval[2], int r_mval[2])
|
|
{
|
|
int tmp[2];
|
|
copy_v2_v2_int(tmp, mval);
|
|
wm_cursor_position_to_ghost_screen_coords(win, &tmp[0], &tmp[1]);
|
|
|
|
GHOST_WindowHandle ghostwin = GHOST_GetWindowUnderCursor(g_system, tmp[0], tmp[1]);
|
|
|
|
if (!ghostwin) {
|
|
return NULL;
|
|
}
|
|
|
|
wmWindow *win_other = GHOST_GetWindowUserData(ghostwin);
|
|
wm_cursor_position_from_ghost_screen_coords(win_other, &tmp[0], &tmp[1]);
|
|
copy_v2_v2_int(r_mval, tmp);
|
|
return win_other;
|
|
}
|
|
|
|
void WM_window_pixel_sample_read(const wmWindowManager *wm,
|
|
const wmWindow *win,
|
|
const int pos[2],
|
|
float r_col[3])
|
|
{
|
|
bool setup_context = wm->windrawable != win;
|
|
|
|
if (setup_context) {
|
|
GHOST_ActivateWindowDrawingContext(win->ghostwin);
|
|
GPU_context_active_set(win->gpuctx);
|
|
}
|
|
|
|
GPU_frontbuffer_read_pixels(pos[0], pos[1], 1, 1, 3, GPU_DATA_FLOAT, r_col);
|
|
|
|
if (setup_context) {
|
|
if (wm->windrawable) {
|
|
GHOST_ActivateWindowDrawingContext(wm->windrawable->ghostwin);
|
|
GPU_context_active_set(wm->windrawable->gpuctx);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Window Screen Shot Utility
|
|
*
|
|
* Include here since it can involve low level buffer switching.
|
|
*
|
|
* \{ */
|
|
|
|
uint *WM_window_pixels_read(wmWindowManager *wm, wmWindow *win, int r_size[2])
|
|
{
|
|
/* WARNING: Reading from the front-buffer immediately after drawing may fail,
|
|
* for a slower but more reliable version of this function #WM_window_pixels_read_offscreen
|
|
* should be preferred. See it's comments for details on why it's needed, see also T98462. */
|
|
bool setup_context = wm->windrawable != win;
|
|
|
|
if (setup_context) {
|
|
GHOST_ActivateWindowDrawingContext(win->ghostwin);
|
|
GPU_context_active_set(win->gpuctx);
|
|
}
|
|
|
|
r_size[0] = WM_window_pixels_x(win);
|
|
r_size[1] = WM_window_pixels_y(win);
|
|
const uint rect_len = r_size[0] * r_size[1];
|
|
uint *rect = MEM_mallocN(sizeof(*rect) * rect_len, __func__);
|
|
|
|
GPU_frontbuffer_read_pixels(0, 0, r_size[0], r_size[1], 4, GPU_DATA_UBYTE, rect);
|
|
|
|
if (setup_context) {
|
|
if (wm->windrawable) {
|
|
GHOST_ActivateWindowDrawingContext(wm->windrawable->ghostwin);
|
|
GPU_context_active_set(wm->windrawable->gpuctx);
|
|
}
|
|
}
|
|
|
|
/* Clear alpha, it is not set to a meaningful value in OpenGL. */
|
|
uchar *cp = (uchar *)rect;
|
|
uint i;
|
|
for (i = 0, cp += 3; i < rect_len; i++, cp += 4) {
|
|
*cp = 0xff;
|
|
}
|
|
return (uint *)rect;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Initial Window State API
|
|
* \{ */
|
|
|
|
void WM_init_state_size_set(int stax, int stay, int sizx, int sizy)
|
|
{
|
|
wm_init_state.start_x = stax; /* left hand pos */
|
|
wm_init_state.start_y = stay; /* bottom pos */
|
|
wm_init_state.size_x = sizx < 640 ? 640 : sizx;
|
|
wm_init_state.size_y = sizy < 480 ? 480 : sizy;
|
|
wm_init_state.override_flag |= WIN_OVERRIDE_GEOM;
|
|
}
|
|
|
|
void WM_init_state_fullscreen_set(void)
|
|
{
|
|
wm_init_state.windowstate = GHOST_kWindowStateFullScreen;
|
|
wm_init_state.override_flag |= WIN_OVERRIDE_WINSTATE;
|
|
}
|
|
|
|
void WM_init_state_normal_set(void)
|
|
{
|
|
wm_init_state.windowstate = GHOST_kWindowStateNormal;
|
|
wm_init_state.override_flag |= WIN_OVERRIDE_WINSTATE;
|
|
}
|
|
|
|
void WM_init_state_maximized_set(void)
|
|
{
|
|
wm_init_state.windowstate = GHOST_kWindowStateMaximized;
|
|
wm_init_state.override_flag |= WIN_OVERRIDE_WINSTATE;
|
|
}
|
|
|
|
void WM_init_window_focus_set(bool do_it)
|
|
{
|
|
wm_init_state.window_focus = do_it;
|
|
}
|
|
|
|
void WM_init_native_pixels(bool do_it)
|
|
{
|
|
wm_init_state.native_pixels = do_it;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Cursor API
|
|
* \{ */
|
|
|
|
void WM_init_input_devices(void)
|
|
{
|
|
if (UNLIKELY(!g_system)) {
|
|
return;
|
|
}
|
|
|
|
GHOST_SetMultitouchGestures(g_system, (U.uiflag & USER_NO_MULTITOUCH_GESTURES) == 0);
|
|
|
|
switch (U.tablet_api) {
|
|
case USER_TABLET_NATIVE:
|
|
GHOST_SetTabletAPI(g_system, GHOST_kTabletWinPointer);
|
|
break;
|
|
case USER_TABLET_WINTAB:
|
|
GHOST_SetTabletAPI(g_system, GHOST_kTabletWintab);
|
|
break;
|
|
case USER_TABLET_AUTOMATIC:
|
|
default:
|
|
GHOST_SetTabletAPI(g_system, GHOST_kTabletAutomatic);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void WM_cursor_warp(wmWindow *win, int x, int y)
|
|
{
|
|
if (!(win && win->ghostwin)) {
|
|
return;
|
|
}
|
|
|
|
int oldx = x, oldy = y;
|
|
|
|
wm_cursor_position_to_ghost_client_coords(win, &x, &y);
|
|
GHOST_SetCursorPosition(g_system, win->ghostwin, x, y);
|
|
|
|
win->eventstate->prev_xy[0] = oldx;
|
|
win->eventstate->prev_xy[1] = oldy;
|
|
|
|
win->eventstate->xy[0] = oldx;
|
|
win->eventstate->xy[1] = oldy;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Window Size (public)
|
|
* \{ */
|
|
|
|
int WM_window_pixels_x(const wmWindow *win)
|
|
{
|
|
float f = GHOST_GetNativePixelSize(win->ghostwin);
|
|
|
|
return (int)(f * (float)win->sizex);
|
|
}
|
|
int WM_window_pixels_y(const wmWindow *win)
|
|
{
|
|
float f = GHOST_GetNativePixelSize(win->ghostwin);
|
|
|
|
return (int)(f * (float)win->sizey);
|
|
}
|
|
|
|
void WM_window_rect_calc(const wmWindow *win, rcti *r_rect)
|
|
{
|
|
BLI_rcti_init(r_rect, 0, WM_window_pixels_x(win), 0, WM_window_pixels_y(win));
|
|
}
|
|
void WM_window_screen_rect_calc(const wmWindow *win, rcti *r_rect)
|
|
{
|
|
rcti window_rect, screen_rect;
|
|
|
|
WM_window_rect_calc(win, &window_rect);
|
|
screen_rect = window_rect;
|
|
|
|
/* Subtract global areas from screen rectangle. */
|
|
LISTBASE_FOREACH (ScrArea *, global_area, &win->global_areas.areabase) {
|
|
int height = ED_area_global_size_y(global_area) - 1;
|
|
|
|
if (global_area->global->flag & GLOBAL_AREA_IS_HIDDEN) {
|
|
continue;
|
|
}
|
|
|
|
switch (global_area->global->align) {
|
|
case GLOBAL_AREA_ALIGN_TOP:
|
|
screen_rect.ymax -= height;
|
|
break;
|
|
case GLOBAL_AREA_ALIGN_BOTTOM:
|
|
screen_rect.ymin += height;
|
|
break;
|
|
default:
|
|
BLI_assert_unreachable();
|
|
break;
|
|
}
|
|
}
|
|
|
|
BLI_assert(BLI_rcti_is_valid(&screen_rect));
|
|
|
|
*r_rect = screen_rect;
|
|
}
|
|
|
|
bool WM_window_is_fullscreen(const wmWindow *win)
|
|
{
|
|
return win->windowstate == GHOST_kWindowStateFullScreen;
|
|
}
|
|
|
|
bool WM_window_is_maximized(const wmWindow *win)
|
|
{
|
|
return win->windowstate == GHOST_kWindowStateMaximized;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Window Screen/Scene/WorkSpaceViewLayer API
|
|
* \{ */
|
|
|
|
void WM_windows_scene_data_sync(const ListBase *win_lb, Scene *scene)
|
|
{
|
|
LISTBASE_FOREACH (wmWindow *, win, win_lb) {
|
|
if (WM_window_get_active_scene(win) == scene) {
|
|
ED_workspace_scene_data_sync(win->workspace_hook, scene);
|
|
}
|
|
}
|
|
}
|
|
|
|
Scene *WM_windows_scene_get_from_screen(const wmWindowManager *wm, const bScreen *screen)
|
|
{
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
|
if (WM_window_get_active_screen(win) == screen) {
|
|
return WM_window_get_active_scene(win);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
ViewLayer *WM_windows_view_layer_get_from_screen(const wmWindowManager *wm, const bScreen *screen)
|
|
{
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
|
if (WM_window_get_active_screen(win) == screen) {
|
|
return WM_window_get_active_view_layer(win);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
WorkSpace *WM_windows_workspace_get_from_screen(const wmWindowManager *wm, const bScreen *screen)
|
|
{
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
|
if (WM_window_get_active_screen(win) == screen) {
|
|
return WM_window_get_active_workspace(win);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
Scene *WM_window_get_active_scene(const wmWindow *win)
|
|
{
|
|
return win->scene;
|
|
}
|
|
|
|
void WM_window_set_active_scene(Main *bmain, bContext *C, wmWindow *win, Scene *scene)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
wmWindow *win_parent = (win->parent) ? win->parent : win;
|
|
bool changed = false;
|
|
|
|
/* Set scene in parent and its child windows. */
|
|
if (win_parent->scene != scene) {
|
|
ED_screen_scene_change(C, win_parent, scene, true);
|
|
changed = true;
|
|
}
|
|
|
|
LISTBASE_FOREACH (wmWindow *, win_child, &wm->windows) {
|
|
if (win_child->parent == win_parent && win_child->scene != scene) {
|
|
ED_screen_scene_change(C, win_child, scene, true);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
/* Update depsgraph and renderers for scene change. */
|
|
ViewLayer *view_layer = WM_window_get_active_view_layer(win_parent);
|
|
ED_scene_change_update(bmain, scene, view_layer);
|
|
|
|
/* Complete redraw. */
|
|
WM_event_add_notifier(C, NC_WINDOW, NULL);
|
|
}
|
|
}
|
|
|
|
ViewLayer *WM_window_get_active_view_layer(const wmWindow *win)
|
|
{
|
|
Scene *scene = WM_window_get_active_scene(win);
|
|
if (scene == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
ViewLayer *view_layer = BKE_view_layer_find(scene, win->view_layer_name);
|
|
if (view_layer) {
|
|
return view_layer;
|
|
}
|
|
|
|
view_layer = BKE_view_layer_default_view(scene);
|
|
if (view_layer) {
|
|
WM_window_set_active_view_layer((wmWindow *)win, view_layer);
|
|
}
|
|
|
|
return view_layer;
|
|
}
|
|
|
|
void WM_window_set_active_view_layer(wmWindow *win, ViewLayer *view_layer)
|
|
{
|
|
BLI_assert(BKE_view_layer_find(WM_window_get_active_scene(win), view_layer->name) != NULL);
|
|
Main *bmain = G_MAIN;
|
|
|
|
wmWindowManager *wm = bmain->wm.first;
|
|
wmWindow *win_parent = (win->parent) ? win->parent : win;
|
|
|
|
/* Set view layer in parent and child windows. */
|
|
LISTBASE_FOREACH (wmWindow *, win_iter, &wm->windows) {
|
|
if ((win_iter == win_parent) || (win_iter->parent == win_parent)) {
|
|
STRNCPY(win_iter->view_layer_name, view_layer->name);
|
|
bScreen *screen = BKE_workspace_active_screen_get(win_iter->workspace_hook);
|
|
ED_render_view_layer_changed(bmain, screen);
|
|
}
|
|
}
|
|
}
|
|
|
|
void WM_window_ensure_active_view_layer(wmWindow *win)
|
|
{
|
|
/* Update layer name is correct after scene changes, load without UI, etc. */
|
|
Scene *scene = WM_window_get_active_scene(win);
|
|
|
|
if (scene && BKE_view_layer_find(scene, win->view_layer_name) == NULL) {
|
|
ViewLayer *view_layer = BKE_view_layer_default_view(scene);
|
|
STRNCPY(win->view_layer_name, view_layer->name);
|
|
}
|
|
}
|
|
|
|
WorkSpace *WM_window_get_active_workspace(const wmWindow *win)
|
|
{
|
|
return BKE_workspace_active_get(win->workspace_hook);
|
|
}
|
|
|
|
void WM_window_set_active_workspace(bContext *C, wmWindow *win, WorkSpace *workspace)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
wmWindow *win_parent = (win->parent) ? win->parent : win;
|
|
|
|
ED_workspace_change(workspace, C, wm, win);
|
|
|
|
LISTBASE_FOREACH (wmWindow *, win_child, &wm->windows) {
|
|
if (win_child->parent == win_parent) {
|
|
bScreen *screen = WM_window_get_active_screen(win_child);
|
|
/* Don't change temporary screens, they only serve a single purpose. */
|
|
if (screen->temp) {
|
|
continue;
|
|
}
|
|
ED_workspace_change(workspace, C, wm, win_child);
|
|
}
|
|
}
|
|
}
|
|
|
|
WorkSpaceLayout *WM_window_get_active_layout(const wmWindow *win)
|
|
{
|
|
const WorkSpace *workspace = WM_window_get_active_workspace(win);
|
|
return (LIKELY(workspace != NULL) ? BKE_workspace_active_layout_get(win->workspace_hook) : NULL);
|
|
}
|
|
void WM_window_set_active_layout(wmWindow *win, WorkSpace *workspace, WorkSpaceLayout *layout)
|
|
{
|
|
BKE_workspace_active_layout_set(win->workspace_hook, win->winid, workspace, layout);
|
|
}
|
|
|
|
bScreen *WM_window_get_active_screen(const wmWindow *win)
|
|
{
|
|
const WorkSpace *workspace = WM_window_get_active_workspace(win);
|
|
/* May be NULL in rare cases like closing Blender */
|
|
return (LIKELY(workspace != NULL) ? BKE_workspace_active_screen_get(win->workspace_hook) : NULL);
|
|
}
|
|
void WM_window_set_active_screen(wmWindow *win, WorkSpace *workspace, bScreen *screen)
|
|
{
|
|
BKE_workspace_active_screen_set(win->workspace_hook, win->winid, workspace, screen);
|
|
}
|
|
|
|
bool WM_window_is_temp_screen(const wmWindow *win)
|
|
{
|
|
const bScreen *screen = WM_window_get_active_screen(win);
|
|
return (screen && screen->temp != 0);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Window IME API
|
|
* \{ */
|
|
|
|
#ifdef WITH_INPUT_IME
|
|
/**
|
|
* \note Keep in mind #wm_window_IME_begin is also used to reposition the IME window.
|
|
*/
|
|
void wm_window_IME_begin(wmWindow *win, int x, int y, int w, int h, bool complete)
|
|
{
|
|
BLI_assert(win);
|
|
|
|
/* Convert to native OS window coordinates. */
|
|
float fac = GHOST_GetNativePixelSize(win->ghostwin);
|
|
x /= fac;
|
|
y /= fac;
|
|
GHOST_BeginIME(win->ghostwin, x, win->sizey - y, w, h, complete);
|
|
}
|
|
|
|
void wm_window_IME_end(wmWindow *win)
|
|
{
|
|
BLI_assert(win && win->ime_data);
|
|
|
|
GHOST_EndIME(win->ghostwin);
|
|
win->ime_data = NULL;
|
|
}
|
|
#endif /* WITH_INPUT_IME */
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Direct OpenGL Context Management
|
|
* \{ */
|
|
|
|
void *WM_opengl_context_create(void)
|
|
{
|
|
/* On Windows there is a problem creating contexts that share resources (almost any object,
|
|
* including legacy display lists, but also textures) with a context which is current in another
|
|
* thread. This is a documented and behavior of both `::wglCreateContextAttribsARB()` and
|
|
* `::wglShareLists()`.
|
|
*
|
|
* Other platforms might successfully share resources from context which is active somewhere
|
|
* else, but to keep our code behave the same on all platform we expect contexts to only be
|
|
* created from the main thread. */
|
|
|
|
BLI_assert(BLI_thread_is_main());
|
|
BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get());
|
|
|
|
GHOST_GLSettings glSettings = {0};
|
|
if (G.debug & G_DEBUG_GPU) {
|
|
glSettings.flags |= GHOST_glDebugContext;
|
|
}
|
|
return GHOST_CreateOpenGLContext(g_system, glSettings);
|
|
}
|
|
|
|
void WM_opengl_context_dispose(void *context)
|
|
{
|
|
BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get());
|
|
GHOST_DisposeOpenGLContext(g_system, (GHOST_ContextHandle)context);
|
|
}
|
|
|
|
void WM_opengl_context_activate(void *context)
|
|
{
|
|
BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get());
|
|
GHOST_ActivateOpenGLContext((GHOST_ContextHandle)context);
|
|
}
|
|
|
|
void WM_opengl_context_release(void *context)
|
|
{
|
|
BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get());
|
|
GHOST_ReleaseOpenGLContext((GHOST_ContextHandle)context);
|
|
}
|
|
|
|
void WM_ghost_show_message_box(const char *title,
|
|
const char *message,
|
|
const char *help_label,
|
|
const char *continue_label,
|
|
const char *link,
|
|
GHOST_DialogOptions dialog_options)
|
|
{
|
|
BLI_assert(g_system);
|
|
GHOST_ShowMessageBox(g_system, title, message, help_label, continue_label, link, dialog_options);
|
|
}
|
|
|
|
/** \} */
|