mirror of
https://github.com/godotengine/godot.git
synced 2025-10-19 16:03:29 +00:00
MacOS: Embedded window support.
This commit is contained in:
parent
1cf573f44d
commit
00e1fdec2c
37 changed files with 3670 additions and 384 deletions
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
474
core/input/input_event_codec.cpp
Normal file
474
core/input/input_event_codec.cpp
Normal 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;
|
||||
}
|
||||
}
|
49
core/input/input_event_codec.h
Normal file
49
core/input/input_event_codec.h
Normal 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);
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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"]:
|
||||
|
|
|
@ -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"])
|
||||
|
||||
|
|
231
platform/macos/display_server_embedded.h
Normal file
231
platform/macos/display_server_embedded.h
Normal 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();
|
||||
};
|
744
platform/macos/display_server_embedded.mm
Normal file
744
platform/macos/display_server_embedded.mm
Normal 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
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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]]) {
|
||||
NSString *value = [NSUserDefaults.standardUserDefaults stringForKey:@"AppleActionOnDoubleClick"];
|
||||
return [value isEqualToString:@"Maximize"];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DisplayServerMacOS::window_minimize_on_title_dbl_click() const {
|
||||
id value = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleActionOnDoubleClick"];
|
||||
if ([value isKindOfClass:[NSString class]]) {
|
||||
NSString *value = [NSUserDefaults.standardUserDefaults stringForKey:@"AppleActionOnDoubleClick"];
|
||||
return [value isEqualToString:@"Minimize"];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
75
platform/macos/editor/embedded_game_view_plugin.h
Normal file
75
platform/macos/editor/embedded_game_view_plugin.h
Normal 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();
|
154
platform/macos/editor/embedded_game_view_plugin.mm
Normal file
154
platform/macos/editor/embedded_game_view_plugin.mm
Normal 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);
|
||||
}
|
110
platform/macos/editor/embedded_process_macos.h
Normal file
110
platform/macos/editor/embedded_process_macos.h
Normal 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();
|
||||
};
|
248
platform/macos/editor/embedded_process_macos.mm
Normal file
248
platform/macos/editor/embedded_process_macos.mm
Normal 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();
|
||||
}
|
||||
}
|
70
platform/macos/embedded_debugger.h
Normal file
70
platform/macos/embedded_debugger.h
Normal 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
|
||||
};
|
177
platform/macos/embedded_debugger.mm
Normal file
177
platform/macos/embedded_debugger.mm
Normal 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
|
108
platform/macos/embedded_gl_manager.h
Normal file
108
platform/macos/embedded_gl_manager.h
Normal 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
|
271
platform/macos/embedded_gl_manager.mm
Normal file
271
platform/macos/embedded_gl_manager.mm
Normal 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
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
OS_MacOS os(argv[0], argc - first_arg, &argv[first_arg]);
|
||||
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++;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
94
platform/macos/macos_quartz_core_spi.h
Normal file
94
platform/macos/macos_quartz_core_spi.h
Normal 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;
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue