MacOS: Embedded window support.

This commit is contained in:
Stuart Carnie 2025-04-29 07:01:27 +10:00
parent 1cf573f44d
commit 00e1fdec2c
37 changed files with 3670 additions and 384 deletions

View file

@ -32,6 +32,23 @@
#include "core/error/error_macros.h"
enum class InputEventType {
INVALID = -1,
KEY,
MOUSE_BUTTON,
MOUSE_MOTION,
JOY_MOTION,
JOY_BUTTON,
SCREEN_TOUCH,
SCREEN_DRAG,
MAGNIFY_GESTURE,
PAN_GESTURE,
MIDI,
SHORTCUT,
ACTION,
MAX,
};
enum class HatDir {
UP = 0,
RIGHT = 1,

View file

@ -89,6 +89,8 @@ public:
virtual bool accumulate(const Ref<InputEvent> &p_event) { return false; }
virtual InputEventType get_type() const { return InputEventType::INVALID; }
InputEvent() {}
};
@ -202,6 +204,8 @@ public:
static Ref<InputEventKey> create_reference(Key p_keycode_with_modifier_masks, bool p_physical = false);
InputEventType get_type() const final override { return InputEventType::KEY; }
InputEventKey() {}
};
@ -261,6 +265,8 @@ public:
virtual String as_text() const override;
virtual String to_string() override;
InputEventType get_type() const final override { return InputEventType::MOUSE_BUTTON; }
InputEventMouseButton() {}
};
@ -306,6 +312,8 @@ public:
virtual bool accumulate(const Ref<InputEvent> &p_event) override;
InputEventType get_type() const final override { return InputEventType::MOUSE_MOTION; }
InputEventMouseMotion() {}
};
@ -333,6 +341,8 @@ public:
static Ref<InputEventJoypadMotion> create_reference(JoyAxis p_axis, float p_value);
InputEventType get_type() const final override { return InputEventType::JOY_MOTION; }
InputEventJoypadMotion() {}
};
@ -363,6 +373,8 @@ public:
static Ref<InputEventJoypadButton> create_reference(JoyButton p_btn_index);
InputEventType get_type() const final override { return InputEventType::JOY_BUTTON; }
InputEventJoypadButton() {}
};
@ -392,6 +404,8 @@ public:
virtual String as_text() const override;
virtual String to_string() override;
InputEventType get_type() const final override { return InputEventType::SCREEN_TOUCH; }
InputEventScreenTouch() {}
};
@ -444,6 +458,8 @@ public:
virtual bool accumulate(const Ref<InputEvent> &p_event) override;
InputEventType get_type() const final override { return InputEventType::SCREEN_DRAG; }
InputEventScreenDrag() {}
};
@ -479,6 +495,8 @@ public:
virtual String as_text() const override;
virtual String to_string() override;
InputEventType get_type() const final override { return InputEventType::ACTION; }
InputEventAction() {}
};
@ -510,6 +528,8 @@ public:
virtual String as_text() const override;
virtual String to_string() override;
InputEventType get_type() const final override { return InputEventType::MAGNIFY_GESTURE; }
InputEventMagnifyGesture() {}
};
@ -528,6 +548,8 @@ public:
virtual String as_text() const override;
virtual String to_string() override;
InputEventType get_type() const final override { return InputEventType::PAN_GESTURE; }
InputEventPanGesture() {}
};
@ -574,6 +596,8 @@ public:
virtual String as_text() const override;
virtual String to_string() override;
InputEventType get_type() const final override { return InputEventType::MIDI; }
InputEventMIDI() {}
};
@ -592,5 +616,7 @@ public:
virtual String as_text() const override;
virtual String to_string() override;
InputEventType get_type() const final override { return InputEventType::SHORTCUT; }
InputEventShortcut();
};

View file

@ -0,0 +1,474 @@
/**************************************************************************/
/* input_event_codec.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "input_event_codec.h"
#include "core/input/input.h"
#include "core/io/marshalls.h"
#include "core/os/os.h"
enum class BoolShift : uint8_t {
SHIFT = 0,
CTRL,
ALT,
META,
ECHO,
PRESSED,
DOUBLE_CLICK,
PEN_INVERTED,
};
// cast operator for BoolShift to uint8_t
inline uint8_t operator<<(uint8_t a, BoolShift b) {
return a << static_cast<uint8_t>(b);
}
uint8_t encode_key_modifier_state(Ref<InputEventWithModifiers> p_event) {
uint8_t bools = 0;
bools |= (uint8_t)p_event->is_shift_pressed() << BoolShift::SHIFT;
bools |= (uint8_t)p_event->is_ctrl_pressed() << BoolShift::CTRL;
bools |= (uint8_t)p_event->is_alt_pressed() << BoolShift::ALT;
bools |= (uint8_t)p_event->is_meta_pressed() << BoolShift::META;
return bools;
}
void decode_key_modifier_state(uint8_t bools, Ref<InputEventWithModifiers> p_event) {
p_event->set_shift_pressed(bools & (1 << BoolShift::SHIFT));
p_event->set_ctrl_pressed(bools & (1 << BoolShift::CTRL));
p_event->set_alt_pressed(bools & (1 << BoolShift::ALT));
p_event->set_meta_pressed(bools & (1 << BoolShift::META));
}
int encode_vector2(const Vector2 &p_vector, uint8_t *p_data) {
p_data += encode_float(p_vector.x, p_data);
encode_float(p_vector.y, p_data);
return sizeof(float) * 2;
}
const uint8_t *decode_vector2(Vector2 &r_vector, const uint8_t *p_data) {
r_vector.x = decode_float(p_data);
p_data += sizeof(float);
r_vector.y = decode_float(p_data);
p_data += sizeof(float);
return p_data;
}
void encode_input_event_key(const Ref<InputEventKey> &p_event, PackedByteArray &r_data) {
r_data.resize(19);
uint8_t *data = r_data.ptrw();
*data = (uint8_t)InputEventType::KEY;
data++;
uint8_t bools = encode_key_modifier_state(p_event);
bools |= (uint8_t)p_event->is_echo() << BoolShift::ECHO;
bools |= (uint8_t)p_event->is_pressed() << BoolShift::PRESSED;
*data = bools;
data++;
data += encode_uint32((uint32_t)p_event->get_keycode(), data);
data += encode_uint32((uint32_t)p_event->get_physical_keycode(), data);
data += encode_uint32((uint32_t)p_event->get_key_label(), data);
data += encode_uint32(p_event->get_unicode(), data);
*data = (uint8_t)p_event->get_location();
data++;
// Assert we had enough space.
DEV_ASSERT(data - r_data.ptrw() >= r_data.size());
}
Error decode_input_event_key(const PackedByteArray &p_data, Ref<InputEventKey> &r_event) {
const uint8_t *data = p_data.ptr();
DEV_ASSERT(static_cast<InputEventType>(*data) == InputEventType::KEY);
data++; // Skip event type.
uint8_t bools = *data;
data++;
decode_key_modifier_state(bools, r_event);
r_event->set_echo(bools & (1 << BoolShift::ECHO));
r_event->set_pressed(bools & (1 << BoolShift::PRESSED));
Key keycode = (Key)decode_uint32(data);
data += sizeof(uint32_t);
Key physical_keycode = (Key)decode_uint32(data);
data += sizeof(uint32_t);
Key key_label = (Key)decode_uint32(data);
data += sizeof(uint32_t);
char32_t unicode = (char32_t)decode_uint32(data);
data += sizeof(uint32_t);
KeyLocation location = (KeyLocation)*data;
r_event->set_keycode(keycode);
r_event->set_physical_keycode(physical_keycode);
r_event->set_key_label(key_label);
r_event->set_unicode(unicode);
r_event->set_location(location);
return OK;
}
void encode_input_event_mouse_button(const Ref<InputEventMouseButton> &p_event, PackedByteArray &r_data) {
r_data.resize(12);
uint8_t *data = r_data.ptrw();
*data = (uint8_t)InputEventType::MOUSE_BUTTON;
data++;
uint8_t bools = encode_key_modifier_state(p_event);
bools |= (uint8_t)p_event->is_pressed() << BoolShift::PRESSED;
bools |= (uint8_t)p_event->is_double_click() << BoolShift::DOUBLE_CLICK;
*data = bools;
data++;
*data = (uint8_t)p_event->get_button_index();
data++;
// Rather than use encode_variant, we explicitly encode the Vector2,
// so decoding is easier. Specifically, we don't have to perform additional error
// checking for decoding the variant and then checking the variant type.
data += encode_vector2(p_event->get_position(), data);
*data = (uint8_t)p_event->get_button_mask();
data++;
// Assert we had enough space.
DEV_ASSERT(data - r_data.ptrw() >= r_data.size());
}
Error decode_input_event_mouse_button(const PackedByteArray &p_data, Ref<InputEventMouseButton> &r_event) {
const uint8_t *data = p_data.ptr();
DEV_ASSERT(static_cast<InputEventType>(*data) == InputEventType::MOUSE_BUTTON);
data++; // Skip event type.
uint8_t bools = *data;
data++;
decode_key_modifier_state(bools, r_event);
r_event->set_pressed(bools & (1 << BoolShift::PRESSED));
r_event->set_double_click(bools & (1 << BoolShift::DOUBLE_CLICK));
r_event->set_button_index((MouseButton)*data);
data++;
Vector2 pos;
data = decode_vector2(pos, data);
r_event->set_position(pos);
r_event->set_global_position(pos); // these are the same
BitField<MouseButtonMask> button_mask = (MouseButtonMask)*data;
r_event->set_button_mask(button_mask);
return OK;
}
void encode_input_event_mouse_motion(const Ref<InputEventMouseMotion> &p_event, PackedByteArray &r_data) {
r_data.resize(31);
uint8_t *data = r_data.ptrw();
*data = (uint8_t)InputEventType::MOUSE_MOTION;
data++;
uint8_t bools = encode_key_modifier_state(p_event);
bools |= (uint8_t)p_event->get_pen_inverted() << BoolShift::PEN_INVERTED;
*data = bools;
data++;
// Rather than use encode_variant, we explicitly encode the Vector2,
// so decoding is easier. Specifically, we don't have to perform additional error
// checking for decoding the variant and then checking the variant type.
data += encode_vector2(p_event->get_position(), data);
data += encode_float(p_event->get_pressure(), data);
data += encode_vector2(p_event->get_tilt(), data);
data += encode_vector2(p_event->get_relative(), data);
*data = (uint8_t)p_event->get_button_mask();
data++;
// Assert we had enough space.
DEV_ASSERT(data - r_data.ptrw() >= r_data.size());
}
void decode_input_event_mouse_motion(const PackedByteArray &p_data, Ref<InputEventMouseMotion> &r_event) {
Input *input = Input::get_singleton();
const uint8_t *data = p_data.ptr();
DEV_ASSERT(static_cast<InputEventType>(*data) == InputEventType::MOUSE_MOTION);
data++; // Skip event type.
uint8_t bools = *data;
data++;
decode_key_modifier_state(bools, r_event);
r_event->set_pen_inverted(bools & (1 << BoolShift::PEN_INVERTED));
{
Vector2 pos;
data = decode_vector2(pos, data);
r_event->set_position(pos);
r_event->set_global_position(pos); // these are the same
}
r_event->set_pressure(decode_float(data));
data += sizeof(float);
{
Vector2 tilt;
data = decode_vector2(tilt, data);
r_event->set_tilt(tilt);
}
r_event->set_velocity(input->get_last_mouse_velocity());
r_event->set_screen_velocity(input->get_last_mouse_screen_velocity());
{
Vector2 relative;
data = decode_vector2(relative, data);
r_event->set_relative(relative);
r_event->set_relative_screen_position(relative);
}
BitField<MouseButtonMask> button_mask = (MouseButtonMask)*data;
r_event->set_button_mask(button_mask);
data++;
// Assert we had enough space.
DEV_ASSERT(data - p_data.ptr() >= p_data.size());
}
void encode_input_event_joypad_button(const Ref<InputEventJoypadButton> &p_event, PackedByteArray &r_data) {
r_data.resize(11);
uint8_t *data = r_data.ptrw();
*data = (uint8_t)InputEventType::JOY_BUTTON;
data++;
uint8_t bools = 0;
bools |= (uint8_t)p_event->is_pressed() << BoolShift::PRESSED;
*data = bools;
data++;
data += encode_uint64(p_event->get_device(), data);
*data = (uint8_t)p_event->get_button_index();
data++;
// Assert we had enough space.
DEV_ASSERT(data - r_data.ptrw() >= r_data.size());
}
void decode_input_event_joypad_button(const PackedByteArray &p_data, Ref<InputEventJoypadButton> &r_event) {
const uint8_t *data = p_data.ptr();
DEV_ASSERT(static_cast<InputEventType>(*data) == InputEventType::JOY_BUTTON);
data++; // Skip event type.
uint8_t bools = *data;
data++;
r_event->set_pressed(bools & (1 << BoolShift::PRESSED));
r_event->set_device(decode_uint64(data));
data += sizeof(uint64_t);
r_event->set_button_index((JoyButton)*data);
data++;
// Assert we had enough space.
DEV_ASSERT(data - p_data.ptr() >= p_data.size());
}
void encode_input_event_joypad_motion(const Ref<InputEventJoypadMotion> &p_event, PackedByteArray &r_data) {
r_data.resize(14);
uint8_t *data = r_data.ptrw();
*data = (uint8_t)InputEventType::JOY_MOTION;
data++;
data += encode_uint64(p_event->get_device(), data);
*data = (uint8_t)p_event->get_axis();
data++;
data += encode_float(p_event->get_axis_value(), data);
// Assert we had enough space.
DEV_ASSERT(data - r_data.ptrw() >= r_data.size());
}
void decode_input_event_joypad_motion(const PackedByteArray &p_data, Ref<InputEventJoypadMotion> &r_event) {
const uint8_t *data = p_data.ptr();
DEV_ASSERT(static_cast<InputEventType>(*data) == InputEventType::JOY_MOTION);
data++; // Skip event type.
r_event->set_device(decode_uint64(data));
data += sizeof(uint64_t);
r_event->set_axis((JoyAxis)*data);
data++;
r_event->set_axis_value(decode_float(data));
data += sizeof(float);
// Assert we had enough space.
DEV_ASSERT(data - p_data.ptr() >= p_data.size());
}
void encode_input_event_gesture_pan(const Ref<InputEventPanGesture> &p_event, PackedByteArray &r_data) {
r_data.resize(18);
uint8_t *data = r_data.ptrw();
*data = (uint8_t)InputEventType::PAN_GESTURE;
data++;
uint8_t bools = encode_key_modifier_state(p_event);
*data = bools;
data++;
data += encode_vector2(p_event->get_position(), data);
data += encode_vector2(p_event->get_delta(), data);
// Assert we had enough space.
DEV_ASSERT(data - r_data.ptrw() >= r_data.size());
}
void decode_input_event_gesture_pan(const PackedByteArray &p_data, Ref<InputEventPanGesture> &r_event) {
const uint8_t *data = p_data.ptr();
DEV_ASSERT(static_cast<InputEventType>(*data) == InputEventType::PAN_GESTURE);
data++; // Skip event type.
uint8_t bools = *data;
data++;
decode_key_modifier_state(bools, r_event);
Vector2 pos;
data = decode_vector2(pos, data);
r_event->set_position(pos);
Vector2 delta;
data = decode_vector2(delta, data);
r_event->set_delta(delta);
// Assert we had enough space.
DEV_ASSERT(data - p_data.ptr() >= p_data.size());
}
void encode_input_event_gesture_magnify(const Ref<InputEventMagnifyGesture> &p_event, PackedByteArray &r_data) {
r_data.resize(14);
uint8_t *data = r_data.ptrw();
*data = (uint8_t)InputEventType::MAGNIFY_GESTURE;
data++;
uint8_t bools = encode_key_modifier_state(p_event);
*data = bools;
data++;
data += encode_vector2(p_event->get_position(), data);
data += encode_float(p_event->get_factor(), data);
// Assert we had enough space.
DEV_ASSERT(data - r_data.ptrw() >= r_data.size());
}
void decode_input_event_gesture_magnify(const PackedByteArray &p_data, Ref<InputEventMagnifyGesture> &r_event) {
const uint8_t *data = p_data.ptr();
DEV_ASSERT(static_cast<InputEventType>(*data) == InputEventType::MAGNIFY_GESTURE);
data++; // Skip event type.
uint8_t bools = *data;
data++;
decode_key_modifier_state(bools, r_event);
Vector2 pos;
data = decode_vector2(pos, data);
r_event->set_position(pos);
r_event->set_factor(decode_float(data));
data += sizeof(float);
// Assert we had enough space.
DEV_ASSERT(data - p_data.ptr() >= p_data.size());
}
bool encode_input_event(const Ref<InputEvent> &p_event, PackedByteArray &r_data) {
switch (p_event->get_type()) {
case InputEventType::KEY:
encode_input_event_key(p_event, r_data);
break;
case InputEventType::MOUSE_BUTTON:
encode_input_event_mouse_button(p_event, r_data);
break;
case InputEventType::MOUSE_MOTION:
encode_input_event_mouse_motion(p_event, r_data);
break;
case InputEventType::JOY_MOTION:
encode_input_event_joypad_motion(p_event, r_data);
break;
case InputEventType::JOY_BUTTON:
encode_input_event_joypad_button(p_event, r_data);
break;
case InputEventType::MAGNIFY_GESTURE:
encode_input_event_gesture_magnify(p_event, r_data);
break;
case InputEventType::PAN_GESTURE:
encode_input_event_gesture_pan(p_event, r_data);
break;
default:
return false;
}
return true;
}
void decode_input_event(const PackedByteArray &p_data, Ref<InputEvent> &r_event) {
const uint8_t *data = p_data.ptr();
switch (static_cast<InputEventType>(*data)) {
case InputEventType::KEY: {
Ref<InputEventKey> event;
event.instantiate();
decode_input_event_key(p_data, event);
r_event = event;
} break;
case InputEventType::MOUSE_BUTTON: {
Ref<InputEventMouseButton> event;
event.instantiate();
decode_input_event_mouse_button(p_data, event);
r_event = event;
} break;
case InputEventType::MOUSE_MOTION: {
Ref<InputEventMouseMotion> event;
event.instantiate();
decode_input_event_mouse_motion(p_data, event);
r_event = event;
} break;
case InputEventType::JOY_BUTTON: {
Ref<InputEventJoypadButton> event;
event.instantiate();
decode_input_event_joypad_button(p_data, event);
r_event = event;
} break;
case InputEventType::JOY_MOTION: {
Ref<InputEventJoypadMotion> event;
event.instantiate();
decode_input_event_joypad_motion(p_data, event);
r_event = event;
} break;
case InputEventType::PAN_GESTURE: {
Ref<InputEventPanGesture> event;
event.instantiate();
decode_input_event_gesture_pan(p_data, event);
r_event = event;
} break;
case InputEventType::MAGNIFY_GESTURE: {
Ref<InputEventMagnifyGesture> event;
event.instantiate();
decode_input_event_gesture_magnify(p_data, event);
r_event = event;
} break;
default: {
WARN_PRINT(vformat("Unknown event type %d.", static_cast<int>(*data)));
} break;
}
}

View file

@ -0,0 +1,49 @@
/**************************************************************************/
/* input_event_codec.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/input/input_event.h"
/**
* Encodes the input event as a byte array.
*
* Returns `true` if the event was successfully encoded, `false` otherwise.
*/
bool encode_input_event(const Ref<InputEvent> &p_event, PackedByteArray &r_data);
void decode_input_event(const PackedByteArray &p_data, Ref<InputEvent> &r_event);
void encode_input_event_key(const Ref<InputEventKey> &p_event, PackedByteArray &r_data);
void encode_input_event_mouse_button(const Ref<InputEventMouseButton> &p_event, PackedByteArray &r_data);
void encode_input_event_mouse_motion(const Ref<InputEventMouseMotion> &p_event, PackedByteArray &r_data);
void encode_input_event_joypad_button(const Ref<InputEventJoypadButton> &p_event, PackedByteArray &r_data);
void encode_input_event_joypad_motion(const Ref<InputEventJoypadMotion> &p_event, PackedByteArray &r_data);
void encode_input_event_gesture_pan(const Ref<InputEventPanGesture> &p_event, PackedByteArray &r_data);
void encode_input_event_gesture_magnify(const Ref<InputEventMagnifyGesture> &p_event, PackedByteArray &r_data);

View file

@ -49,7 +49,8 @@ struct GameController {
bool double_nintendo_joycon_layout = false;
bool single_nintendo_joycon_layout = false;
bool axis_changed[(int)JoyAxis::MAX];
uint32_t axis_changed_mask = 0;
static_assert(static_cast<uint32_t>(JoyAxis::MAX) < 32, "JoyAxis::MAX must be less than 32");
double axis_value[(int)JoyAxis::MAX];
GameController(int p_joy_id, GCController *p_controller);

View file

@ -136,7 +136,6 @@ GameController::GameController(int p_joy_id, GCController *p_controller) :
force_feedback = NO;
for (int i = 0; i < (int)JoyAxis::MAX; i++) {
axis_changed[i] = false;
axis_value[i] = 0.0;
}
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
@ -169,36 +168,36 @@ GameController::GameController(int p_joy_id, GCController *p_controller) :
auto JOYSTICK_LEFT = ^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
if (axis_value[(int)JoyAxis::LEFT_X] != xValue) {
axis_changed[(int)JoyAxis::LEFT_X] = true;
axis_changed_mask |= (1 << (int)JoyAxis::LEFT_X);
axis_value[(int)JoyAxis::LEFT_X] = xValue;
}
if (axis_value[(int)JoyAxis::LEFT_Y] != -yValue) {
axis_changed[(int)JoyAxis::LEFT_Y] = true;
axis_changed_mask |= (1 << (int)JoyAxis::LEFT_Y);
axis_value[(int)JoyAxis::LEFT_Y] = -yValue;
}
};
auto JOYSTICK_RIGHT = ^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
if (axis_value[(int)JoyAxis::RIGHT_X] != xValue) {
axis_changed[(int)JoyAxis::RIGHT_X] = true;
axis_changed_mask |= (1 << (int)JoyAxis::RIGHT_X);
axis_value[(int)JoyAxis::RIGHT_X] = xValue;
}
if (axis_value[(int)JoyAxis::RIGHT_Y] != -yValue) {
axis_changed[(int)JoyAxis::RIGHT_Y] = true;
axis_changed_mask |= (1 << (int)JoyAxis::RIGHT_Y);
axis_value[(int)JoyAxis::RIGHT_Y] = -yValue;
}
};
auto TRIGGER_LEFT = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (axis_value[(int)JoyAxis::TRIGGER_LEFT] != value) {
axis_changed[(int)JoyAxis::TRIGGER_LEFT] = true;
axis_changed_mask |= (1 << (int)JoyAxis::TRIGGER_LEFT);
axis_value[(int)JoyAxis::TRIGGER_LEFT] = value;
}
};
auto TRIGGER_RIGHT = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (axis_value[(int)JoyAxis::TRIGGER_RIGHT] != value) {
axis_changed[(int)JoyAxis::TRIGGER_RIGHT] = true;
axis_changed_mask |= (1 << (int)JoyAxis::TRIGGER_RIGHT);
axis_value[(int)JoyAxis::TRIGGER_RIGHT] = value;
}
};
@ -595,20 +594,25 @@ void JoypadApple::joypad_vibration_stop(GameController &p_joypad, uint64_t p_tim
}
void JoypadApple::process_joypads() {
Input *input = Input::get_singleton();
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
for (KeyValue<int, GameController *> &E : joypads) {
int id = E.key;
GameController &joypad = *E.value;
for (int i = 0; i < (int)JoyAxis::MAX; i++) {
if (joypad.axis_changed[i]) {
joypad.axis_changed[i] = false;
Input::get_singleton()->joy_axis(id, (JoyAxis)i, joypad.axis_value[i]);
}
uint32_t changed = joypad.axis_changed_mask;
joypad.axis_changed_mask = 0;
// Loop over changed axes.
while (changed) {
// Find the index of the next set bit.
uint32_t i = (uint32_t)__builtin_ctzll(changed);
// Clear the set bit.
changed &= (changed - 1);
input->joy_axis(id, (JoyAxis)i, joypad.axis_value[i]);
}
if (joypad.force_feedback) {
Input *input = Input::get_singleton();
uint64_t timestamp = input->get_joy_vibration_timestamp(id);
if (timestamp > (unsigned)joypad.ff_effect_timestamp) {

View file

@ -408,6 +408,8 @@ void ScriptEditorDebugger::_msg_debug_exit(uint64_t p_thread_id, const Array &p_
void ScriptEditorDebugger::_msg_set_pid(uint64_t p_thread_id, const Array &p_data) {
ERR_FAIL_COND(p_data.is_empty());
remote_pid = p_data[0];
// We emit the started signal after we've set the PID.
emit_signal(SNAME("started"));
}
void ScriptEditorDebugger::_msg_scene_click_ctrl(uint64_t p_thread_id, const Array &p_data) {
@ -1174,7 +1176,6 @@ void ScriptEditorDebugger::start(Ref<RemoteDebuggerPeer> p_peer) {
_set_reason_text(TTR("Debug session started."), MESSAGE_SUCCESS);
_update_buttons_state();
emit_signal(SNAME("started"));
Array quit_keys = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("editor/stop_running_project"));
_put_msg("scene:setup_scene", quit_keys);

View file

@ -7097,6 +7097,14 @@ void EditorNode::_touch_actions_panel_mode_changed() {
}
#endif
#ifdef MACOS_ENABLED
extern "C" GameViewPluginBase *get_game_view_plugin();
#else
GameViewPluginBase *get_game_view_plugin() {
return memnew(GameViewPlugin);
}
#endif
EditorNode::EditorNode() {
DEV_ASSERT(!singleton);
singleton = this;
@ -8175,7 +8183,7 @@ EditorNode::EditorNode() {
add_editor_plugin(memnew(ScriptEditorPlugin));
if (!Engine::get_singleton()->is_recovery_mode_hint()) {
add_editor_plugin(memnew(GameViewPlugin));
add_editor_plugin(get_game_view_plugin());
}
EditorAudioBuses *audio_bus_editor = EditorAudioBuses::register_editor();

View file

@ -35,25 +35,14 @@
#include "scene/resources/style_box_flat.h"
#include "scene/theme/theme_db.h"
void EmbeddedProcess::_notification(int p_what) {
void EmbeddedProcessBase::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
window = get_window();
} break;
case NOTIFICATION_PROCESS: {
if (updated_embedded_process_queued) {
updated_embedded_process_queued = false;
_update_embedded_process();
}
} break;
case NOTIFICATION_DRAW: {
_draw();
} break;
case NOTIFICATION_RESIZED:
case NOTIFICATION_VISIBILITY_CHANGED:
case NOTIFICATION_WM_POSITION_CHANGED: {
queue_update_embedded_process();
} break;
case NOTIFICATION_THEME_CHANGED: {
focus_style_box = get_theme_stylebox(SNAME("FocusViewport"), EditorStringName(EditorStyles));
Ref<StyleBoxFlat> focus_style_box_flat = focus_style_box;
@ -68,60 +57,43 @@ void EmbeddedProcess::_notification(int p_what) {
margin_bottom_right = Point2i();
}
} break;
case NOTIFICATION_FOCUS_ENTER: {
queue_update_embedded_process();
} break;
case NOTIFICATION_APPLICATION_FOCUS_IN: {
application_has_focus = true;
last_application_focus_time = OS::get_singleton()->get_ticks_msec();
} break;
case NOTIFICATION_APPLICATION_FOCUS_OUT: {
application_has_focus = false;
} break;
}
}
void EmbeddedProcess::set_window_size(const Size2i p_window_size) {
void EmbeddedProcessBase::_bind_methods() {
ADD_SIGNAL(MethodInfo("embedding_completed"));
ADD_SIGNAL(MethodInfo("embedding_failed"));
ADD_SIGNAL(MethodInfo("embedded_process_updated"));
ADD_SIGNAL(MethodInfo("embedded_process_focused"));
}
void EmbeddedProcessBase::_draw() {
if (is_process_focused() && focus_style_box.is_valid()) {
Size2 size = get_size();
Rect2 r = Rect2(Point2(), size);
focus_style_box->draw(get_canvas_item(), r);
}
}
void EmbeddedProcessBase::set_window_size(const Size2i &p_window_size) {
if (window_size != p_window_size) {
window_size = p_window_size;
queue_update_embedded_process();
}
}
void EmbeddedProcess::set_keep_aspect(bool p_keep_aspect) {
void EmbeddedProcessBase::set_keep_aspect(bool p_keep_aspect) {
if (keep_aspect != p_keep_aspect) {
keep_aspect = p_keep_aspect;
queue_update_embedded_process();
}
}
Rect2i EmbeddedProcess::get_adjusted_embedded_window_rect(Rect2i p_rect) {
Rect2i control_rect = Rect2i(p_rect.position + margin_top_left, (p_rect.size - get_margins_size()).maxi(1));
if (window) {
control_rect.position += window->get_position();
}
if (window_size != Size2i()) {
Rect2i desired_rect = Rect2i();
if (!keep_aspect && control_rect.size.x >= window_size.x && control_rect.size.y >= window_size.y) {
// Fixed at the desired size.
desired_rect.size = window_size;
} else {
float ratio = MIN((float)control_rect.size.x / window_size.x, (float)control_rect.size.y / window_size.y);
desired_rect.size = Size2i(window_size.x * ratio, window_size.y * ratio).maxi(1);
}
desired_rect.position = Size2i(control_rect.position.x + ((control_rect.size.x - desired_rect.size.x) / 2), control_rect.position.y + ((control_rect.size.y - desired_rect.size.y) / 2));
return desired_rect;
} else {
// Stretch, use all the control area.
return control_rect;
}
}
Rect2i EmbeddedProcess::get_screen_embedded_window_rect() {
Rect2i EmbeddedProcessBase::get_screen_embedded_window_rect() const {
return get_adjusted_embedded_window_rect(get_global_rect());
}
int EmbeddedProcess::get_margin_size(Side p_side) const {
int EmbeddedProcessBase::get_margin_size(Side p_side) const {
ERR_FAIL_INDEX_V((int)p_side, 4, 0);
switch (p_side) {
@ -138,18 +110,51 @@ int EmbeddedProcess::get_margin_size(Side p_side) const {
return 0;
}
Size2 EmbeddedProcess::get_margins_size() {
Size2 EmbeddedProcessBase::get_margins_size() const {
return margin_top_left + margin_bottom_right;
}
bool EmbeddedProcess::is_embedding_in_progress() {
EmbeddedProcessBase::EmbeddedProcessBase() {
set_focus_mode(FOCUS_ALL);
}
EmbeddedProcessBase::~EmbeddedProcessBase() {
}
Rect2i EmbeddedProcess::get_adjusted_embedded_window_rect(const Rect2i &p_rect) const {
Rect2i control_rect = Rect2i(p_rect.position + margin_top_left, (p_rect.size - get_margins_size()).maxi(1));
if (window) {
control_rect.position += window->get_position();
}
if (window_size != Size2i()) {
Rect2i desired_rect;
if (!keep_aspect && control_rect.size.x >= window_size.x && control_rect.size.y >= window_size.y) {
// Fixed at the desired size.
desired_rect.size = window_size;
} else {
float ratio = MIN((float)control_rect.size.x / window_size.x, (float)control_rect.size.y / window_size.y);
desired_rect.size = Size2i(window_size.x * ratio, window_size.y * ratio).maxi(1);
}
desired_rect.position = Size2i(control_rect.position.x + ((control_rect.size.x - desired_rect.size.x) / 2), control_rect.position.y + ((control_rect.size.y - desired_rect.size.y) / 2));
return desired_rect;
} else {
// Stretch, use all the control area.
return control_rect;
}
}
bool EmbeddedProcess::is_embedding_in_progress() const {
return !timer_embedding->is_stopped();
}
bool EmbeddedProcess::is_embedding_completed() {
bool EmbeddedProcess::is_embedding_completed() const {
return embedding_completed;
}
bool EmbeddedProcess::is_process_focused() const {
return focused_process_id == current_process_id && has_focus();
}
int EmbeddedProcess::get_embedded_pid() const {
return current_process_id;
}
@ -270,11 +275,29 @@ void EmbeddedProcess::_timer_embedding_timeout() {
_try_embed_process();
}
void EmbeddedProcess::_draw() {
if (focused_process_id == current_process_id && has_focus() && focus_style_box.is_valid()) {
Size2 size = get_size();
Rect2 r = Rect2(Point2(), size);
focus_style_box->draw(get_canvas_item(), r);
void EmbeddedProcess::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_PROCESS: {
if (updated_embedded_process_queued) {
updated_embedded_process_queued = false;
_update_embedded_process();
}
} break;
case NOTIFICATION_RESIZED:
case NOTIFICATION_VISIBILITY_CHANGED:
case NOTIFICATION_WM_POSITION_CHANGED: {
queue_update_embedded_process();
} break;
case NOTIFICATION_APPLICATION_FOCUS_IN: {
application_has_focus = true;
last_application_focus_time = OS::get_singleton()->get_ticks_msec();
} break;
case NOTIFICATION_APPLICATION_FOCUS_OUT: {
application_has_focus = false;
} break;
case NOTIFICATION_FOCUS_ENTER: {
queue_update_embedded_process();
} break;
}
}
@ -386,14 +409,8 @@ Window *EmbeddedProcess::_get_current_modal_window() {
return nullptr;
}
void EmbeddedProcess::_bind_methods() {
ADD_SIGNAL(MethodInfo("embedding_completed"));
ADD_SIGNAL(MethodInfo("embedding_failed"));
ADD_SIGNAL(MethodInfo("embedded_process_updated"));
ADD_SIGNAL(MethodInfo("embedded_process_focused"));
}
EmbeddedProcess::EmbeddedProcess() {
EmbeddedProcess::EmbeddedProcess() :
EmbeddedProcessBase() {
timer_embedding = memnew(Timer);
timer_embedding->set_wait_time(0.1);
timer_embedding->set_one_shot(true);
@ -404,8 +421,6 @@ EmbeddedProcess::EmbeddedProcess() {
timer_update_embedded_process->set_wait_time(0.1);
add_child(timer_update_embedded_process);
timer_update_embedded_process->connect("timeout", callable_mp(this, &EmbeddedProcess::_timer_update_embedded_process_timeout));
set_focus_mode(FOCUS_ALL);
}
EmbeddedProcess::~EmbeddedProcess() {

View file

@ -32,8 +32,49 @@
#include "scene/gui/control.h"
class EmbeddedProcess : public Control {
GDCLASS(EmbeddedProcess, Control);
class ScriptEditorDebugger;
class EmbeddedProcessBase : public Control {
GDCLASS(EmbeddedProcessBase, Control);
void _draw();
protected:
Ref<StyleBox> focus_style_box;
Size2i window_size;
bool keep_aspect = false;
Point2i margin_top_left;
Point2i margin_bottom_right;
Window *window = nullptr;
static void _bind_methods();
void _notification(int p_what);
public:
virtual void set_script_debugger(ScriptEditorDebugger *p_debugger) {}
virtual bool is_embedding_completed() const = 0;
virtual bool is_embedding_in_progress() const = 0;
virtual bool is_process_focused() const = 0;
virtual void embed_process(OS::ProcessID p_pid) = 0;
virtual int get_embedded_pid() const = 0;
virtual void reset() = 0;
virtual void request_close() = 0;
virtual void queue_update_embedded_process() = 0;
void set_window_size(const Size2i &p_window_size);
void set_keep_aspect(bool p_keep_aspect);
virtual Rect2i get_adjusted_embedded_window_rect(const Rect2i &p_rect) const = 0;
Rect2i get_screen_embedded_window_rect() const;
int get_margin_size(Side p_side) const;
Size2 get_margins_size() const;
EmbeddedProcessBase();
virtual ~EmbeddedProcessBase();
};
class EmbeddedProcess : public EmbeddedProcessBase {
GDCLASS(EmbeddedProcess, EmbeddedProcessBase);
bool application_has_focus = true;
uint64_t last_application_focus_time = 0;
@ -45,51 +86,37 @@ class EmbeddedProcess : public Control {
bool updated_embedded_process_queued = false;
bool last_updated_embedded_process_focused = false;
Window *window = nullptr;
Timer *timer_embedding = nullptr;
Timer *timer_update_embedded_process = nullptr;
const int embedding_timeout = 45000;
bool keep_aspect = false;
Size2i window_size;
Ref<StyleBox> focus_style_box;
Point2i margin_top_left;
Point2i margin_bottom_right;
Rect2i last_global_rect;
void _try_embed_process();
void _update_embedded_process();
void _timer_embedding_timeout();
void _timer_update_embedded_process_timeout();
void _draw();
void _check_mouse_over();
void _check_focused_process_id();
bool _is_embedded_process_updatable();
Rect2i _get_global_embedded_window_rect();
Window *_get_current_modal_window();
protected:
static void _bind_methods();
void _notification(int p_what);
public:
void embed_process(OS::ProcessID p_pid);
void reset();
void request_close();
bool is_embedding_in_progress() const override;
bool is_embedding_completed() const override;
bool is_process_focused() const override;
void embed_process(OS::ProcessID p_pid) override;
int get_embedded_pid() const override;
void reset() override;
void request_close() override;
void queue_update_embedded_process() override;
void set_window_size(const Size2i p_window_size);
void set_keep_aspect(bool p_keep_aspect);
void queue_update_embedded_process();
Rect2i get_adjusted_embedded_window_rect(Rect2i p_rect);
Rect2i get_screen_embedded_window_rect();
int get_margin_size(Side p_side) const;
Size2 get_margins_size();
bool is_embedding_in_progress();
bool is_embedding_completed();
int get_embedded_pid() const;
Rect2i get_adjusted_embedded_window_rect(const Rect2i &p_rect) const override;
EmbeddedProcess();
~EmbeddedProcess();
~EmbeddedProcess() override;
};

View file

@ -243,11 +243,17 @@ void GameView::_sessions_changed() {
_update_debugger_buttons();
#ifdef MACOS_ENABLED
if (!embedded_script_debugger || !embedded_script_debugger->is_session_active() || embedded_script_debugger->get_remote_pid() != embedded_process->get_embedded_pid()) {
_attach_script_debugger();
}
#else
if (embedded_process->is_embedding_completed()) {
if (!embedded_script_debugger || !embedded_script_debugger->is_session_active() || embedded_script_debugger->get_remote_pid() != embedded_process->get_embedded_pid()) {
_attach_script_debugger();
}
}
#endif
}
void GameView::_instance_starting_static(int p_idx, List<String> &r_arguments) {
@ -370,7 +376,9 @@ void GameView::_stop_pressed() {
}
void GameView::_embedding_completed() {
#ifndef MACOS_ENABLED
_attach_script_debugger();
#endif
_update_ui();
if (make_floating_on_play) {
get_window()->set_flag(Window::FLAG_ALWAYS_ON_TOP, bool(GLOBAL_GET("display/window/size/always_on_top")));
@ -507,9 +515,11 @@ GameView::EmbedAvailability GameView::_get_embed_available() {
if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) {
return EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED;
}
#ifndef MACOS_ENABLED
if (get_tree()->get_root()->is_embedding_subwindows()) {
return EMBED_NOT_AVAILABLE_SINGLE_WINDOW_MODE;
}
#endif
String display_driver = GLOBAL_GET("display/display_server/driver");
if (display_driver == "headless" || display_driver == "wayland") {
return EMBED_NOT_AVAILABLE_PROJECT_DISPLAY_DRIVER;
@ -786,14 +796,19 @@ void GameView::_attach_script_debugger() {
}
embedded_script_debugger = nullptr;
for (int i = 0; EditorDebuggerNode::get_singleton()->get_debugger(i); i++) {
ScriptEditorDebugger *script_debugger = EditorDebuggerNode::get_singleton()->get_debugger(i);
int i = 0;
while (ScriptEditorDebugger *script_debugger = EditorDebuggerNode::get_singleton()->get_debugger(i)) {
if (script_debugger->is_session_active() && script_debugger->get_remote_pid() == embedded_process->get_embedded_pid()) {
embedded_script_debugger = script_debugger;
break;
}
i++;
}
#ifdef MACOS_ENABLED
embedded_process->set_script_debugger(embedded_script_debugger);
#endif
if (embedded_script_debugger) {
embedded_script_debugger->connect("remote_window_title_changed", callable_mp(this, &GameView::_remote_window_title_changed));
embedded_script_debugger->connect("embed_shortcut_requested", callable_mp(this, &GameView::_handle_shortcut_requested));
@ -845,6 +860,12 @@ void GameView::_update_arguments_for_instance(int p_idx, List<String> &r_argumen
List<String>::Element *N = r_arguments.insert_before(user_args_element, "--wid");
N = r_arguments.insert_after(N, itos(DisplayServer::get_singleton()->window_get_native_handle(DisplayServer::WINDOW_HANDLE, get_window()->get_window_id())));
#if MACOS_ENABLED
r_arguments.push_back("--display-driver");
r_arguments.push_back("embedded");
r_arguments.push_back("--embedded");
#endif
// Be sure to have the correct window size in the embedded_process control.
_update_embed_window_size();
Rect2i rect = embedded_process->get_screen_embedded_window_rect();
@ -931,11 +952,12 @@ void GameView::_feature_profile_changed() {
node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->set_visible(is_3d_enabled);
}
GameView::GameView(Ref<GameViewDebugger> p_debugger, WindowWrapper *p_wrapper) {
GameView::GameView(Ref<GameViewDebugger> p_debugger, EmbeddedProcessBase *p_embedded_process, WindowWrapper *p_wrapper) {
singleton = this;
debugger = p_debugger;
window_wrapper = p_wrapper;
embedded_process = p_embedded_process;
// Add some margin to the sides for better aesthetics.
// This prevents the first button's hover/pressed effect from "touching" the panel's border,
@ -1051,7 +1073,6 @@ GameView::GameView(Ref<GameViewDebugger> p_debugger, WindowWrapper *p_wrapper) {
camera_override_menu->set_h_size_flags(SIZE_SHRINK_END);
camera_override_menu->set_tooltip_text(TTR("Camera Override Options"));
camera_override_menu->set_accessibility_name(TTRC("Camera Override Options"));
_camera_override_menu_id_pressed(EditorSettings::get_singleton()->get_project_metadata("game_view", "camera_override_mode", 0));
PopupMenu *menu = camera_override_menu->get_popup();
menu->connect(SceneStringName(id_pressed), callable_mp(this, &GameView::_camera_override_menu_id_pressed));
@ -1061,6 +1082,7 @@ GameView::GameView(Ref<GameViewDebugger> p_debugger, WindowWrapper *p_wrapper) {
menu->add_radio_check_item(TTR("Manipulate In-Game"), CAMERA_MODE_INGAME);
menu->set_item_checked(menu->get_item_index(CAMERA_MODE_INGAME), true);
menu->add_radio_check_item(TTR("Manipulate From Editors"), CAMERA_MODE_EDITORS);
_camera_override_menu_id_pressed(EditorSettings::get_singleton()->get_project_metadata("game_view", "camera_override_mode", 0));
embedding_separator = memnew(VSeparator);
main_menu_hbox->add_child(embedding_separator);
@ -1118,7 +1140,6 @@ GameView::GameView(Ref<GameViewDebugger> p_debugger, WindowWrapper *p_wrapper) {
panel->set_theme_type_variation("GamePanel");
panel->set_v_size_flags(SIZE_EXPAND_FILL);
embedded_process = memnew(EmbeddedProcess);
panel->add_child(embedded_process);
embedded_process->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
embedded_process->connect("embedding_failed", callable_mp(this, &GameView::_embedding_failed));
@ -1131,6 +1152,9 @@ GameView::GameView(Ref<GameViewDebugger> p_debugger, WindowWrapper *p_wrapper) {
state_container->add_theme_constant_override("margin_left", 8 * EDSCALE);
state_container->add_theme_constant_override("margin_right", 8 * EDSCALE);
state_container->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
#ifdef MACOS_ENABLED
state_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
#endif
panel->add_child(state_container);
state_label = memnew(Label());
@ -1156,7 +1180,7 @@ GameView::GameView(Ref<GameViewDebugger> p_debugger, WindowWrapper *p_wrapper) {
///////
void GameViewPlugin::selected_notify() {
void GameViewPluginBase::selected_notify() {
if (_is_window_wrapper_enabled()) {
#ifdef ANDROID_ENABLED
notify_main_screen_changed(get_plugin_name());
@ -1168,7 +1192,7 @@ void GameViewPlugin::selected_notify() {
}
#ifndef ANDROID_ENABLED
void GameViewPlugin::make_visible(bool p_visible) {
void GameViewPluginBase::make_visible(bool p_visible) {
if (p_visible) {
window_wrapper->show();
} else {
@ -1176,35 +1200,54 @@ void GameViewPlugin::make_visible(bool p_visible) {
}
}
void GameViewPlugin::set_window_layout(Ref<ConfigFile> p_layout) {
void GameViewPluginBase::set_window_layout(Ref<ConfigFile> p_layout) {
game_view->set_window_layout(p_layout);
}
void GameViewPlugin::get_window_layout(Ref<ConfigFile> p_layout) {
void GameViewPluginBase::get_window_layout(Ref<ConfigFile> p_layout) {
game_view->get_window_layout(p_layout);
}
void GameViewPluginBase::setup(Ref<GameViewDebugger> p_debugger, EmbeddedProcessBase *p_embedded_process) {
debugger = p_debugger;
window_wrapper = memnew(WindowWrapper);
window_wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), TTR("Game Workspace")));
window_wrapper->set_margins_enabled(true);
game_view = memnew(GameView(debugger, p_embedded_process, window_wrapper));
game_view->set_v_size_flags(Control::SIZE_EXPAND_FILL);
window_wrapper->set_wrapped_control(game_view, nullptr);
EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(window_wrapper);
window_wrapper->set_v_size_flags(Control::SIZE_EXPAND_FILL);
window_wrapper->hide();
window_wrapper->connect("window_visibility_changed", callable_mp(this, &GameViewPlugin::_focus_another_editor).unbind(1));
}
#endif // ANDROID_ENABLED
void GameViewPlugin::_notification(int p_what) {
void GameViewPluginBase::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
add_debugger_plugin(debugger);
connect("main_screen_changed", callable_mp(this, &GameViewPlugin::_save_last_editor));
connect("main_screen_changed", callable_mp(this, &GameViewPluginBase::_save_last_editor));
} break;
case NOTIFICATION_EXIT_TREE: {
remove_debugger_plugin(debugger);
disconnect("main_screen_changed", callable_mp(this, &GameViewPlugin::_save_last_editor));
disconnect("main_screen_changed", callable_mp(this, &GameViewPluginBase::_save_last_editor));
} break;
}
}
void GameViewPlugin::_save_last_editor(const String &p_editor) {
void GameViewPluginBase::_save_last_editor(const String &p_editor) {
if (p_editor != get_plugin_name()) {
last_editor = p_editor;
}
}
void GameViewPlugin::_focus_another_editor() {
void GameViewPluginBase::_focus_another_editor() {
if (_is_window_wrapper_enabled()) {
if (last_editor.is_empty()) {
EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_2D);
@ -1214,7 +1257,7 @@ void GameViewPlugin::_focus_another_editor() {
}
}
bool GameViewPlugin::_is_window_wrapper_enabled() const {
bool GameViewPluginBase::_is_window_wrapper_enabled() const {
#ifdef ANDROID_ENABLED
return true;
#else
@ -1222,22 +1265,15 @@ bool GameViewPlugin::_is_window_wrapper_enabled() const {
#endif // ANDROID_ENABLED
}
GameViewPlugin::GameViewPlugin() {
debugger.instantiate();
#ifndef ANDROID_ENABLED
window_wrapper = memnew(WindowWrapper);
window_wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), TTR("Game Workspace")));
window_wrapper->set_margins_enabled(true);
game_view = memnew(GameView(debugger, window_wrapper));
game_view->set_v_size_flags(Control::SIZE_EXPAND_FILL);
window_wrapper->set_wrapped_control(game_view, nullptr);
EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(window_wrapper);
window_wrapper->set_v_size_flags(Control::SIZE_EXPAND_FILL);
window_wrapper->hide();
window_wrapper->connect("window_visibility_changed", callable_mp(this, &GameViewPlugin::_focus_another_editor).unbind(1));
#endif // ANDROID_ENABLED
GameViewPluginBase::GameViewPluginBase() {
}
GameViewPlugin::GameViewPlugin() :
GameViewPluginBase() {
#ifndef ANDROID_ENABLED
Ref<GameViewDebugger> game_view_debugger;
game_view_debugger.instantiate();
EmbeddedProcess *embedded_process = memnew(EmbeddedProcess);
setup(game_view_debugger, embedded_process);
#endif
}

View file

@ -37,7 +37,7 @@
#include "scene/debugger/scene_debugger.h"
#include "scene/gui/box_container.h"
class EmbeddedProcess;
class EmbeddedProcessBase;
class VSeparator;
class WindowWrapper;
class ScriptEditorDebugger;
@ -154,7 +154,7 @@ class GameView : public VBoxContainer {
MenuButton *embed_options_menu = nullptr;
Label *game_size_label = nullptr;
Panel *panel = nullptr;
EmbeddedProcess *embedded_process = nullptr;
EmbeddedProcessBase *embedded_process = nullptr;
Label *state_label = nullptr;
void _sessions_changed();
@ -214,11 +214,11 @@ public:
void set_window_layout(Ref<ConfigFile> p_layout);
void get_window_layout(Ref<ConfigFile> p_layout);
GameView(Ref<GameViewDebugger> p_debugger, WindowWrapper *p_wrapper);
GameView(Ref<GameViewDebugger> p_debugger, EmbeddedProcessBase *p_embedded_process, WindowWrapper *p_wrapper);
};
class GameViewPlugin : public EditorPlugin {
GDCLASS(GameViewPlugin, EditorPlugin);
class GameViewPluginBase : public EditorPlugin {
GDCLASS(GameViewPluginBase, EditorPlugin);
#ifndef ANDROID_ENABLED
GameView *game_view = nullptr;
@ -238,6 +238,9 @@ class GameViewPlugin : public EditorPlugin {
protected:
void _notification(int p_what);
#ifndef ANDROID_ENABLED
void setup(Ref<GameViewDebugger> p_debugger, EmbeddedProcessBase *p_embedded_process);
#endif
public:
virtual String get_plugin_name() const override { return TTRC("Game"); }
@ -254,6 +257,12 @@ public:
virtual void set_window_layout(Ref<ConfigFile> p_layout) override;
virtual void get_window_layout(Ref<ConfigFile> p_layout) override;
#endif // ANDROID_ENABLED
GameViewPluginBase();
};
class GameViewPlugin : public GameViewPluginBase {
GDCLASS(GameViewPlugin, GameViewPluginBase);
public:
GameViewPlugin();
};

View file

@ -11,7 +11,10 @@ files = [
"godot_application_delegate.mm",
"crash_handler_macos.mm",
"macos_terminal_logger.mm",
"display_server_embedded.mm",
"display_server_macos.mm",
"embedded_debugger.mm",
"embedded_gl_manager.mm",
"godot_button_view.mm",
"godot_content_view.mm",
"godot_status_item.mm",
@ -30,6 +33,12 @@ files = [
"gl_manager_macos_legacy.mm",
]
if env.editor_build:
files += [
"editor/embedded_game_view_plugin.mm",
"editor/embedded_process_macos.mm",
]
prog = env.add_program("#bin/godot", files)
if env["debug_symbols"] and env["separate_debug_symbols"]:

View file

@ -231,6 +231,8 @@ def configure(env: "SConsEnvironment"):
"Security",
"-framework",
"UniformTypeIdentifiers",
"-framework",
"IOSurface",
]
)
env.Append(LIBS=["pthread", "z"])
@ -245,7 +247,6 @@ def configure(env: "SConsEnvironment"):
env.Append(LINKFLAGS=["-lANGLE.macos." + env["arch"]])
env.Append(LINKFLAGS=["-lEGL.macos." + env["arch"]])
env.Append(LINKFLAGS=["-lGLES.macos." + env["arch"]])
extra_frameworks.add("IOSurface")
env.Prepend(CPPEXTPATH=["#thirdparty/angle/include"])
env.Append(LINKFLAGS=["-rpath", "@executable_path/../Frameworks", "-rpath", "@executable_path"])
@ -264,7 +265,6 @@ def configure(env: "SConsEnvironment"):
if env["vulkan"]:
env.AppendUnique(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"])
extra_frameworks.add("Metal")
extra_frameworks.add("IOSurface")
if not env["use_volk"]:
env.Append(LINKFLAGS=["-lMoltenVK"])

View file

@ -0,0 +1,231 @@
/**************************************************************************/
/* display_server_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/input/input.h"
#include "servers/display_server.h"
#if defined(GLES3_ENABLED)
#include "embedded_gl_manager.h"
#include "platform_gl.h"
#endif // GLES3_ENABLED
#if defined(RD_ENABLED)
#include "servers/rendering/rendering_device.h"
#if defined(VULKAN_ENABLED)
#import "rendering_context_driver_vulkan_macos.h"
#endif // VULKAN_ENABLED
#if defined(METAL_ENABLED)
#import "drivers/metal/rendering_context_driver_metal.h"
#endif
#endif // RD_ENABLED
@class CAContext;
class DisplayServerEmbedded : public DisplayServer {
GDCLASS(DisplayServerEmbedded, DisplayServer)
_THREAD_SAFE_CLASS_
struct {
float screen_max_scale = 1.0f;
float screen_dpi = 96.0f;
} state;
NativeMenu *native_menu = nullptr;
HashMap<WindowID, ObjectID> window_attached_instance_id;
HashMap<WindowID, Callable> window_event_callbacks;
HashMap<WindowID, Callable> window_resize_callbacks;
HashMap<WindowID, Callable> input_event_callbacks;
HashMap<WindowID, Callable> input_text_callbacks;
float content_scale = 1.0f;
WindowID window_id_counter = MAIN_WINDOW_ID;
CAContext *ca_context = nil;
// Either be a CAMetalLayer or a CALayer depending on the rendering driver.
CALayer *layer = nil;
#ifdef GLES3_ENABLED
GLManagerEmbedded *gl_manager = nullptr;
#endif
#if defined(RD_ENABLED)
RenderingContextDriver *rendering_context = nullptr;
RenderingDevice *rendering_device = nullptr;
#endif
String rendering_driver;
Point2i ime_last_position;
Point2i im_selection;
String im_text;
MouseMode mouse_mode = MOUSE_MODE_VISIBLE;
MouseMode mouse_mode_base = MOUSE_MODE_VISIBLE;
MouseMode mouse_mode_override = MOUSE_MODE_VISIBLE;
bool mouse_mode_override_enabled = false;
void _mouse_update_mode();
CursorShape cursor_shape = CURSOR_ARROW;
struct Joy {
String name;
uint64_t timestamp = 0;
Joy() = default;
Joy(const String &p_name) :
name(p_name) {}
};
HashMap<int, Joy> joysticks;
public:
static void register_embedded_driver();
static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error);
static Vector<String> get_rendering_drivers_func();
// MARK: - Events
virtual void process_events() override;
virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
static void _dispatch_input_events(const Ref<InputEvent> &p_event);
void send_input_event(const Ref<InputEvent> &p_event, DisplayServer::WindowID p_id = MAIN_WINDOW_ID) const;
void send_input_text(const String &p_text, DisplayServer::WindowID p_id = MAIN_WINDOW_ID) const;
void send_window_event(DisplayServer::WindowEvent p_event, DisplayServer::WindowID p_id = MAIN_WINDOW_ID) const;
void _window_callback(const Callable &p_callable, const Variant &p_arg) const;
virtual void beep() const override;
// MARK: - Mouse
virtual void mouse_set_mode(MouseMode p_mode) override;
virtual MouseMode mouse_get_mode() const override;
virtual void mouse_set_mode_override(MouseMode p_mode) override;
virtual MouseMode mouse_get_mode_override() const override;
virtual void mouse_set_mode_override_enabled(bool p_override_enabled) override;
virtual bool mouse_is_mode_override_enabled() const override;
virtual Point2i mouse_get_position() const override;
virtual BitField<MouseButtonMask> mouse_get_button_state() const override;
// MARK: - Joystick
void joy_add(int p_idx, const String &p_name);
void joy_del(int p_idx);
// MARK: - Window
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
virtual int get_screen_count() const override;
virtual int get_primary_screen() const override;
virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Vector<DisplayServer::WindowID> get_window_list() const override;
virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override;
virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_transient(WindowID p_window, WindowID p_parent) override;
virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override;
virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual float screen_get_max_scale() const override;
virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual bool can_any_window_draw() const override;
virtual void window_set_ime_active(const bool p_active, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_ime_position(const Point2i &p_pos, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override;
virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override;
void update_im_text(const Point2i &p_selection, const String &p_text);
virtual Point2i ime_get_selection() const override;
virtual String ime_get_text() const override;
virtual void cursor_set_shape(CursorShape p_shape) override;
virtual CursorShape cursor_get_shape() const override;
virtual void cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override;
void update_state(const Dictionary &p_state);
void set_content_scale(float p_scale);
virtual void swap_buffers() override;
DisplayServerEmbedded(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error);
~DisplayServerEmbedded();
};

View file

@ -0,0 +1,744 @@
/**************************************************************************/
/* display_server_embedded.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "display_server_embedded.h"
#import "embedded_debugger.h"
#import "macos_quartz_core_spi.h"
#import "core/config/project_settings.h"
#import "core/debugger/engine_debugger.h"
#if defined(GLES3_ENABLED)
#include "drivers/gles3/rasterizer_gles3.h"
#endif
#if defined(RD_ENABLED)
#import "servers/rendering/renderer_rd/renderer_compositor_rd.h"
#endif
DisplayServerEmbedded::DisplayServerEmbedded(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) {
EmbeddedDebugger::initialize(this);
r_error = OK; // default to OK
native_menu = memnew(NativeMenu);
Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
rendering_driver = p_rendering_driver;
#if defined(RD_ENABLED)
#if defined(VULKAN_ENABLED)
#if defined(__x86_64__)
bool fallback_to_vulkan = GLOBAL_GET("rendering/rendering_device/fallback_to_vulkan");
if (!fallback_to_vulkan) {
WARN_PRINT("Metal is not supported on Intel Macs, switching to Vulkan.");
}
// Metal rendering driver not available on Intel.
if (rendering_driver == "metal") {
rendering_driver = "vulkan";
OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
}
#endif
if (rendering_driver == "vulkan") {
rendering_context = memnew(RenderingContextDriverVulkanMacOS);
}
#endif
#if defined(METAL_ENABLED)
if (rendering_driver == "metal") {
rendering_context = memnew(RenderingContextDriverMetal);
}
#endif
if (rendering_context) {
if (rendering_context->initialize() != OK) {
memdelete(rendering_context);
rendering_context = nullptr;
#if defined(GLES3_ENABLED)
bool fallback_to_opengl3 = GLOBAL_GET("rendering/rendering_device/fallback_to_opengl3");
if (fallback_to_opengl3 && rendering_driver != "opengl3") {
WARN_PRINT("Your device seem not to support MoltenVK or Metal, switching to OpenGL 3.");
rendering_driver = "opengl3";
OS::get_singleton()->set_current_rendering_method("gl_compatibility");
OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
} else
#endif
{
r_error = ERR_CANT_CREATE;
ERR_FAIL_MSG("Could not initialize " + rendering_driver);
}
}
}
#endif
#if defined(GLES3_ENABLED)
if (rendering_driver == "opengl3_angle") {
WARN_PRINT("ANGLE not supported for embedded display, switching to native OpenGL.");
rendering_driver = "opengl3";
OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
}
if (rendering_driver == "opengl3") {
gl_manager = memnew(GLManagerEmbedded);
if (gl_manager->initialize() != OK) {
memdelete(gl_manager);
gl_manager = nullptr;
r_error = ERR_UNAVAILABLE;
ERR_FAIL_MSG("Could not initialize native OpenGL.");
}
layer = [CALayer new];
// OpenGL content is flipped, so it must be transformed.
layer.anchorPoint = CGPointMake(0, 0);
layer.transform = CATransform3DMakeScale(1.0, -1.0, 1.0);
Error err = gl_manager->window_create(window_id_counter, layer, p_resolution.width, p_resolution.height);
if (err != OK) {
ERR_FAIL_MSG("Could not create OpenGL context.");
}
}
#endif
#if defined(RD_ENABLED)
if (rendering_context) {
layer = [CAMetalLayer new];
layer.anchorPoint = CGPointMake(0, 1);
union {
#ifdef VULKAN_ENABLED
RenderingContextDriverVulkanMacOS::WindowPlatformData vulkan;
#endif
#ifdef METAL_ENABLED
RenderingContextDriverMetal::WindowPlatformData metal;
#endif
} wpd;
#ifdef VULKAN_ENABLED
if (rendering_driver == "vulkan") {
wpd.vulkan.layer_ptr = (CAMetalLayer *const *)&layer;
}
#endif
#ifdef METAL_ENABLED
if (rendering_driver == "metal") {
wpd.metal.layer = (CAMetalLayer *)layer;
}
#endif
Error err = rendering_context->window_create(window_id_counter, &wpd);
ERR_FAIL_COND_MSG(err != OK, vformat("Can't create a %s context", rendering_driver));
// The rendering context is always in pixels
rendering_context->window_set_size(window_id_counter, p_resolution.width, p_resolution.height);
rendering_context->window_set_vsync_mode(window_id_counter, p_vsync_mode);
}
#endif
#if defined(GLES3_ENABLED)
if (rendering_driver == "opengl3") {
RasterizerGLES3::make_current(true);
}
if (rendering_driver == "opengl3_angle") {
RasterizerGLES3::make_current(false);
}
#endif
#if defined(RD_ENABLED)
if (rendering_context) {
rendering_device = memnew(RenderingDevice);
rendering_device->initialize(rendering_context, MAIN_WINDOW_ID);
rendering_device->screen_create(MAIN_WINDOW_ID);
RendererCompositorRD::make_current();
}
#endif
constexpr CGFloat CONTENT_SCALE = 2.0;
layer.contentsScale = CONTENT_SCALE;
layer.magnificationFilter = kCAFilterNearest;
layer.minificationFilter = kCAFilterNearest;
layer.opaque = NO; // Never opaque when embedded.
layer.actions = @{ @"contents" : [NSNull null] }; // Disable implicit animations for contents.
// AppKit frames, bounds and positions are always in points.
CGRect bounds = CGRectMake(0, 0, p_resolution.width, p_resolution.height);
bounds = CGRectApplyAffineTransform(bounds, CGAffineTransformMakeScale(1.0 / CONTENT_SCALE, 1.0 / CONTENT_SCALE));
layer.bounds = bounds;
CGSConnectionID connection_id = CGSMainConnectionID();
ca_context = [CAContext contextWithCGSConnection:connection_id options:@{ kCAContextCIFilterBehavior : @"ignore" }];
ca_context.layer = layer;
{
Array arr = { ca_context.contextId };
EngineDebugger::get_singleton()->send_message("game_view:set_context_id", arr);
}
}
DisplayServerEmbedded::~DisplayServerEmbedded() {
if (native_menu) {
memdelete(native_menu);
native_menu = nullptr;
}
EmbeddedDebugger::deinitialize();
#if defined(GLES3_ENABLED)
if (gl_manager) {
memdelete(gl_manager);
gl_manager = nullptr;
}
#endif
#if defined(RD_ENABLED)
if (rendering_device) {
memdelete(rendering_device);
rendering_device = nullptr;
}
if (rendering_context) {
memdelete(rendering_context);
rendering_context = nullptr;
}
#endif
}
DisplayServer *DisplayServerEmbedded::create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t /* p_parent_window */, Error &r_error) {
return memnew(DisplayServerEmbedded(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, r_error));
}
Vector<String> DisplayServerEmbedded::get_rendering_drivers_func() {
Vector<String> drivers;
#if defined(VULKAN_ENABLED)
drivers.push_back("vulkan");
#endif
#if defined(METAL_ENABLED)
drivers.push_back("metal");
#endif
#if defined(GLES3_ENABLED)
drivers.push_back("opengl3");
#endif
return drivers;
}
void DisplayServerEmbedded::register_embedded_driver() {
register_create_function("embedded", create_func, get_rendering_drivers_func);
}
void DisplayServerEmbedded::beep() const {
NSBeep();
}
// MARK: - Mouse
void DisplayServerEmbedded::_mouse_update_mode() {
MouseMode wanted_mouse_mode = mouse_mode_override_enabled
? mouse_mode_override
: mouse_mode_base;
if (wanted_mouse_mode == mouse_mode) {
return;
}
EngineDebugger::get_singleton()->send_message("game_view:mouse_set_mode", { wanted_mouse_mode });
mouse_mode = wanted_mouse_mode;
}
void DisplayServerEmbedded::mouse_set_mode(MouseMode p_mode) {
if (p_mode == mouse_mode_base) {
return;
}
mouse_mode_base = p_mode;
_mouse_update_mode();
}
DisplayServerEmbedded::MouseMode DisplayServerEmbedded::mouse_get_mode() const {
return mouse_mode;
}
void DisplayServerEmbedded::mouse_set_mode_override(MouseMode p_mode) {
ERR_FAIL_INDEX(p_mode, MouseMode::MOUSE_MODE_MAX);
if (p_mode == mouse_mode_override) {
return;
}
mouse_mode_override = p_mode;
_mouse_update_mode();
}
DisplayServer::MouseMode DisplayServerEmbedded::mouse_get_mode_override() const {
return mouse_mode_override;
}
void DisplayServerEmbedded::mouse_set_mode_override_enabled(bool p_override_enabled) {
if (p_override_enabled == mouse_mode_override_enabled) {
return;
}
mouse_mode_override_enabled = p_override_enabled;
_mouse_update_mode();
}
bool DisplayServerEmbedded::mouse_is_mode_override_enabled() const {
return mouse_mode_override_enabled;
}
Point2i DisplayServerEmbedded::mouse_get_position() const {
_THREAD_SAFE_METHOD_
const NSPoint mouse_pos = [NSEvent mouseLocation];
const float scale = screen_get_max_scale();
for (NSScreen *screen in [NSScreen screens]) {
NSRect frame = [screen frame];
if (NSMouseInRect(mouse_pos, frame, NO)) {
Vector2i pos = Vector2i((int)mouse_pos.x, (int)mouse_pos.y);
pos *= scale;
// TODO(sgc): fix this
// pos -= _get_screens_origin();
pos.y *= -1;
return pos;
}
}
return Vector2i();
}
BitField<MouseButtonMask> DisplayServerEmbedded::mouse_get_button_state() const {
BitField<MouseButtonMask> last_button_state = MouseButtonMask::NONE;
NSUInteger buttons = [NSEvent pressedMouseButtons];
if (buttons & (1 << 0)) {
last_button_state.set_flag(MouseButtonMask::LEFT);
}
if (buttons & (1 << 1)) {
last_button_state.set_flag(MouseButtonMask::RIGHT);
}
if (buttons & (1 << 2)) {
last_button_state.set_flag(MouseButtonMask::MIDDLE);
}
if (buttons & (1 << 3)) {
last_button_state.set_flag(MouseButtonMask::MB_XBUTTON1);
}
if (buttons & (1 << 4)) {
last_button_state.set_flag(MouseButtonMask::MB_XBUTTON2);
}
return last_button_state;
}
// MARK: Events
void DisplayServerEmbedded::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
window_resize_callbacks[p_window] = p_callable;
}
void DisplayServerEmbedded::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
window_event_callbacks[p_window] = p_callable;
}
void DisplayServerEmbedded::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
input_event_callbacks[p_window] = p_callable;
}
void DisplayServerEmbedded::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
input_text_callbacks[p_window] = p_callable;
}
void DisplayServerEmbedded::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
// Not supported
}
void DisplayServerEmbedded::joy_add(int p_idx, const String &p_name) {
Joy *joy = joysticks.getptr(p_idx);
if (joy == nullptr) {
joysticks[p_idx] = Joy(p_name);
Input::get_singleton()->joy_connection_changed(p_idx, true, p_name);
}
}
void DisplayServerEmbedded::joy_del(int p_idx) {
if (joysticks.erase(p_idx)) {
Input::get_singleton()->joy_connection_changed(p_idx, false, String());
}
}
void DisplayServerEmbedded::process_events() {
Input *input = Input::get_singleton();
for (KeyValue<int, Joy> &kv : joysticks) {
uint64_t ts = input->get_joy_vibration_timestamp(kv.key);
if (ts > kv.value.timestamp) {
kv.value.timestamp = ts;
Vector2 strength = input->get_joy_vibration_strength(kv.key);
if (strength == Vector2()) {
EngineDebugger::get_singleton()->send_message("game_view:joy_stop", { kv.key });
} else {
float duration = input->get_joy_vibration_duration(kv.key);
EngineDebugger::get_singleton()->send_message("game_view:joy_start", { kv.key, duration, strength });
}
}
}
input->flush_buffered_events();
}
void DisplayServerEmbedded::_dispatch_input_events(const Ref<InputEvent> &p_event) {
Ref<InputEventFromWindow> event_from_window = p_event;
WindowID window_id = INVALID_WINDOW_ID;
if (event_from_window.is_valid()) {
window_id = event_from_window->get_window_id();
}
DisplayServerEmbedded *ds = (DisplayServerEmbedded *)DisplayServer::get_singleton();
ds->send_input_event(p_event, window_id);
}
void DisplayServerEmbedded::send_input_event(const Ref<InputEvent> &p_event, WindowID p_id) const {
if (p_id != INVALID_WINDOW_ID) {
_window_callback(input_event_callbacks[p_id], p_event);
} else {
for (const KeyValue<WindowID, Callable> &E : input_event_callbacks) {
_window_callback(E.value, p_event);
}
}
}
void DisplayServerEmbedded::send_input_text(const String &p_text, WindowID p_id) const {
const Callable *cb = input_text_callbacks.getptr(p_id);
if (cb) {
_window_callback(*cb, p_text);
}
}
void DisplayServerEmbedded::send_window_event(DisplayServer::WindowEvent p_event, WindowID p_id) const {
const Callable *cb = window_event_callbacks.getptr(p_id);
if (cb) {
_window_callback(*cb, int(p_event));
}
}
void DisplayServerEmbedded::_window_callback(const Callable &p_callable, const Variant &p_arg) const {
if (p_callable.is_valid()) {
p_callable.call(p_arg);
}
}
// MARK: -
bool DisplayServerEmbedded::has_feature(Feature p_feature) const {
switch (p_feature) {
#ifndef DISABLE_DEPRECATED
case FEATURE_GLOBAL_MENU: {
return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU));
} break;
#endif
case FEATURE_CURSOR_SHAPE:
case FEATURE_IME:
// case FEATURE_CUSTOM_CURSOR_SHAPE:
// case FEATURE_HIDPI:
// case FEATURE_ICON:
// case FEATURE_MOUSE:
// case FEATURE_MOUSE_WARP:
// case FEATURE_NATIVE_DIALOG:
// case FEATURE_NATIVE_ICON:
// case FEATURE_WINDOW_TRANSPARENCY:
// case FEATURE_CLIPBOARD:
// case FEATURE_KEEP_SCREEN_ON:
// case FEATURE_ORIENTATION:
// case FEATURE_VIRTUAL_KEYBOARD:
// case FEATURE_TEXT_TO_SPEECH:
// case FEATURE_TOUCHSCREEN:
return true;
default:
return false;
}
}
String DisplayServerEmbedded::get_name() const {
return "embedded";
}
int DisplayServerEmbedded::get_screen_count() const {
return 1;
}
int DisplayServerEmbedded::get_primary_screen() const {
return 0;
}
Point2i DisplayServerEmbedded::screen_get_position(int p_screen) const {
return Size2i();
}
Size2i DisplayServerEmbedded::screen_get_size(int p_screen) const {
return window_get_size(MAIN_WINDOW_ID);
}
Rect2i DisplayServerEmbedded::screen_get_usable_rect(int p_screen) const {
return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen));
}
int DisplayServerEmbedded::screen_get_dpi(int p_screen) const {
return 96;
}
float DisplayServerEmbedded::screen_get_refresh_rate(int p_screen) const {
_THREAD_SAFE_METHOD_
p_screen = _get_screen_index(p_screen);
NSArray *screenArray = [NSScreen screens];
if ((NSUInteger)p_screen < [screenArray count]) {
NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription];
const CGDisplayModeRef displayMode = CGDisplayCopyDisplayMode([[description objectForKey:@"NSScreenNumber"] unsignedIntValue]);
const double displayRefreshRate = CGDisplayModeGetRefreshRate(displayMode);
return (float)displayRefreshRate;
}
ERR_PRINT("An error occurred while trying to get the screen refresh rate.");
return SCREEN_REFRESH_RATE_FALLBACK;
}
Vector<DisplayServer::WindowID> DisplayServerEmbedded::get_window_list() const {
Vector<DisplayServer::WindowID> list;
list.push_back(MAIN_WINDOW_ID);
return list;
}
DisplayServer::WindowID DisplayServerEmbedded::get_window_at_screen_position(const Point2i &p_position) const {
return MAIN_WINDOW_ID;
}
void DisplayServerEmbedded::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
window_attached_instance_id[p_window] = p_instance;
}
ObjectID DisplayServerEmbedded::window_get_attached_instance_id(WindowID p_window) const {
return window_attached_instance_id[p_window];
}
void DisplayServerEmbedded::window_set_title(const String &p_title, WindowID p_window) {
// Not supported
}
int DisplayServerEmbedded::window_get_current_screen(WindowID p_window) const {
return SCREEN_OF_MAIN_WINDOW;
}
void DisplayServerEmbedded::window_set_current_screen(int p_screen, WindowID p_window) {
// Not supported
}
Point2i DisplayServerEmbedded::window_get_position(WindowID p_window) const {
return Point2i();
}
Point2i DisplayServerEmbedded::window_get_position_with_decorations(WindowID p_window) const {
return Point2i();
}
void DisplayServerEmbedded::window_set_position(const Point2i &p_position, WindowID p_window) {
// Probably not supported for single window iOS app
}
void DisplayServerEmbedded::window_set_transient(WindowID p_window, WindowID p_parent) {
// Not supported
}
void DisplayServerEmbedded::window_set_max_size(const Size2i p_size, WindowID p_window) {
// Not supported
}
Size2i DisplayServerEmbedded::window_get_max_size(WindowID p_window) const {
return Size2i();
}
void DisplayServerEmbedded::window_set_min_size(const Size2i p_size, WindowID p_window) {
// Not supported
}
Size2i DisplayServerEmbedded::window_get_min_size(WindowID p_window) const {
return Size2i();
}
void DisplayServerEmbedded::window_set_size(const Size2i p_size, WindowID p_window) {
[CATransaction begin];
[CATransaction setDisableActions:YES];
// TODO(sgc): Pass scale as argument from parent process.
constexpr CGFloat CONTENT_SCALE = 2.0;
CGRect bounds = CGRectMake(0, 0, p_size.width, p_size.height);
bounds = CGRectApplyAffineTransform(bounds, CGAffineTransformMakeScale(1.0 / CONTENT_SCALE, 1.0 / CONTENT_SCALE));
layer.bounds = bounds;
#if defined(RD_ENABLED)
if (rendering_context) {
rendering_context->window_set_size(p_window, p_size.width, p_size.height);
}
#endif
#if defined(GLES3_ENABLED)
if (gl_manager) {
gl_manager->window_resize(p_window, p_size.width, p_size.height);
}
#endif
[CATransaction commit];
Callable *cb = window_resize_callbacks.getptr(p_window);
if (cb) {
Variant resize_rect = Rect2i(Point2i(), p_size);
_window_callback(window_resize_callbacks[p_window], resize_rect);
}
}
Size2i DisplayServerEmbedded::window_get_size(WindowID p_window) const {
#if defined(RD_ENABLED)
if (rendering_context) {
RenderingContextDriver::SurfaceID surface = rendering_context->surface_get_from_window(p_window);
ERR_FAIL_COND_V_MSG(surface == 0, Size2i(), "Invalid window ID");
uint32_t width = rendering_context->surface_get_width(surface);
uint32_t height = rendering_context->surface_get_height(surface);
return Size2i(width, height);
}
#endif
#ifdef GLES3_ENABLED
if (gl_manager) {
return gl_manager->window_get_size(p_window);
}
#endif
return Size2i();
}
Size2i DisplayServerEmbedded::window_get_size_with_decorations(WindowID p_window) const {
return window_get_size(p_window);
}
void DisplayServerEmbedded::window_set_mode(WindowMode p_mode, WindowID p_window) {
// Not supported
}
DisplayServer::WindowMode DisplayServerEmbedded::window_get_mode(WindowID p_window) const {
return WindowMode::WINDOW_MODE_WINDOWED;
}
bool DisplayServerEmbedded::window_is_maximize_allowed(WindowID p_window) const {
return false;
}
void DisplayServerEmbedded::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
// Not supported
}
bool DisplayServerEmbedded::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
return false;
}
void DisplayServerEmbedded::window_request_attention(WindowID p_window) {
// Not supported
}
void DisplayServerEmbedded::window_move_to_foreground(WindowID p_window) {
// Not supported
}
bool DisplayServerEmbedded::window_is_focused(WindowID p_window) const {
return true;
}
float DisplayServerEmbedded::screen_get_max_scale() const {
return state.screen_max_scale;
}
bool DisplayServerEmbedded::window_can_draw(WindowID p_window) const {
return true;
}
bool DisplayServerEmbedded::can_any_window_draw() const {
return true;
}
void DisplayServerEmbedded::window_set_ime_active(const bool p_active, WindowID p_window) {
EngineDebugger::get_singleton()->send_message("game_view:window_set_ime_active", { p_active });
}
void DisplayServerEmbedded::window_set_ime_position(const Point2i &p_pos, WindowID p_window) {
if (p_pos == ime_last_position) {
return;
}
EngineDebugger::get_singleton()->send_message("game_view:window_set_ime_position", { p_pos });
ime_last_position = p_pos;
}
void DisplayServerEmbedded::update_state(const Dictionary &p_state) {
state.screen_max_scale = p_state["screen_get_max_scale"];
}
void DisplayServerEmbedded::set_content_scale(float p_scale) {
content_scale = p_scale;
}
void DisplayServerEmbedded::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
// Not supported
}
DisplayServer::VSyncMode DisplayServerEmbedded::window_get_vsync_mode(WindowID p_window) const {
_THREAD_SAFE_METHOD_
#if defined(RD_ENABLED)
if (rendering_context) {
return rendering_context->window_get_vsync_mode(p_window);
}
#endif
return DisplayServer::VSYNC_ENABLED;
}
void DisplayServerEmbedded::update_im_text(const Point2i &p_selection, const String &p_text) {
im_selection = p_selection;
im_text = p_text;
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
}
Point2i DisplayServerEmbedded::ime_get_selection() const {
return im_selection;
}
String DisplayServerEmbedded::ime_get_text() const {
return im_text;
}
void DisplayServerEmbedded::cursor_set_shape(CursorShape p_shape) {
cursor_shape = p_shape;
EngineDebugger::get_singleton()->send_message("game_view:cursor_set_shape", { p_shape });
}
DisplayServer::CursorShape DisplayServerEmbedded::cursor_get_shape() const {
return cursor_shape;
}
void DisplayServerEmbedded::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
WARN_PRINT_ONCE("Custom cursor images are not supported in embedded mode.");
}
void DisplayServerEmbedded::swap_buffers() {
#ifdef GLES3_ENABLED
if (gl_manager) {
gl_manager->swap_buffers();
}
#endif
}

View file

@ -51,6 +51,7 @@
#endif
#endif // RD_ENABLED
#define FontVariation __FontVariation
#define BitMap _QDBitMap // Suppress deprecated QuickDraw definition.
#import <AppKit/AppKit.h>
@ -60,8 +61,18 @@
#import <Foundation/Foundation.h>
#import <IOKit/pwr_mgt/IOPMLib.h>
@class GodotWindow;
@class GodotContentView;
@class GodotWindowDelegate;
@class GodotButtonView;
@class GodotEmbeddedView;
@class CALayerHost;
#undef BitMap
#undef CursorShape
#undef FontVariation
class EmbeddedProcessMacOS;
class DisplayServerMacOS : public DisplayServer {
GDSOFTCLASS(DisplayServerMacOS, DisplayServer);
@ -83,10 +94,10 @@ public:
};
struct WindowData {
id window_delegate;
id window_object;
id window_view;
id window_button_view;
GodotWindowDelegate *window_delegate;
GodotWindow *window_object;
GodotContentView *window_view;
GodotButtonView *window_button_view;
Vector<Vector2> mpath;
@ -241,6 +252,12 @@ private:
void initialize_tts() const;
struct EmbeddedProcessData {
const EmbeddedProcessMacOS *process;
CALayer *layer_host = nil;
};
HashMap<OS::ProcessID, EmbeddedProcessData> embedded_processes;
public:
void menu_callback(id p_sender);
@ -442,6 +459,13 @@ public:
virtual bool get_swap_cancel_ok() override;
virtual void enable_for_stealing_focus(OS::ProcessID pid) override;
#ifdef DEBUG_ENABLED
Error embed_process_update(WindowID p_window, const EmbeddedProcessMacOS *p_process);
#endif
virtual Error request_close_embedded_process(OS::ProcessID p_pid) override;
virtual Error remove_embedded_process(OS::ProcessID p_pid) override;
virtual int keyboard_get_layout_count() const override;
virtual int keyboard_get_current_layout() const override;
virtual void keyboard_set_current_layout(int p_index) override;

View file

@ -30,6 +30,10 @@
#import "display_server_macos.h"
#ifdef DEBUG_ENABLED
#import "editor/embedded_process_macos.h"
#endif
#import "godot_application.h"
#import "godot_application_delegate.h"
#import "godot_button_view.h"
#import "godot_content_view.h"
@ -40,6 +44,7 @@
#import "godot_window.h"
#import "godot_window_delegate.h"
#import "key_mapping_macos.h"
#import "macos_quartz_core_spi.h"
#import "os_macos.h"
#import "tts_macos.h"
@ -140,7 +145,7 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod
[wd.window_object setTabbingMode:NSWindowTabbingModeDisallowed];
}
CALayer *layer = [(NSView *)wd.window_view layer];
CALayer *layer = [wd.window_view layer];
if (layer) {
layer.contentsScale = scale;
}
@ -198,7 +203,6 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod
}
}
if (gl_manager_angle) {
CALayer *layer = [(NSView *)wd.window_view layer];
Error err = gl_manager_angle->window_create(window_id_counter, nullptr, (__bridge void *)layer, p_rect.size.width, p_rect.size.height);
if (err != OK) {
gl_failed = true;
@ -880,6 +884,7 @@ bool DisplayServerMacOS::has_feature(Feature p_feature) const {
case FEATURE_WINDOW_DRAG:
case FEATURE_SCREEN_EXCLUDE_FROM_CAPTURE:
case FEATURE_EMOJI_AND_SYMBOL_PICKER:
case FEATURE_WINDOW_EMBEDDING:
return true;
#ifdef ACCESSKIT_ENABLED
case FEATURE_ACCESSIBILITY_SCREEN_READER: {
@ -1765,11 +1770,10 @@ float DisplayServerMacOS::screen_get_scale(int p_screen) const {
p_screen = _get_screen_index(p_screen);
if (OS::get_singleton()->is_hidpi_allowed()) {
NSArray *screenArray = [NSScreen screens];
if ((NSUInteger)p_screen < [screenArray count]) {
if ([[screenArray objectAtIndex:p_screen] respondsToSelector:@selector(backingScaleFactor)]) {
return std::fmax(1.0, [[screenArray objectAtIndex:p_screen] backingScaleFactor]);
}
NSArray<NSScreen *> *screens = NSScreen.screens;
NSUInteger index = (NSUInteger)p_screen;
if (index < screens.count) {
return std::fmax(1.0f, screens[index].backingScaleFactor);
}
}
@ -2010,8 +2014,8 @@ void DisplayServerMacOS::show_window(WindowID p_id) {
WindowData &wd = windows[p_id];
if (p_id == MAIN_WINDOW_ID) {
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
static_cast<OS_MacOS *>(OS::get_singleton())->activate();
[GodotApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[GodotApp activateApplication];
}
popup_open(p_id);
@ -2033,6 +2037,7 @@ void DisplayServerMacOS::delete_sub_window(WindowID p_id) {
WindowData &wd = windows[p_id];
[wd.window_object setContentView:nil];
// This will cause the delegate to release the window.
[wd.window_object close];
mouse_enter_window(get_window_at_screen_position(mouse_get_position()));
@ -2601,19 +2606,13 @@ bool DisplayServerMacOS::window_is_maximize_allowed(WindowID p_window) const {
}
bool DisplayServerMacOS::window_maximize_on_title_dbl_click() const {
id value = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleActionOnDoubleClick"];
if ([value isKindOfClass:[NSString class]]) {
return [value isEqualToString:@"Maximize"];
}
return false;
NSString *value = [NSUserDefaults.standardUserDefaults stringForKey:@"AppleActionOnDoubleClick"];
return [value isEqualToString:@"Maximize"];
}
bool DisplayServerMacOS::window_minimize_on_title_dbl_click() const {
id value = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleActionOnDoubleClick"];
if ([value isKindOfClass:[NSString class]]) {
return [value isEqualToString:@"Minimize"];
}
return false;
NSString *value = [NSUserDefaults.standardUserDefaults stringForKey:@"AppleActionOnDoubleClick"];
return [value isEqualToString:@"Minimize"];
}
void DisplayServerMacOS::window_start_drag(WindowID p_window) {
@ -2645,7 +2644,7 @@ void DisplayServerMacOS::window_set_window_buttons_offset(const Vector2i &p_offs
wd.wb_offset = p_offset / scale;
wd.wb_offset = wd.wb_offset.maxi(12);
if (wd.window_button_view) {
[(GodotButtonView *)wd.window_button_view setOffset:NSMakePoint(wd.wb_offset.x, wd.wb_offset.y)];
[wd.window_button_view setOffset:NSMakePoint(wd.wb_offset.x, wd.wb_offset.y)];
}
}
@ -3275,6 +3274,75 @@ bool DisplayServerMacOS::get_swap_cancel_ok() {
return false;
}
void DisplayServerMacOS::enable_for_stealing_focus(OS::ProcessID pid) {
}
#define GET_OR_FAIL_V(m_val, m_map, m_key, m_retval) \
m_val = m_map.getptr(m_key); \
if (m_val == nullptr) { \
ERR_FAIL_V(m_retval); \
}
#ifdef DEBUG_ENABLED
Error DisplayServerMacOS::embed_process_update(WindowID p_window, const EmbeddedProcessMacOS *p_process) {
_THREAD_SAFE_METHOD_
WindowData *wd;
GET_OR_FAIL_V(wd, windows, p_window, FAILED);
OS::ProcessID p_pid = p_process->get_embedded_pid();
[CATransaction begin];
[CATransaction setDisableActions:YES];
EmbeddedProcessData *ed = embedded_processes.getptr(p_pid);
if (ed == nil) {
ed = &embedded_processes.insert(p_pid, EmbeddedProcessData())->value;
ed->process = p_process;
CALayerHost *host = [CALayerHost new];
uint32_t p_context_id = p_process->get_context_id();
host.contextId = static_cast<CAContextID>(p_context_id);
host.contentsScale = wd->window_object.backingScaleFactor;
host.contentsGravity = kCAGravityCenter;
ed->layer_host = host;
[wd->window_view.layer addSublayer:host];
}
Rect2i p_rect = p_process->get_screen_embedded_window_rect();
CGRect rect = CGRectMake(p_rect.position.x, p_rect.position.y, p_rect.size.x, p_rect.size.y);
rect = CGRectApplyAffineTransform(rect, CGAffineTransformMakeScale(0.5, 0.5));
CGFloat height = wd->window_view.frame.size.height;
CGFloat x = rect.origin.x;
CGFloat y = (height - rect.origin.y);
ed->layer_host.position = CGPointMake(x, y);
ed->layer_host.hidden = !p_process->is_visible_in_tree();
[CATransaction commit];
return OK;
}
#endif
Error DisplayServerMacOS::request_close_embedded_process(OS::ProcessID p_pid) {
return OK;
}
Error DisplayServerMacOS::remove_embedded_process(OS::ProcessID p_pid) {
_THREAD_SAFE_METHOD_
EmbeddedProcessData *ed;
GET_OR_FAIL_V(ed, embedded_processes, p_pid, ERR_DOES_NOT_EXIST);
[ed->layer_host removeFromSuperlayer];
embedded_processes.erase(p_pid);
return OK;
}
int DisplayServerMacOS::keyboard_get_layout_count() const {
if (keyboard_layout_dirty) {
_update_keyboard_layouts();

View file

@ -0,0 +1,75 @@
/**************************************************************************/
/* embedded_game_view_plugin.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "editor/plugins/game_view_plugin.h"
class EmbeddedProcessMacOS;
class GameViewDebuggerMacOS : public GameViewDebugger {
GDCLASS(GameViewDebuggerMacOS, GameViewDebugger);
EmbeddedProcessMacOS *embedded_process = nullptr;
/// Message handler function for capture.
/// @brief A function pointer to the message handler function.
typedef bool (GameViewDebuggerMacOS::*ParseMessageFunc)(const Array &p_args);
/// @brief A map of message handlers.
static HashMap<String, ParseMessageFunc> parse_message_handlers;
/// @brief Initialize the message handlers.
static void _init_capture_message_handlers();
bool _msg_set_context_id(const Array &p_args);
bool _msg_cursor_set_shape(const Array &p_args);
bool _msg_mouse_set_mode(const Array &p_args);
bool _msg_window_set_ime_active(const Array &p_args);
bool _msg_window_set_ime_position(const Array &p_args);
bool _msg_joy_start(const Array &p_args);
bool _msg_joy_stop(const Array &p_args);
public:
virtual bool capture(const String &p_message, const Array &p_data, int p_session) override;
virtual bool has_capture(const String &p_capture) const override;
GameViewDebuggerMacOS(EmbeddedProcessMacOS *p_embedded_process);
};
class GameViewPluginMacOS : public GameViewPluginBase {
GDCLASS(GameViewPluginMacOS, GameViewPluginBase);
public:
GameViewPluginMacOS();
};
extern "C" void register_game_view_plugin();

View file

@ -0,0 +1,154 @@
/**************************************************************************/
/* embedded_game_view_plugin.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "embedded_game_view_plugin.h"
#include "embedded_process_macos.h"
#include "editor/editor_node.h"
#include "editor/window_wrapper.h"
HashMap<String, GameViewDebuggerMacOS::ParseMessageFunc> GameViewDebuggerMacOS::parse_message_handlers;
bool GameViewDebuggerMacOS::_msg_set_context_id(const Array &p_args) {
ERR_FAIL_COND_V_MSG(p_args.size() != 1, false, "set_context_id: invalid number of arguments");
embedded_process->set_context_id(p_args[0]);
return true;
}
bool GameViewDebuggerMacOS::_msg_cursor_set_shape(const Array &p_args) {
ERR_FAIL_COND_V_MSG(p_args.size() != 1, false, "cursor_set_shape: invalid number of arguments");
Control::CursorShape shape = Control::CursorShape(p_args[0]);
embedded_process->get_layer_host()->set_default_cursor_shape(static_cast<Control::CursorShape>(shape));
return true;
}
bool GameViewDebuggerMacOS::_msg_mouse_set_mode(const Array &p_args) {
ERR_FAIL_COND_V_MSG(p_args.size() != 1, false, "mouse_set_mode: invalid number of arguments");
DisplayServer::MouseMode mode = DisplayServer::MouseMode(p_args[0]);
embedded_process->mouse_set_mode(mode);
return true;
}
bool GameViewDebuggerMacOS::_msg_window_set_ime_active(const Array &p_args) {
ERR_FAIL_COND_V_MSG(p_args.size() != 1, false, "window_set_ime_active: invalid number of arguments");
bool active = p_args[0];
DisplayServer::WindowID wid = embedded_process->get_window()->get_window_id();
DisplayServer::get_singleton()->window_set_ime_active(active, wid);
return true;
}
bool GameViewDebuggerMacOS::_msg_window_set_ime_position(const Array &p_args) {
ERR_FAIL_COND_V_MSG(p_args.size() != 1, false, "window_set_ime_position: invalid number of arguments");
Point2i pos = p_args[0];
Point2i xpos = embedded_process->get_layer_host()->get_global_transform_with_canvas().xform(pos);
DisplayServer::WindowID wid = embedded_process->get_window()->get_window_id();
DisplayServer::get_singleton()->window_set_ime_position(xpos, wid);
return true;
}
bool GameViewDebuggerMacOS::_msg_joy_start(const Array &p_args) {
ERR_FAIL_COND_V_MSG(p_args.size() != 3, false, "joy_start: invalid number of arguments");
int joy_id = p_args[0];
float duration = p_args[1];
Vector2 strength = p_args[2];
Input::get_singleton()->start_joy_vibration(joy_id, strength.x, strength.y, duration);
return true;
}
bool GameViewDebuggerMacOS::_msg_joy_stop(const Array &p_args) {
ERR_FAIL_COND_V_MSG(p_args.size() != 1, false, "joy_stop: invalid number of arguments");
int joy_id = p_args[0];
Input::get_singleton()->stop_joy_vibration(joy_id);
return true;
}
void GameViewDebuggerMacOS::_init_capture_message_handlers() {
parse_message_handlers["game_view:set_context_id"] = &GameViewDebuggerMacOS::_msg_set_context_id;
parse_message_handlers["game_view:cursor_set_shape"] = &GameViewDebuggerMacOS::_msg_cursor_set_shape;
parse_message_handlers["game_view:mouse_set_mode"] = &GameViewDebuggerMacOS::_msg_mouse_set_mode;
parse_message_handlers["game_view:window_set_ime_active"] = &GameViewDebuggerMacOS::_msg_window_set_ime_active;
parse_message_handlers["game_view:window_set_ime_position"] = &GameViewDebuggerMacOS::_msg_window_set_ime_position;
parse_message_handlers["game_view:joy_start"] = &GameViewDebuggerMacOS::_msg_joy_start;
parse_message_handlers["game_view:joy_stop"] = &GameViewDebuggerMacOS::_msg_joy_stop;
}
bool GameViewDebuggerMacOS::has_capture(const String &p_capture) const {
return p_capture == "game_view";
}
bool GameViewDebuggerMacOS::capture(const String &p_message, const Array &p_data, int p_session) {
Ref<EditorDebuggerSession> session = get_session(p_session);
ERR_FAIL_COND_V(session.is_null(), true);
ParseMessageFunc *fn_ptr = parse_message_handlers.getptr(p_message);
if (fn_ptr) {
return (this->**fn_ptr)(p_data);
} else {
// Any other messages with this prefix should be ignored.
WARN_PRINT("GameViewDebuggerMacOS unknown message: " + p_message);
return ERR_SKIP;
}
return true;
}
GameViewDebuggerMacOS::GameViewDebuggerMacOS(EmbeddedProcessMacOS *p_embedded_process) :
embedded_process(p_embedded_process) {
if (parse_message_handlers.is_empty()) {
_init_capture_message_handlers();
}
}
GameViewPluginMacOS::GameViewPluginMacOS() {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return;
}
EmbeddedProcessMacOS *embedded_process = memnew(EmbeddedProcessMacOS);
Ref<GameViewDebuggerMacOS> debugger;
debugger.instantiate(embedded_process);
setup(debugger, embedded_process);
}
extern "C" GameViewPluginBase *get_game_view_plugin() {
return memnew(GameViewPluginMacOS);
}

View file

@ -0,0 +1,110 @@
/**************************************************************************/
/* embedded_process_macos.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "editor/plugins/embedded_process.h"
class DisplayServerMacOS;
class LayerHost : public Control {
GDCLASS(LayerHost, Control);
ScriptEditorDebugger *script_debugger = nullptr;
virtual void gui_input(const Ref<InputEvent> &p_event) override;
protected:
void _notification(int p_what);
public:
void set_script_debugger(ScriptEditorDebugger *p_debugger) {
script_debugger = p_debugger;
}
};
class EmbeddedProcessMacOS final : public EmbeddedProcessBase {
GDCLASS(EmbeddedProcessMacOS, EmbeddedProcessBase);
enum class EmbeddingState {
IDLE,
IN_PROGRESS,
COMPLETED,
FAILED,
};
DisplayServerMacOS *ds = nullptr;
EmbeddingState embedding_state = EmbeddingState::IDLE;
uint32_t context_id = 0;
ScriptEditorDebugger *script_debugger = nullptr;
LayerHost *layer_host = nullptr;
OS::ProcessID current_process_id = 0;
// Embedded process state.
/// @brief The current mouse mode of the embedded process.
DisplayServer::MouseMode mouse_mode = DisplayServer::MOUSE_MODE_VISIBLE;
void _try_embed_process();
void update_embedded_process() const;
void _joy_connection_changed(int p_index, bool p_connected) const;
protected:
void _notification(int p_what);
public:
// MARK: - Message Handlers
void set_context_id(uint32_t p_context_id);
uint32_t get_context_id() const { return context_id; }
void set_script_debugger(ScriptEditorDebugger *p_debugger) override;
bool is_embedding_in_progress() const override {
return embedding_state == EmbeddingState::IN_PROGRESS;
}
bool is_embedding_completed() const override {
return embedding_state == EmbeddingState::COMPLETED;
}
virtual bool is_process_focused() const override { return layer_host->has_focus(); }
virtual void embed_process(OS::ProcessID p_pid) override;
virtual int get_embedded_pid() const override { return current_process_id; }
virtual void reset() override;
virtual void request_close() override;
virtual void queue_update_embedded_process() override { update_embedded_process(); }
Rect2i get_adjusted_embedded_window_rect(const Rect2i &p_rect) const override;
void mouse_set_mode(DisplayServer::MouseMode p_mode);
_FORCE_INLINE_ LayerHost *get_layer_host() const { return layer_host; }
EmbeddedProcessMacOS();
};

View file

@ -0,0 +1,248 @@
/**************************************************************************/
/* embedded_process_macos.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "embedded_process_macos.h"
#include "platform/macos/display_server_macos.h"
#include "core/input/input_event_codec.h"
#include "editor/debugger/script_editor_debugger.h"
#include "editor/editor_settings.h"
#include "scene/gui/control.h"
#include "scene/main/window.h"
void EmbeddedProcessMacOS::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_RESIZED:
case NOTIFICATION_VISIBILITY_CHANGED: {
update_embedded_process();
} break;
}
}
void EmbeddedProcessMacOS::update_embedded_process() const {
layer_host->set_rect(get_adjusted_embedded_window_rect(get_rect()));
if (is_embedding_completed()) {
ds->embed_process_update(window->get_window_id(), this);
Rect2i rect = get_screen_embedded_window_rect();
script_debugger->send_message("embed:window_size", { rect.size });
}
}
void EmbeddedProcessMacOS::set_context_id(uint32_t p_context_id) {
if (!window) {
return;
}
context_id = p_context_id;
_try_embed_process();
}
void EmbeddedProcessMacOS::set_script_debugger(ScriptEditorDebugger *p_debugger) {
script_debugger = p_debugger;
layer_host->set_script_debugger(script_debugger);
_try_embed_process();
}
void EmbeddedProcessMacOS::embed_process(OS::ProcessID p_pid) {
if (!window) {
return;
}
if (current_process_id != 0) {
// Stop embedding the last process.
OS::get_singleton()->kill(current_process_id);
}
reset();
current_process_id = p_pid;
embedding_state = EmbeddingState::IN_PROGRESS;
// Attempt to embed the process, but if it has just started and the window is not ready yet,
// we will retry in this case.
_try_embed_process();
}
void EmbeddedProcessMacOS::_joy_connection_changed(int p_index, bool p_connected) const {
if (!script_debugger) {
return;
}
if (p_connected) {
String name = Input::get_singleton()->get_joy_name(p_index);
script_debugger->send_message("embed:joy_add", { p_index, name });
} else {
script_debugger->send_message("embed:joy_del", { p_index });
}
}
void EmbeddedProcessMacOS::reset() {
if (!ds) {
ds = static_cast<DisplayServerMacOS *>(DisplayServer::get_singleton());
}
if (current_process_id != 0 && is_embedding_completed()) {
ds->remove_embedded_process(current_process_id);
}
current_process_id = 0;
embedding_state = EmbeddingState::IDLE;
context_id = 0;
script_debugger = nullptr;
queue_redraw();
}
void EmbeddedProcessMacOS::request_close() {
if (current_process_id != 0 && is_embedding_completed()) {
ds->request_close_embedded_process(current_process_id);
}
}
void EmbeddedProcessMacOS::_try_embed_process() {
if (current_process_id == 0 || script_debugger == nullptr || context_id == 0) {
return;
}
Error err = ds->embed_process_update(window->get_window_id(), this);
if (err == OK) {
Rect2i rect = get_screen_embedded_window_rect();
script_debugger->send_message("embed:window_size", { rect.size });
embedding_state = EmbeddingState::COMPLETED;
queue_redraw();
emit_signal(SNAME("embedding_completed"));
// Replicate some of the DisplayServer state.
{
Dictionary state;
state["screen_get_max_scale"] = ds->screen_get_max_scale();
// script_debugger->send_message("embed:ds_state", { state });
}
// Send initial joystick state.
{
Input *input = Input::get_singleton();
TypedArray<int> joy_pads = input->get_connected_joypads();
for (const Variant &idx : joy_pads) {
String name = input->get_joy_name(idx);
script_debugger->send_message("embed:joy_add", { idx, name });
}
}
layer_host->grab_focus();
} else {
// Another unknown error.
reset();
emit_signal(SNAME("embedding_failed"));
}
}
Rect2i EmbeddedProcessMacOS::get_adjusted_embedded_window_rect(const Rect2i &p_rect) const {
Rect2i control_rect = Rect2i(p_rect.position + margin_top_left, (p_rect.size - get_margins_size()).maxi(1));
if (window_size != Size2i()) {
Rect2i desired_rect;
if (!keep_aspect && control_rect.size.x >= window_size.x && control_rect.size.y >= window_size.y) {
// Fixed at the desired size.
desired_rect.size = window_size;
} else {
float ratio = MIN((float)control_rect.size.x / window_size.x, (float)control_rect.size.y / window_size.y);
desired_rect.size = Size2i(window_size.x * ratio, window_size.y * ratio).maxi(1);
}
desired_rect.position = Size2i(control_rect.position.x + ((control_rect.size.x - desired_rect.size.x) / 2), control_rect.position.y + ((control_rect.size.y - desired_rect.size.y) / 2));
return desired_rect;
} else {
// Stretch, use all the control area.
return control_rect;
}
}
void EmbeddedProcessMacOS::mouse_set_mode(DisplayServer::MouseMode p_mode) {
DisplayServer::get_singleton()->mouse_set_mode(p_mode);
}
EmbeddedProcessMacOS::EmbeddedProcessMacOS() :
EmbeddedProcessBase() {
layer_host = memnew(LayerHost);
add_child(layer_host);
layer_host->set_focus_mode(FOCUS_ALL);
layer_host->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
layer_host->set_custom_minimum_size(Size2(100, 100));
Input *input = Input::get_singleton();
input->connect(SNAME("joy_connection_changed"), callable_mp(this, &EmbeddedProcessMacOS::_joy_connection_changed));
// This shortcut allows a user to forcibly release a captured mouse from within the editor, regardless of whether
// the embedded process has implemented support to release the cursor.
ED_SHORTCUT("game_view/release_mouse", TTRC("Release Mouse"), KeyModifierMask::ALT | Key::ESCAPE);
}
void LayerHost::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_FOCUS_ENTER: {
if (script_debugger) {
script_debugger->send_message("embed:win_event", { DisplayServer::WINDOW_EVENT_MOUSE_ENTER });
}
} break;
case NOTIFICATION_FOCUS_EXIT: {
if (script_debugger) {
script_debugger->send_message("embed:win_event", { DisplayServer::WINDOW_EVENT_MOUSE_EXIT });
}
} break;
case MainLoop::NOTIFICATION_OS_IME_UPDATE: {
if (script_debugger && has_focus()) {
const String ime_text = DisplayServer::get_singleton()->ime_get_text();
const Vector2i ime_selection = DisplayServer::get_singleton()->ime_get_selection();
script_debugger->send_message("embed:ime_update", { ime_text, ime_selection });
}
} break;
}
}
void LayerHost::gui_input(const Ref<InputEvent> &p_event) {
if (!script_debugger) {
return;
}
if (p_event->is_pressed()) {
if (ED_IS_SHORTCUT("game_view/release_mouse", p_event)) {
DisplayServer *ds = DisplayServer::get_singleton();
if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_VISIBLE) {
ds->mouse_set_mode(DisplayServer::MOUSE_MODE_VISIBLE);
script_debugger->send_message("embed:mouse_set_mode", { DisplayServer::MOUSE_MODE_VISIBLE });
}
accept_event();
return;
}
}
PackedByteArray data;
if (encode_input_event(p_event, data)) {
script_debugger->send_message("embed:event", { data });
accept_event();
}
}

View file

@ -0,0 +1,70 @@
/**************************************************************************/
/* embedded_debugger.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/templates/hash_map.h"
#include "core/variant/array.h"
class DisplayServerEmbedded;
/// @brief Singleton class to process embedded debugging message in the child process.
class EmbeddedDebugger {
inline static EmbeddedDebugger *singleton = nullptr;
EmbeddedDebugger(DisplayServerEmbedded *p_ds);
public:
static void initialize(DisplayServerEmbedded *p_ds);
static void deinitialize();
~EmbeddedDebugger();
#ifdef DEBUG_ENABLED
private:
DisplayServerEmbedded *ds;
/// Message handler function for parse_message.
typedef Error (EmbeddedDebugger::*ParseMessageFunc)(const Array &p_args);
static HashMap<String, ParseMessageFunc> parse_message_handlers;
static void _init_parse_message_handlers();
Error _msg_window_size(const Array &p_args);
Error _msg_mouse_set_mode(const Array &p_args);
Error _msg_event(const Array &p_args);
Error _msg_win_event(const Array &p_args);
Error _msg_ime_update(const Array &p_args);
Error _msg_joy_add(const Array &p_args);
Error _msg_joy_del(const Array &p_args);
public:
static Error parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured);
#endif
};

View file

@ -0,0 +1,177 @@
/**************************************************************************/
/* embedded_debugger.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "embedded_debugger.h"
#include "display_server_embedded.h"
#include "core/debugger/engine_debugger.h"
#include "core/input/input_event_codec.h"
#ifdef DEBUG_ENABLED
HashMap<String, EmbeddedDebugger::ParseMessageFunc> EmbeddedDebugger::parse_message_handlers;
#endif
EmbeddedDebugger::EmbeddedDebugger(DisplayServerEmbedded *p_ds) {
singleton = this;
#ifdef DEBUG_ENABLED
ds = p_ds;
if (parse_message_handlers.is_empty()) {
_init_parse_message_handlers();
}
EngineDebugger::register_message_capture("embed", EngineDebugger::Capture(this, EmbeddedDebugger::parse_message));
#endif
}
EmbeddedDebugger::~EmbeddedDebugger() {
singleton = nullptr;
}
void EmbeddedDebugger::initialize(DisplayServerEmbedded *p_ds) {
if (EngineDebugger::is_active()) {
memnew(EmbeddedDebugger(p_ds));
}
}
void EmbeddedDebugger::deinitialize() {
if (singleton) {
memdelete(singleton);
}
}
#ifdef DEBUG_ENABLED
void EmbeddedDebugger::_init_parse_message_handlers() {
parse_message_handlers["window_size"] = &EmbeddedDebugger::_msg_window_size;
parse_message_handlers["mouse_set_mode"] = &EmbeddedDebugger::_msg_mouse_set_mode;
parse_message_handlers["event"] = &EmbeddedDebugger::_msg_event;
parse_message_handlers["win_event"] = &EmbeddedDebugger::_msg_win_event;
parse_message_handlers["ime_update"] = &EmbeddedDebugger::_msg_ime_update;
parse_message_handlers["joy_add"] = &EmbeddedDebugger::_msg_joy_add;
parse_message_handlers["joy_del"] = &EmbeddedDebugger::_msg_joy_del;
}
Error EmbeddedDebugger::_msg_window_size(const Array &p_args) {
Size2i size = p_args[0];
ds->window_set_size(size);
return OK;
}
Error EmbeddedDebugger::_msg_mouse_set_mode(const Array &p_args) {
DisplayServer::MouseMode mode = p_args[0];
ds->mouse_set_mode(mode);
return OK;
}
Error EmbeddedDebugger::_msg_event(const Array &p_args) {
Input *input = Input::get_singleton();
if (!input) {
// Ignore if we've received an event before the process has initialized.
return OK;
}
PackedByteArray data = p_args[0];
Ref<InputEvent> event;
decode_input_event(data, event);
{
Ref<InputEventMouse> e = event;
if (e.is_valid()) {
input->set_mouse_position(e->get_position());
}
}
{
Ref<InputEventMagnifyGesture> e = event;
if (e.is_valid()) {
input->set_mouse_position(e->get_position());
}
}
{
Ref<InputEventPanGesture> e = event;
if (e.is_valid()) {
input->set_mouse_position(e->get_position());
}
}
if (event.is_valid()) {
input->parse_input_event(event);
}
return OK;
}
Error EmbeddedDebugger::_msg_win_event(const Array &p_args) {
DisplayServer::WindowEvent win_event = p_args[0];
ds->send_window_event(win_event, DisplayServer::MAIN_WINDOW_ID);
if (win_event == DisplayServer::WindowEvent::WINDOW_EVENT_MOUSE_EXIT) {
Input::get_singleton()->release_pressed_events();
}
return OK;
}
Error EmbeddedDebugger::_msg_ime_update(const Array &p_args) {
ERR_FAIL_COND_V_MSG(p_args.size() != 2, ERR_INVALID_PARAMETER, "Invalid number of arguments for 'ime_update' message.");
String ime_text = p_args[0];
Vector2i ime_selection = p_args[1];
ds->update_im_text(ime_selection, ime_text);
return OK;
}
Error EmbeddedDebugger::_msg_joy_add(const Array &p_args) {
ERR_FAIL_COND_V_MSG(p_args.size() != 2, ERR_INVALID_PARAMETER, "Invalid number of arguments for 'joy_add' message.");
int idx = p_args[0];
String name = p_args[1];
ds->joy_add(idx, name);
return OK;
}
Error EmbeddedDebugger::_msg_joy_del(const Array &p_args) {
ERR_FAIL_COND_V_MSG(p_args.size() != 1, ERR_INVALID_PARAMETER, "Invalid number of arguments for 'joy_del' message.");
int idx = p_args[0];
ds->joy_del(idx);
return OK;
}
Error EmbeddedDebugger::parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured) {
EmbeddedDebugger *self = static_cast<EmbeddedDebugger *>(p_user);
r_captured = true;
ParseMessageFunc *fn_ptr = parse_message_handlers.getptr(p_msg);
if (fn_ptr) {
return (self->**fn_ptr)(p_args);
} else {
// Any other messages with this prefix should be ignored.
WARN_PRINT("Unknown message: " + p_msg);
return ERR_SKIP;
}
}
#endif

View file

@ -0,0 +1,108 @@
/**************************************************************************/
/* embedded_gl_manager.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#if defined(MACOS_ENABLED) && defined(GLES3_ENABLED)
#include "core/os/os.h"
#include "core/templates/local_vector.h"
#include "servers/display_server.h"
#import <AppKit/AppKit.h>
#import <ApplicationServices/ApplicationServices.h>
#import <CoreVideo/CoreVideo.h>
GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wdeprecated-declarations") // OpenGL is deprecated in macOS 10.14.
typedef CGLContextObj (*CGLGetCurrentContextPtr)(void);
typedef CGLError (*CGLTexImageIOSurface2DPtr)(CGLContextObj ctx, GLenum target, GLenum internal_format,
GLsizei width, GLsizei height, GLenum format, GLenum type, IOSurfaceRef ioSurface, GLuint plane);
typedef const char *(*CGLErrorStringPtr)(CGLError);
class GLManagerEmbedded {
/// @brief The number of framebuffers to create for each window.
///
/// Triple-buffering is used to avoid stuttering.
static constexpr uint32_t BUFFER_COUNT = 3;
struct FrameBuffer {
IOSurfaceRef surface = nullptr;
unsigned int tex = 0;
unsigned int fbo = 0;
};
struct GLWindow {
uint32_t width = 0;
uint32_t height = 0;
CALayer *layer = nullptr;
NSOpenGLContext *context = nullptr;
FrameBuffer framebuffers[BUFFER_COUNT];
uint32_t current_fb = 0;
bool is_valid = false;
void destroy_framebuffers();
~GLWindow() { destroy_framebuffers(); }
};
RBMap<DisplayServer::WindowID, GLWindow> windows;
typedef RBMap<DisplayServer::WindowID, GLWindow>::Element GLWindowElement;
NSOpenGLContext *shared_context = nullptr;
DisplayServer::WindowID current_window = DisplayServer::INVALID_WINDOW_ID;
Error create_context(GLWindow &p_win);
bool framework_loaded = false;
CGLGetCurrentContextPtr CGLGetCurrentContext = nullptr;
CGLTexImageIOSurface2DPtr CGLTexImageIOSurface2D = nullptr;
CGLErrorStringPtr CGLErrorString = nullptr;
public:
Error window_create(DisplayServer::WindowID p_window_id, CALayer *p_layer, int p_width, int p_height);
void window_destroy(DisplayServer::WindowID p_window_id);
void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height);
Size2i window_get_size(DisplayServer::WindowID p_window_id) const;
void release_current();
void swap_buffers();
void window_make_current(DisplayServer::WindowID p_window_id);
Error initialize();
GLManagerEmbedded();
~GLManagerEmbedded();
};
GODOT_CLANG_WARNING_PUSH
#endif // MACOS_ENABLED && GLES3_ENABLED

View file

@ -0,0 +1,271 @@
/**************************************************************************/
/* embedded_gl_manager.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "embedded_gl_manager.h"
#import "drivers/gles3/storage/texture_storage.h"
#import "platform_gl.h"
#if defined(MACOS_ENABLED) && defined(GLES3_ENABLED)
#import <QuartzCore/QuartzCore.h>
#include <dlfcn.h>
GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wdeprecated-declarations") // OpenGL is deprecated in macOS 10.14.
Error GLManagerEmbedded::create_context(GLWindow &p_win) {
NSOpenGLPixelFormatAttribute attributes[] = {
NSOpenGLPFADoubleBuffer,
NSOpenGLPFAClosestPolicy,
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
NSOpenGLPFAColorSize, 32,
NSOpenGLPFADepthSize, 24,
NSOpenGLPFAStencilSize, 8,
0
};
NSOpenGLPixelFormat *pixel_format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
ERR_FAIL_NULL_V(pixel_format, ERR_CANT_CREATE);
p_win.context = [[NSOpenGLContext alloc] initWithFormat:pixel_format shareContext:shared_context];
ERR_FAIL_NULL_V(p_win.context, ERR_CANT_CREATE);
if (shared_context == nullptr) {
shared_context = p_win.context;
}
[p_win.context makeCurrentContext];
return OK;
}
Error GLManagerEmbedded::window_create(DisplayServer::WindowID p_window_id, CALayer *p_layer, int p_width, int p_height) {
GLWindow win;
win.layer = p_layer;
win.width = 0;
win.height = 0;
if (create_context(win) != OK) {
return FAILED;
}
windows[p_window_id] = win;
window_make_current(p_window_id);
return OK;
}
void GLManagerEmbedded::window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) {
GLWindowElement *el = windows.find(p_window_id);
ERR_FAIL_NULL_MSG(el, "Window resize failed: window does not exist.");
GLWindow &win = el->get();
if (win.width == (uint32_t)p_width && win.height == (uint32_t)p_height) {
return;
}
win.width = (uint32_t)p_width;
win.height = (uint32_t)p_height;
win.destroy_framebuffers();
for (FrameBuffer &fb : win.framebuffers) {
NSDictionary *surfaceProps = @{
(NSString *)kIOSurfaceWidth : @(p_width),
(NSString *)kIOSurfaceHeight : @(p_height),
(NSString *)kIOSurfaceBytesPerElement : @(4),
(NSString *)kIOSurfacePixelFormat : @(kCVPixelFormatType_32BGRA),
};
fb.surface = IOSurfaceCreate((__bridge CFDictionaryRef)surfaceProps);
if (fb.surface == nullptr) {
ERR_PRINT(vformat("Failed to create IOSurface: width=%d, height=%d", p_width, p_height));
win.destroy_framebuffers();
return;
}
glGenTextures(1, &fb.tex);
glBindTexture(GL_TEXTURE_RECTANGLE, fb.tex);
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
CGLError err = CGLTexImageIOSurface2D(CGLGetCurrentContext(),
GL_TEXTURE_RECTANGLE,
GL_RGBA,
p_width,
p_height,
GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV,
fb.surface,
0);
if (err != kCGLNoError) {
String err_string = String(CGLErrorString(err));
ERR_PRINT(vformat("CGLTexImageIOSurface2D failed (%d): %s", err, err_string));
win.destroy_framebuffers();
return;
}
glGenFramebuffers(1, &fb.fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fb.fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, fb.tex, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
ERR_PRINT("Unable to create framebuffer from IOSurface texture.");
win.destroy_framebuffers();
return;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_RECTANGLE, 0);
}
win.current_fb = 0;
GLES3::TextureStorage::system_fbo = win.framebuffers[win.current_fb].fbo;
win.is_valid = true;
}
void GLManagerEmbedded::GLWindow::destroy_framebuffers() {
is_valid = false;
GLES3::TextureStorage::system_fbo = 0;
for (FrameBuffer &fb : framebuffers) {
if (fb.fbo) {
glDeleteFramebuffers(1, &fb.fbo);
fb.fbo = 0;
}
if (fb.tex) {
glDeleteTextures(1, &fb.tex);
fb.tex = 0;
}
if (fb.surface) {
IOSurfaceRef old_surface = fb.surface;
fb.surface = nullptr;
CFRelease(old_surface);
}
}
}
Size2i GLManagerEmbedded::window_get_size(DisplayServer::WindowID p_window_id) const {
const GLWindowElement *el = windows.find(p_window_id);
if (el == nullptr) {
return Size2i();
}
const GLWindow &win = el->value();
return Size2i(win.width, win.height);
}
void GLManagerEmbedded::window_destroy(DisplayServer::WindowID p_window_id) {
GLWindowElement *el = windows.find(p_window_id);
if (el == nullptr) {
return;
}
if (current_window == p_window_id) {
current_window = DisplayServer::INVALID_WINDOW_ID;
}
windows.erase(el);
}
void GLManagerEmbedded::release_current() {
if (current_window == DisplayServer::INVALID_WINDOW_ID) {
return;
}
[NSOpenGLContext clearCurrentContext];
current_window = DisplayServer::INVALID_WINDOW_ID;
}
void GLManagerEmbedded::window_make_current(DisplayServer::WindowID p_window_id) {
if (current_window == p_window_id) {
return;
}
const GLWindowElement *el = windows.find(p_window_id);
if (el == nullptr) {
return;
}
const GLWindow &win = el->value();
[win.context makeCurrentContext];
current_window = p_window_id;
}
void GLManagerEmbedded::swap_buffers() {
GLWindow &win = windows[current_window];
[win.context flushBuffer];
static bool last_valid = false;
if (!win.is_valid) {
if (last_valid) {
ERR_PRINT("GLWindow framebuffers are invalid.");
last_valid = false;
}
return;
}
last_valid = true;
[CATransaction begin];
[CATransaction setDisableActions:YES];
win.layer.contents = (__bridge id)win.framebuffers[win.current_fb].surface;
[CATransaction commit];
win.current_fb = (win.current_fb + 1) % BUFFER_COUNT;
GLES3::TextureStorage::system_fbo = win.framebuffers[win.current_fb].fbo;
}
Error GLManagerEmbedded::initialize() {
return framework_loaded ? OK : ERR_CANT_CREATE;
}
GLManagerEmbedded::GLManagerEmbedded() {
NSBundle *framework = [NSBundle bundleWithIdentifier:@"com.apple.opengl"];
if ([framework load]) {
void *library_handle = dlopen([framework.executablePath UTF8String], RTLD_NOW);
if (library_handle) {
CGLGetCurrentContext = (CGLGetCurrentContextPtr)dlsym(library_handle, "CGLGetCurrentContext");
CGLTexImageIOSurface2D = (CGLTexImageIOSurface2DPtr)dlsym(library_handle, "CGLTexImageIOSurface2D");
CGLErrorString = (CGLErrorStringPtr)dlsym(library_handle, "CGLErrorString");
framework_loaded = CGLGetCurrentContext && CGLTexImageIOSurface2D && CGLErrorString;
}
}
}
GLManagerEmbedded::~GLManagerEmbedded() {
release_current();
}
GODOT_CLANG_WARNING_POP
#endif // MACOS_ENABLED && GLES3_ENABLED

View file

@ -37,10 +37,17 @@
#include "drivers/egl/egl_manager.h"
#include "servers/display_server.h"
// Suppress redefinition conflicts
#define FontVariation __FontVariation
#define BitMap __BitMap
#import <AppKit/AppKit.h>
#import <ApplicationServices/ApplicationServices.h>
#import <CoreVideo/CoreVideo.h>
#undef BitMap
#undef FontVariation
class GLManagerANGLE_MacOS : public EGLManager {
private:
virtual const char *_get_platform_extension_name() const override;

View file

@ -36,5 +36,15 @@
#import <Foundation/Foundation.h>
#import <IOKit/hidsystem/ev_keymap.h>
@class GodotApplicationDelegate;
@interface GodotApplication : NSApplication
extern "C" GodotApplication *GodotApp;
@property(readonly, nonatomic) GodotApplicationDelegate *godotDelegate;
- (GodotApplication *)init;
- (void)activateApplication;
@end

View file

@ -31,9 +31,46 @@
#import "godot_application.h"
#import "display_server_macos.h"
#import "godot_application_delegate.h"
#import "os_macos.h"
GodotApplication *GodotApp = nil;
@interface GodotApplication ()
- (void)forceUnbundledWindowActivationHackStep1;
- (void)forceUnbundledWindowActivationHackStep2;
- (void)forceUnbundledWindowActivationHackStep3;
@end
@implementation GodotApplication
- (GodotApplication *)init {
self = [super init];
GodotApp = self;
return self;
}
- (GodotApplicationDelegate *)godotDelegate {
return (GodotApplicationDelegate *)self.delegate;
}
- (void)activateApplication {
[NSApp activateIgnoringOtherApps:YES];
NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
const char *bundled_id = getenv("__CFBundleIdentifier");
NSString *nsbundleid_env = [NSString stringWithUTF8String:(bundled_id != nullptr) ? bundled_id : ""];
NSString *nsbundleid = [[NSBundle mainBundle] bundleIdentifier];
if (nsappname == nil || isatty(STDOUT_FILENO) || isatty(STDIN_FILENO) || isatty(STDERR_FILENO) || ![nsbundleid isEqualToString:nsbundleid_env]) {
#if DEV_ENABLED
if (!OS_MacOS::is_debugger_attached())
#endif
// If the executable is started from terminal or is not bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored).
[self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02];
}
}
- (void)mediaKeyEvent:(int)key state:(BOOL)state repeat:(BOOL)repeat {
Key keycode = Key::NONE;
switch (key) {
@ -129,4 +166,28 @@
}
}
- (void)forceUnbundledWindowActivationHackStep1 {
// Step 1: Switch focus to macOS SystemUIServer process.
// Required to perform step 2, TransformProcessType will fail if app is already the in focus.
for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.systemuiserver"]) {
[app activateWithOptions:NSApplicationActivateIgnoringOtherApps];
break;
}
[self performSelector:@selector(forceUnbundledWindowActivationHackStep2)
withObject:nil
afterDelay:0.02];
}
- (void)forceUnbundledWindowActivationHackStep2 {
// Step 2: Register app as foreground process.
ProcessSerialNumber psn = { 0, kCurrentProcess };
(void)TransformProcessType(&psn, kProcessTransformToForegroundApplication);
[self performSelector:@selector(forceUnbundledWindowActivationHackStep3) withObject:nil afterDelay:0.02];
}
- (void)forceUnbundledWindowActivationHackStep3 {
// Step 3: Switch focus back to app window.
[[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];
}
@end

View file

@ -35,18 +35,12 @@
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
@interface GodotApplicationDelegate : NSObject <NSUserInterfaceItemSearching, NSApplicationDelegate> {
bool high_contrast;
bool reduce_motion;
bool reduce_transparency;
bool voice_over;
}
class OS_MacOS_NSApp;
@interface GodotApplicationDelegate : NSObject <NSUserInterfaceItemSearching, NSApplicationDelegate>
- (GodotApplicationDelegate *)initWithOS:(OS_MacOS_NSApp *)os;
- (void)forceUnbundledWindowActivationHackStep1;
- (void)forceUnbundledWindowActivationHackStep2;
- (void)forceUnbundledWindowActivationHackStep3;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
- (void)accessibilityDisplayOptionsChange:(NSNotification *)notification;
- (bool)getHighContrast;
- (bool)getReduceMotion;
- (bool)getReduceTransparency;

View file

@ -34,7 +34,28 @@
#import "native_menu_macos.h"
#import "os_macos.h"
@implementation GodotApplicationDelegate
#import "main/main.h"
@interface GodotApplicationDelegate ()
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
- (void)accessibilityDisplayOptionsChange:(NSNotification *)notification;
@end
@implementation GodotApplicationDelegate {
bool high_contrast;
bool reduce_motion;
bool reduce_transparency;
bool voice_over;
OS_MacOS_NSApp *os_mac;
}
- (GodotApplicationDelegate *)initWithOS:(OS_MacOS_NSApp *)os {
self = [super init];
if (self) {
os_mac = os;
}
return self;
}
- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
return YES;
@ -93,30 +114,6 @@
}
}
- (void)forceUnbundledWindowActivationHackStep1 {
// Step 1: Switch focus to macOS SystemUIServer process.
// Required to perform step 2, TransformProcessType will fail if app is already the in focus.
for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.systemuiserver"]) {
[app activateWithOptions:NSApplicationActivateIgnoringOtherApps];
break;
}
[self performSelector:@selector(forceUnbundledWindowActivationHackStep2)
withObject:nil
afterDelay:0.02];
}
- (void)forceUnbundledWindowActivationHackStep2 {
// Step 2: Register app as foreground process.
ProcessSerialNumber psn = { 0, kCurrentProcess };
(void)TransformProcessType(&psn, kProcessTransformToForegroundApplication);
[self performSelector:@selector(forceUnbundledWindowActivationHackStep3) withObject:nil afterDelay:0.02];
}
- (void)forceUnbundledWindowActivationHackStep3 {
// Step 3: Switch focus back to app window.
[[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];
}
- (void)system_theme_changed:(NSNotification *)notification {
DisplayServerMacOS *ds = Object::cast_to<DisplayServerMacOS>(DisplayServer::get_singleton());
if (ds) {
@ -125,22 +122,7 @@
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
static_cast<OS_MacOS *>(OS::get_singleton())->start_main();
}
- (void)activate {
[NSApp activateIgnoringOtherApps:YES];
NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
const char *bundled_id = getenv("__CFBundleIdentifier");
NSString *nsbundleid_env = [NSString stringWithUTF8String:(bundled_id != nullptr) ? bundled_id : ""];
NSString *nsbundleid = [[NSBundle mainBundle] bundleIdentifier];
if (nsappname == nil || isatty(STDOUT_FILENO) || isatty(STDIN_FILENO) || isatty(STDERR_FILENO) || ![nsbundleid isEqualToString:nsbundleid_env]) {
// If the executable is started from terminal or is not bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored).
[self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02];
}
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(system_theme_changed:) name:@"AppleInterfaceThemeChangedNotification" object:nil];
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(system_theme_changed:) name:@"AppleColorPreferencesChangedNotification" object:nil];
os_mac->start_main();
}
static const char *godot_ac_ctx = "gd_accessibility_observer_ctx";
@ -155,6 +137,9 @@ static const char *godot_ac_ctx = "gd_accessibility_observer_ctx";
reduce_transparency = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceTransparency];
voice_over = [[NSWorkspace sharedWorkspace] isVoiceOverEnabled];
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(system_theme_changed:) name:@"AppleInterfaceThemeChangedNotification" object:nil];
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(system_theme_changed:) name:@"AppleColorPreferencesChangedNotification" object:nil];
return self;
}
@ -195,10 +180,6 @@ static const char *godot_ac_ctx = "gd_accessibility_observer_ctx";
}
- (void)application:(NSApplication *)application openURLs:(NSArray<NSURL *> *)urls {
OS_MacOS *os = (OS_MacOS *)OS::get_singleton();
if (!os) {
return;
}
List<String> args;
for (NSURL *url in urls) {
if ([url isFileURL]) {
@ -208,33 +189,29 @@ static const char *godot_ac_ctx = "gd_accessibility_observer_ctx";
}
}
if (!args.is_empty()) {
if (os->get_main_loop()) {
if (os_mac->get_main_loop()) {
// Application is already running, open a new instance with the URL/files as command line arguments.
os->create_instance(args);
} else if (os->get_cmd_argc() == 0) {
os_mac->create_instance(args);
} else if (os_mac->get_cmd_argc() == 0) {
// Application is just started, add to the list of command line arguments and continue.
os->set_cmdline_platform_args(args);
os_mac->set_cmdline_platform_args(args);
}
}
}
- (void)application:(NSApplication *)sender openFiles:(NSArray<NSString *> *)filenames {
OS_MacOS *os = (OS_MacOS *)OS::get_singleton();
if (!os) {
return;
}
List<String> args;
for (NSString *filename in filenames) {
NSURL *url = [NSURL URLWithString:filename];
args.push_back(String::utf8([url.path UTF8String]));
}
if (!args.is_empty()) {
if (os->get_main_loop()) {
if (os_mac->get_main_loop()) {
// Application is already running, open a new instance with the URL/files as command line arguments.
os->create_instance(args);
} else if (os->get_cmd_argc() == 0) {
os_mac->create_instance(args);
} else if (os_mac->get_cmd_argc() == 0) {
// Application is just started, add to the list of command line arguments and continue.
os->set_cmdline_platform_args(args);
os_mac->set_cmdline_platform_args(args);
}
}
}
@ -244,14 +221,14 @@ static const char *godot_ac_ctx = "gd_accessibility_observer_ctx";
if (ds) {
ds->mouse_process_popups(true);
}
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
if (os_mac->get_main_loop()) {
os_mac->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
}
}
- (void)applicationDidBecomeActive:(NSNotification *)notification {
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
if (os_mac->get_main_loop()) {
os_mac->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
}
}
@ -272,29 +249,26 @@ static const char *godot_ac_ctx = "gd_accessibility_observer_ctx";
}
- (void)applicationWillTerminate:(NSNotification *)notification {
OS_MacOS *os = (OS_MacOS *)OS::get_singleton();
if (os) {
os->cleanup();
exit(os->get_exit_code());
}
os_mac->cleanup();
exit(os_mac->get_exit_code());
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
if (os_mac->os_should_terminate()) {
return NSTerminateNow;
}
DisplayServerMacOS *ds = Object::cast_to<DisplayServerMacOS>(DisplayServer::get_singleton());
if (ds && ds->has_window(DisplayServerMacOS::MAIN_WINDOW_ID)) {
ds->send_window_event(ds->get_window(DisplayServerMacOS::MAIN_WINDOW_ID), DisplayServerMacOS::WINDOW_EVENT_CLOSE_REQUEST);
}
OS_MacOS *os = (OS_MacOS *)OS::get_singleton();
if (!os || os->os_should_terminate()) {
return NSTerminateNow;
}
return NSTerminateCancel;
}
- (void)showAbout:(id)sender {
OS_MacOS *os = (OS_MacOS *)OS::get_singleton();
if (os && os->get_main_loop()) {
os->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT);
if (os_mac->get_main_loop()) {
os_mac->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT);
}
}

View file

@ -31,6 +31,7 @@
#import "godot_content_view.h"
#import "display_server_macos.h"
#import "godot_window.h"
#import "key_mapping_macos.h"
#include "main/main.h"
@ -153,7 +154,12 @@
// MARK: Backing Layer
- (CALayer *)makeBackingLayer {
return [[CAMetalLayer class] layer];
CAMetalLayer *layer = [CAMetalLayer new];
layer.edgeAntialiasingMask = 0;
layer.masksToBounds = NO;
layer.presentsWithTransaction = NO;
[layer removeAllAnimations];
return layer;
}
- (BOOL)wantsUpdateLayer {

View file

@ -30,9 +30,9 @@
#import "os_macos.h"
#include "main/main.h"
#import "godot_application.h"
#include <unistd.h>
#include "main/main.h"
#if defined(SANITIZERS_ENABLED)
#include <sys/resource.h>
@ -50,20 +50,74 @@ int main(int argc, char **argv) {
setrlimit(RLIMIT_STACK, &stack_lim);
#endif
int first_arg = 1;
const char *dbg_arg = "-NSDocumentRevisionsDebugMode";
LocalVector<char *> args;
args.resize(argc);
uint32_t argsc = 0;
int wait_for_debugger = 0; // wait 5 second by default
bool is_embedded = false;
for (int i = 0; i < argc; i++) {
if (strcmp(dbg_arg, argv[i]) == 0) {
first_arg = i + 2;
if (strcmp("-NSDocumentRevisionsDebugMode", argv[i]) == 0) {
// remove "-NSDocumentRevisionsDebugMode" and the next argument
continue;
}
if (strcmp("--os-debug", argv[i]) == 0) {
i++;
wait_for_debugger = 5000; // wait 5 seconds by default
if (i < argc && strncmp(argv[i], "--", 2) != 0) {
wait_for_debugger = atoi(argv[i]);
}
continue;
}
if (strcmp("--embedded", argv[i]) == 0) {
is_embedded = true;
continue;
}
args.ptr()[argsc] = argv[i];
argsc++;
}
OS_MacOS os(argv[0], argc - first_arg, &argv[first_arg]);
uint32_t remaining_args = argsc - 1;
OS_MacOS *os = nullptr;
if (is_embedded) {
#ifdef DEBUG_ENABLED
os = memnew(OS_MacOS_Embedded(args[0], remaining_args, remaining_args > 0 ? &args[1] : nullptr));
#else
WARN_PRINT("Embedded mode is not supported in release builds.");
return EXIT_FAILURE;
#endif
} else {
os = memnew(OS_MacOS_NSApp(args[0], remaining_args, remaining_args > 0 ? &args[1] : nullptr));
}
#ifdef TOOLS_ENABLED
if (wait_for_debugger > 0) {
os->wait_for_debugger(wait_for_debugger);
print_verbose("Continuing execution.");
}
#else
if (wait_for_debugger > 0) {
WARN_PRINT("--os-debug is not supported in release builds.");
}
#endif
if (is_embedded) {
// No dock icon for the embedded process, as it is hosted in the Godot editor.
ProcessSerialNumber psn = { 0, kCurrentProcess };
(void)TransformProcessType(&psn, kProcessTransformToBackgroundApplication);
}
// We must override main when testing is enabled.
TEST_MAIN_OVERRIDE
os.run(); // Note: This function will never return.
os->run();
return os.get_exit_code();
memdelete(os);
return os->get_exit_code();
}

View file

@ -32,6 +32,7 @@
#import "display_server_macos.h"
#import "godot_button_view.h"
#import "godot_content_view.h"
#import "godot_window.h"
@implementation GodotWindowDelegate
@ -307,7 +308,7 @@
DisplayServerMacOS::WindowData &wd = ds->get_window(window_id);
if (wd.window_button_view) {
[(GodotButtonView *)wd.window_button_view displayButtons];
[wd.window_button_view displayButtons];
}
if (ds->mouse_get_mode() == DisplayServer::MOUSE_MODE_CAPTURED) {
@ -339,7 +340,7 @@
DisplayServerMacOS::WindowData &wd = ds->get_window(window_id);
if (wd.window_button_view) {
[(GodotButtonView *)wd.window_button_view displayButtons];
[wd.window_button_view displayButtons];
}
wd.focused = false;

View file

@ -0,0 +1,94 @@
/**************************************************************************/
/* macos_quartz_core_spi.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
// Copyright 2014 The Chromium Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the OpenEmu Team nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY OpenEmu Team ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL OpenEmu Team BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/Foundation.h>
#import <Quartz/Quartz.h>
#include <stdint.h>
// https://chromium.googlesource.com/chromium/src/+/refs/heads/main/ui/base/cocoa/remote_layer_api.h
// The CAContextID type identifies a CAContext across processes. This is the
// token that is passed from the process that is sharing the CALayer that it is
// rendering to the process that will be displaying that CALayer.
typedef uint32_t CAContextID;
// The CAContext has a static CAContextID which can be sent to another process.
// When a CALayerHost is created using that CAContextID in another process, the
// content displayed by that CALayerHost will be the content of the CALayer
// that is set as the |layer| property on the CAContext.
@interface CAContext : NSObject
+ (instancetype)contextWithCGSConnection:(CAContextID)contextId options:(NSDictionary *)optionsDict;
@property(readonly) CAContextID contextId;
@property(retain) CALayer *layer;
@end
// The CALayerHost is created in the process that will display the content
// being rendered by another process. Setting the |contextId| property on
// an object of this class will make this layer display the content of the
// CALayer that is set to the CAContext with that CAContextID in the layer
// sharing process.
@interface CALayerHost : CALayer
@property CAContextID contextId;
@end
// The CGSConnectionID is used to create the CAContext in the process that is
// going to share the CALayers that it is rendering to another process to
// display.
typedef uint32_t CGSConnectionID;
extern "C" CGSConnectionID CGSMainConnectionID(void);
extern "C" NSString *const kCAContextCIFilterBehavior;

View file

@ -40,16 +40,6 @@
#include "servers/audio_server.h"
class OS_MacOS : public OS_Unix {
const char *execpath = nullptr;
int argc = 0;
char **argv = nullptr;
id delegate = nullptr;
bool should_terminate = false;
bool main_stared = false;
JoypadApple *joypad_apple = nullptr;
#ifdef COREAUDIO_ENABLED
AudioDriverCoreAudio audio_driver;
#endif
@ -59,10 +49,6 @@ class OS_MacOS : public OS_Unix {
CrashHandler crash_handler;
CFRunLoopObserverRef pre_wait_observer = nil;
MainLoop *main_loop = nullptr;
List<String> launch_service_args;
CGFloat _weight_to_ct(int p_weight) const;
@ -70,11 +56,15 @@ class OS_MacOS : public OS_Unix {
String _get_default_fontname(const String &p_font_name) const;
static _FORCE_INLINE_ String get_framework_executable(const String &p_path);
static void pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context);
void terminate();
protected:
const char *execpath = nullptr;
int argc = 0;
char **argv = nullptr;
JoypadApple *joypad_apple = nullptr;
MainLoop *main_loop = nullptr;
virtual void initialize_core() override;
virtual void initialize() override;
virtual void finalize() override;
@ -132,6 +122,11 @@ public:
virtual Vector<String> get_granted_permissions() const override;
virtual void revoke_granted_permissions() override;
#ifdef TOOLS_ENABLED
static bool is_debugger_attached();
void wait_for_debugger(uint32_t p_msec);
#endif
virtual bool _check_internal_feature_support(const String &p_feature) override;
virtual void disable_crash_handler() override;
@ -142,13 +137,38 @@ public:
virtual String get_system_ca_certificates() override;
virtual OS::PreferredTextureFormat get_preferred_texture_format() const override;
void run(); // Runs macOS native event loop.
virtual void run() = 0;
OS_MacOS(const char *p_execpath, int p_argc, char **p_argv);
};
class OS_MacOS_NSApp : public OS_MacOS {
id delegate = nullptr;
bool should_terminate = false;
bool main_started = false;
CFRunLoopObserverRef pre_wait_observer = nil;
void terminate();
public:
void start_main(); // Initializes and runs Godot main loop.
void activate();
void cleanup();
bool os_should_terminate() const { return should_terminate; }
int get_cmd_argc() const { return argc; }
OS_MacOS(const char *p_execpath, int p_argc, char **p_argv);
~OS_MacOS();
virtual void run() override;
OS_MacOS_NSApp(const char *p_execpath, int p_argc, char **p_argv);
};
#ifdef DEBUG_ENABLED
class OS_MacOS_Embedded : public OS_MacOS {
public:
virtual void run() override;
OS_MacOS_Embedded(const char *p_execpath, int p_argc, char **p_argv);
};
#endif

View file

@ -31,6 +31,9 @@
#import "os_macos.h"
#import "dir_access_macos.h"
#ifdef DEBUG_ENABLED
#import "display_server_embedded.h"
#endif
#import "display_server_macos.h"
#import "godot_application.h"
#import "godot_application_delegate.h"
@ -46,32 +49,6 @@
#include <os/log.h>
#include <sys/sysctl.h>
void OS_MacOS::pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context) {
OS_MacOS *os = static_cast<OS_MacOS *>(OS::get_singleton());
@autoreleasepool {
@try {
// Get rid of pending events.
DisplayServer *ds = DisplayServer::get_singleton();
DisplayServerMacOS *ds_mac = Object::cast_to<DisplayServerMacOS>(ds);
if (ds_mac) {
ds_mac->_process_events(false);
} else if (ds) {
ds->process_events();
}
os->joypad_apple->process_joypads();
if (Main::iteration()) {
os->terminate();
}
} @catch (NSException *exception) {
ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String));
}
}
CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Prevent main loop from sleeping.
}
void OS_MacOS::initialize() {
crash_handler.initialize();
@ -151,6 +128,59 @@ void OS_MacOS::revoke_granted_permissions() {
}
}
#if TOOLS_ENABLED
// Function to check if a debugger is attached to the current process
bool OS_MacOS::is_debugger_attached() {
int mib[4];
struct kinfo_proc info{};
size_t size = sizeof(info);
// Initialize the flags so that, if sysctl fails, info.kp_proc.p_flag will be 0.
info.kp_proc.p_flag = 0;
// Initialize mib, which tells sysctl the info we want, in this case we're looking for information
// about a specific process ID.
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PID;
mib[3] = getpid();
if (sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0) {
perror("sysctl");
return false;
}
return (info.kp_proc.p_flag & P_TRACED) != 0;
}
void OS_MacOS::wait_for_debugger(uint32_t p_msec) {
if (p_msec == 0) {
return;
}
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
CFTimeInterval wait_time = p_msec / 1000.0;
NSTimer *timer = [NSTimer timerWithTimeInterval:0.100
repeats:YES
block:^(NSTimer *t) {
if (is_debugger_attached() || CFAbsoluteTimeGetCurrent() > start + wait_time) {
[NSApp stopModalWithCode:NSModalResponseContinue];
[t invalidate];
}
}];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSModalPanelRunLoopMode];
pid_t pid = getpid();
alert(vformat("Attach debugger to pid: %d", pid));
print("continue...");
}
#endif
void OS_MacOS::initialize_core() {
OS_Unix::initialize_core();
@ -854,74 +884,11 @@ OS::PreferredTextureFormat OS_MacOS::get_preferred_texture_format() const {
return PREFERRED_TEXTURE_FORMAT_S3TC_BPTC;
}
void OS_MacOS::run() {
[NSApp run];
}
void OS_MacOS::start_main() {
Error err;
@autoreleasepool {
err = Main::setup(execpath, argc, argv);
}
if (err == OK) {
main_stared = true;
int ret;
@autoreleasepool {
ret = Main::start();
}
if (ret == EXIT_SUCCESS) {
if (main_loop) {
@autoreleasepool {
main_loop->initialize();
}
pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr);
CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
return;
}
} else {
set_exit_code(EXIT_FAILURE);
}
} else if (err == ERR_HELP) { // Returned by --help and --version, so success.
set_exit_code(EXIT_SUCCESS);
} else {
set_exit_code(EXIT_FAILURE);
}
terminate();
}
void OS_MacOS::activate() {
[delegate activate];
}
void OS_MacOS::terminate() {
if (pre_wait_observer) {
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
CFRelease(pre_wait_observer);
pre_wait_observer = nil;
}
should_terminate = true;
[NSApp terminate:nil];
}
void OS_MacOS::cleanup() {
if (main_loop) {
main_loop->finalize();
}
if (main_stared) {
@autoreleasepool {
Main::cleanup();
}
}
}
OS_MacOS::OS_MacOS(const char *p_execpath, int p_argc, char **p_argv) {
execpath = p_execpath;
argc = p_argc;
argv = p_argv;
if (is_sandboxed()) {
// Load security-scoped bookmarks, request access, remove stale or invalid bookmarks.
NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
@ -939,8 +906,6 @@ OS_MacOS::OS_MacOS(const char *p_execpath, int p_argc, char **p_argv) {
[[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"];
}
main_loop = nullptr;
Vector<Logger *> loggers;
loggers.push_back(memnew(MacOSTerminalLogger));
_set_logger(memnew(CompositeLogger(loggers)));
@ -950,7 +915,94 @@ OS_MacOS::OS_MacOS(const char *p_execpath, int p_argc, char **p_argv) {
#endif
DisplayServerMacOS::register_macos_driver();
}
// MARK: - OS_MacOS_NSApp
void OS_MacOS_NSApp::run() {
[NSApp run];
}
void OS_MacOS_NSApp::start_main() {
Error err;
@autoreleasepool {
err = Main::setup(execpath, argc, argv);
}
if (err == OK) {
main_started = true;
int ret;
@autoreleasepool {
ret = Main::start();
}
if (ret == EXIT_SUCCESS) {
if (main_loop) {
@autoreleasepool {
main_loop->initialize();
}
DisplayServer *ds = DisplayServer::get_singleton();
DisplayServerMacOS *ds_mac = Object::cast_to<DisplayServerMacOS>(ds);
pre_wait_observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
@autoreleasepool {
@try {
if (ds_mac) {
ds_mac->_process_events(false);
} else if (ds) {
ds->process_events();
}
joypad_apple->process_joypads();
if (Main::iteration()) {
terminate();
}
} @catch (NSException *exception) {
ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String));
}
}
CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Prevent main loop from sleeping.
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
return;
}
} else {
set_exit_code(EXIT_FAILURE);
}
} else if (err == ERR_HELP) { // Returned by --help and --version, so success.
set_exit_code(EXIT_SUCCESS);
} else {
set_exit_code(EXIT_FAILURE);
}
terminate();
}
void OS_MacOS_NSApp::terminate() {
if (pre_wait_observer) {
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
CFRelease(pre_wait_observer);
pre_wait_observer = nil;
}
should_terminate = true;
[NSApp terminate:nil];
}
void OS_MacOS_NSApp::cleanup() {
if (main_loop) {
main_loop->finalize();
}
if (main_started) {
@autoreleasepool {
Main::cleanup();
}
}
}
OS_MacOS_NSApp::OS_MacOS_NSApp(const char *p_execpath, int p_argc, char **p_argv) :
OS_MacOS(p_execpath, p_argc, p_argv) {
// Implicitly create shared NSApplication instance.
[GodotApplication sharedApplication];
@ -964,12 +1016,69 @@ OS_MacOS::OS_MacOS(const char *p_execpath, int p_argc, char **p_argv) {
NSMenu *main_menu = [[NSMenu alloc] initWithTitle:@""];
[NSApp setMainMenu:main_menu];
delegate = [[GodotApplicationDelegate alloc] init];
delegate = [[GodotApplicationDelegate alloc] initWithOS:this];
ERR_FAIL_NULL(delegate);
[NSApp setDelegate:delegate];
[NSApp registerUserInterfaceItemSearchHandler:delegate];
}
OS_MacOS::~OS_MacOS() {
// NOP
// MARK: - OS_MacOS_Embedded
#ifdef DEBUG_ENABLED
void OS_MacOS_Embedded::run() {
CFRunLoopGetCurrent();
@autoreleasepool {
Error err = Main::setup(execpath, argc, argv);
if (err != OK) {
if (err == ERR_HELP) {
return set_exit_code(EXIT_SUCCESS);
}
return set_exit_code(EXIT_FAILURE);
}
}
int ret;
@autoreleasepool {
ret = Main::start();
}
DisplayServerEmbedded *ds = Object::cast_to<DisplayServerEmbedded>(DisplayServer::get_singleton());
if (!ds) {
ERR_FAIL_MSG("DisplayServerEmbedded is not initialized.");
}
if (ds && ret == EXIT_SUCCESS && main_loop) {
@autoreleasepool {
main_loop->initialize();
}
while (true) {
@autoreleasepool {
@try {
ds->process_events();
if (Main::iteration()) {
break;
}
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, 0);
} @catch (NSException *exception) {
ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String));
}
}
}
main_loop->finalize();
}
Main::cleanup();
}
OS_MacOS_Embedded::OS_MacOS_Embedded(const char *p_execpath, int p_argc, char **p_argv) :
OS_MacOS(p_execpath, p_argc, p_argv) {
DisplayServerEmbedded::register_embedded_driver();
}
#endif