pandemonium_engine_minimal/platform/frt/os_frt.cpp
2023-12-14 21:54:22 +01:00

625 lines
17 KiB
C++

// os_frt.cpp
/*
* FRT - A Godot platform targeting single board computers
* Copyright (c) 2017-2019 Emanuele Fornara
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include "core/version.h"
#include "core/os/os.h"
#include "core/input/input.h"
#include "core/os/file_access.h"
#include "drivers/unix/os_unix.h"
#include "servers/rendering_server.h"
#include "servers/rendering/rasterizer.h"
#include "servers/audio/audio_driver_dummy.h"
#include "servers/rendering/rendering_server_raster.h"
#include "drivers/alsa/audio_driver_alsa.h"
#include "drivers/pulseaudio/audio_driver_pulseaudio.h"
#include "main/main.h"
#include "main/input_default.h"
#include "main/performance.h"
#include "core/string/print_string.h"
#define VIDEO_DRIVER_GLES2 0
#define VIDEO_DRIVER_COUNT 1
#include "platform/x11/joypad_linux.h"
//#define FRT_DL_SKIP
#include "drivers/gles2/rasterizer_gles2.h"
typedef AudioDriverManager AudioDriverManagerSW;
typedef AudioDriver AudioDriverSW;
#define set_mouse_pos set_mouse_position
#define get_mouse_pos get_mouse_position
#define get_mouse_speed get_last_mouse_speed
#define has has_setting
#define FRT_MOCK_GODOT_INPUT_MODIFIER_STATE
#define INPUT_MODIFIER_REF Ref<InputEventWithModifiers>
#define INPUT_EVENT_REF(t) Ref<t>
#define joystick_linux JoypadLinux
#include "core/config/project_settings.h"
#define PROJECT_SETTINGS \
ProjectSettings *project_settings = ProjectSettings::get_singleton();
#include "frt.h"
#include "bits/mouse_virtual.h"
using namespace frt;
namespace frt {
extern const char *perfmon_filename;
extern const char *extract_resource_name;
}
static class PerfMon {
private:
FILE *f;
char sep;
void init_separator() {
char s[32];
snprintf(s, sizeof(s), "%f", 2.5);
s[sizeof(s) - 1] = '\0';
sep = strchr(s, ',') ? ';' : ',';
}
public:
PerfMon()
: f(0) {}
~PerfMon() { cleanup(); }
bool is_valid() { return f; }
void init() {
if (!perfmon_filename)
return;
if (!(f = fopen(perfmon_filename, "w")))
return;
init_separator();
Performance *p = Performance::get_singleton();
fprintf(f, "time/ticks_ms");
for (int i = 0; i < Performance::MONITOR_MAX; i++)
fprintf(f, "%c%s", sep,
p->get_monitor_name(Performance::Monitor(i)).ascii().get_data());
fprintf(f, "\n");
}
void iteration(uint32_t t) {
if (!f)
return;
Performance *p = Performance::get_singleton();
fprintf(f, "%u", (unsigned int)t);
for (int i = 0; i < Performance::MONITOR_MAX; i++)
fprintf(f, "%c%f", sep,
p->get_monitor(Performance::Monitor(i)));
fprintf(f, "\n");
}
void cleanup() {
if (f)
fclose(f);
f = 0;
}
} perfmon;
class OS_FRT : public OS_Unix, public Runnable {
private:
App *app;
Param *disable_meta_keys_param;
Param *exit_on_shiftenter_param;
Env *env;
Vec2 screen_size;
ContextGL *context_gl;
RenderingServer *rendering_server;
VideoMode current_videomode;
int current_video_driver;
List<String> args;
MainLoop *main_loop;
#ifdef PULSEAUDIO_ENABLED
AudioDriverPulseAudio driver_pulseaudio;
#endif
#ifdef ALSA_ENABLED
AudioDriverALSA driver_alsa;
#endif
AudioDriverDummy driver_dummy;
int audio_driver_index;
Point2 mouse_pos;
Point2 last_mouse_pos;
bool last_mouse_pos_valid;
MouseMode mouse_mode;
int mouse_state;
bool grab;
int event_id;
InputDefault *input;
#ifdef JOYDEV_ENABLED
joystick_linux *joystick;
#endif
MouseVirtual mouse_virtual;
uint64_t last_click;
public:
int get_video_driver_count() const { return VIDEO_DRIVER_COUNT; }
int get_current_video_driver() const { return current_video_driver; }
const char *get_video_driver_name(int driver) const {
return "GLES2";
}
OS::VideoMode get_default_video_mode() const {
return OS::VideoMode(screen_size.x, screen_size.y, true, false, true);
}
int get_audio_driver_count() const {
return AudioDriverManagerSW::get_driver_count();
}
const char *get_audio_driver_name(int driver) const {
AudioDriverSW *driver_ = AudioDriverManagerSW::get_driver(driver);
ERR_FAIL_COND_V(!driver_, "");
return driver_->get_name();
}
bool _check_internal_feature_support(const String &feature) {
return feature == "pc" || feature == "etc";
}
String get_config_path() const {
if (has_environment("XDG_CONFIG_HOME"))
return get_environment("XDG_CONFIG_HOME");
if (has_environment("HOME"))
return get_environment("HOME").plus_file(".config");
return ".";
}
String get_data_path() const {
if (has_environment("XDG_DATA_HOME"))
return get_environment("XDG_DATA_HOME");
if (has_environment("HOME"))
return get_environment("HOME").plus_file(".local/share");
return get_config_path();
}
String get_cache_path() const {
if (has_environment("XDG_CACHE_HOME"))
return get_environment("XDG_CACHE_HOME");
if (has_environment("HOME"))
return get_environment("HOME").plus_file(".cache");
return get_config_path();
}
void extract_resource_fatal(const char *msg) {
fatal("failed extracting resource '%s': %s.",
extract_resource_name, msg);
}
void extract_resource_if_requested() {
const char *s = extract_resource_name;
if (!s)
return;
if (!isalnum(*s))
extract_resource_fatal("invalid name");
for (int i = 0; s[i]; i++)
if (!isalnum(*s) && !strchr(".-_", *s))
extract_resource_fatal("invalid name");
String name = "res://frt/";
name += s;
if (!FileAccess::exists(name))
extract_resource_fatal("not found");
FileAccess *in = FileAccess::open(name, FileAccess::READ);
if (!in)
extract_resource_fatal("failed opening resource");
int len = in->get_len();
uint8_t *buf = new uint8_t[len]; // memory "leak" is fine here
if (!buf)
extract_resource_fatal("failed allocating memory");
int n = in->get_buffer(buf, len);
if (n != len)
extract_resource_fatal("failed reading resource");
in->close();
FileAccess *out = FileAccess::open(s, FileAccess::WRITE);
if (!out)
extract_resource_fatal("failed opening output file");
out->store_buffer(buf, len);
out->close();
exit(0);
}
void get_project_frt_params() {
PROJECT_SETTINGS
String name;
const int n = app->get_n_of_params();
for (int i = 0; i < n; i++) {
Param *p = app->get_param(i);
if (p->source == Param::CommandLine)
continue;
name = "frt/";
name += p->name;
if (!project_settings->has(name))
continue;
p->source = Param::ProjectSettings;
Value &v = p->value;
switch (v.t) {
case Value::Bool:
v.u.b = bool(project_settings->get(name));
break;
case Value::Int:
v.u.i = int(project_settings->get(name));
break;
case Value::Float:
v.u.f = float(project_settings->get(name));
break;
case Value::String: {
String s = String(project_settings->get(name));
v.u.s = strdup(s.ascii());
// TODO: keep track and dealloc string copy
} break;
}
}
}
bool disable_meta_keys() {
return disable_meta_keys_param->value.u.b;
}
bool exit_on_shiftenter() {
return exit_on_shiftenter_param->value.u.b;
}
Error initialize(const VideoMode &desired, int video_driver, int audio_driver) {
get_project_frt_params();
extract_resource_if_requested();
args = OS::get_singleton()->get_cmdline_args();
current_videomode = desired;
main_loop = 0;
Vec2 view(current_videomode.width, current_videomode.height);
int gl_version = video_driver == 2;
context_gl = env->video->create_the_gl_context(gl_version, view);
context_gl->initialize();
RasterizerGLES2::register_config();
RasterizerGLES2::make_current();
current_video_driver = VIDEO_DRIVER_GLES2;
rendering_server = memnew(RenderingServerRaster);
// TODO: Audio Module
AudioDriverManagerSW::get_driver(audio_driver)->set_singleton();
audio_driver_index = audio_driver;
if (AudioDriverManagerSW::get_driver(audio_driver)->init() != OK) {
audio_driver_index = -1;
for (int i = 0; i < AudioDriverManagerSW::get_driver_count(); i++) {
if (i == audio_driver)
continue;
AudioDriverManagerSW::get_driver(i)->set_singleton();
if (AudioDriverManagerSW::get_driver(i)->init() == OK) {
audio_driver_index = i;
break;
}
}
}
if (!rendering_server)
return FAILED;
rendering_server->init();
input = memnew(InputDefault);
#ifdef JOYDEV_ENABLED
joystick = memnew(joystick_linux(input));
#endif
last_click = 0;
return OK;
}
void finalize() {
if (main_loop)
memdelete(main_loop);
main_loop = NULL;
for (int i = 0; i < get_audio_driver_count(); i++)
AudioDriverManager::get_driver(i)->finish();
memdelete(rendering_server);
#ifdef JOYDEV_ENABLED
memdelete(joystick);
#endif
memdelete(input);
args.clear();
}
void set_mouse_show(bool show) {}
void set_mouse_grab(bool grab) { this->grab = grab; }
bool is_mouse_grab_enabled() const { return grab; }
int get_mouse_button_state() const { return mouse_state; }
Point2 get_mouse_pos() const { return mouse_pos; }
void set_mouse_mode(MouseMode mode) { mouse_mode = mode; }
OS::MouseMode get_mouse_mode() const { return mouse_mode; }
void set_window_title(const String &title) {
env->video->set_title(title.utf8().get_data());
}
void set_video_mode(const VideoMode &video_mode, int screen) {}
OS::VideoMode get_video_mode(int screen) const {
return current_videomode;
}
Size2 get_window_size() const {
return Vector2(current_videomode.width, current_videomode.height);
}
void get_fullscreen_mode_list(List<VideoMode> *list, int screen) const {}
MainLoop *get_main_loop() const { return main_loop; }
void delete_main_loop() {
if (main_loop)
memdelete(main_loop);
main_loop = 0;
}
void set_main_loop(MainLoop *main_loop) {
this->main_loop = main_loop;
input->set_main_loop(main_loop);
}
bool can_draw() const { return true; };
String get_name() const { return "FRT"; }
void move_window_to_foreground() {}
void set_cursor_shape(CursorShape shape) {}
void set_custom_mouse_cursor(const RES&, OS::CursorShape, const Vector2&) {}
void release_rendering_thread() { context_gl->release_current(); }
void make_rendering_thread() { context_gl->make_current(); }
void swap_buffers() { context_gl->swap_buffers(); }
uint32_t unicode_fallback(int gd_code, bool pressed,
const InputModifierState &st) {
if (st.alt || st.control || st.meta || !pressed)
return 0;
if (gd_code >= 'A' && gd_code <= 'Z')
return st.shift ? gd_code : gd_code + ('a' - 'A');
if (gd_code >= ' ' && gd_code <= '~')
return gd_code;
return 0;
}
void get_key_modifier_state(INPUT_MODIFIER_REF mod,
InputModifierState *st = 0) {
InputModifierState state;
if (env->keyboard) {
env->keyboard->get_modifier_state(state);
} else {
state.shift = false;
state.control = false;
state.alt = false;
state.meta = false;
}
mod->set_shift(state.shift);
mod->set_control(state.control);
mod->set_alt(state.alt);
mod->set_metakey(state.meta);
if (st)
*st = state;
}
void process_keyboard_event(int key, bool pressed, uint32_t unicode, bool echo) {
INPUT_EVENT_REF(InputEventKey) k;
k.instance();
InputModifierState st;
get_key_modifier_state(k, &st);
k->set_pressed(pressed);
k->set_scancode(key);
if (unicode)
k->set_unicode(unicode);
else
k->set_unicode(unicode_fallback(key, pressed, st));
k->set_echo(echo);
input->parse_input_event(k);
}
void process_mouse_motion(int x, int y) {
mouse_pos.x = x;
mouse_pos.y = y;
if (!last_mouse_pos_valid) {
last_mouse_pos = mouse_pos;
last_mouse_pos_valid = true;
}
Vector2 rel = mouse_pos - last_mouse_pos;
last_mouse_pos = mouse_pos;
INPUT_EVENT_REF(InputEventMouseMotion) mm;
mm.instance();
get_key_modifier_state(mm);
mm->set_button_mask(mouse_state);
mm->set_position(mouse_pos);
mm->set_global_position(mouse_pos);
mm->set_speed(input->get_mouse_speed());
mm->set_relative(rel);
input->set_mouse_pos(mouse_pos);
input->parse_input_event(mm);
}
void process_mouse_button(int index, bool pressed) {
int bit = (1 << (index - 1));
if (pressed)
mouse_state |= bit;
else
mouse_state &= ~bit;
INPUT_EVENT_REF(InputEventMouseButton) mb;
mb.instance();
get_key_modifier_state(mb);
mb->set_button_mask(mouse_state);
mb->set_position(mouse_pos);
mb->set_global_position(mouse_pos);
mb->set_button_index(index);
mb->set_pressed(pressed);
if (index == 1 && pressed) {
const uint64_t t = get_ticks_usec();
const uint64_t dt = t - last_click;
if (dt < 300000) {
last_click = 0;
mb->set_doubleclick(true);
} else {
last_click = t;
}
}
input->parse_input_event(mb);
}
struct KeyboardHandler : Keyboard::Handler {
OS_FRT *instance;
Keyboard *keyboard;
void handle_keyboard_key(int gd_code, bool pressed, uint32_t unicode, bool echo) {
if (!instance->disable_meta_keys()) {
InputModifierState st;
keyboard->get_modifier_state(st);
if (st.meta && instance->handle_meta(gd_code, pressed))
return;
}
if (instance->exit_on_shiftenter()) {
InputModifierState st;
keyboard->get_modifier_state(st);
if (st.shift && pressed && (gd_code == GD_KEY_RETURN || gd_code == GD_KEY_ENTER))
App::instance()->quit();
}
instance->process_keyboard_event(gd_code, pressed, unicode, echo);
}
} keyboard_handler;
struct MouseHandler : Mouse::Handler {
OS_FRT *instance;
Video *video;
void handle_mouse_button(Mouse::Button button, bool pressed) {
int index;
switch (button) {
case Mouse::Left:
index = 1;
break;
case Mouse::Middle:
index = 3;
break;
case Mouse::Right:
index = 2;
break;
case Mouse::WheelUp:
index = BUTTON_WHEEL_UP;
break;
case Mouse::WheelDown:
index = BUTTON_WHEEL_DOWN;
break;
default:
return;
}
instance->process_mouse_button(index, pressed);
}
void handle_mouse_motion(Vec2 pos) {
Vec2 view = video->move_pointer(pos);
instance->process_mouse_motion(view.x, view.y);
}
} mouse_handler;
bool dispatch_handle_meta(int gd_code, bool pressed) {
// keep it simple: hard-coded order should be fine
if (env->mouse && env->mouse->handle_meta(gd_code, pressed))
return true;
if (env->keyboard && env->keyboard->handle_meta(gd_code, pressed))
return true;
if (env->video && env->video->handle_meta(gd_code, pressed))
return true;
return false;
}
void run() {
if (!main_loop)
return;
disable_meta_keys_param = app->get_param("disable_meta_keys");
exit_on_shiftenter_param = app->get_param("exit_on_shiftenter");
keyboard_handler.instance = this;
keyboard_handler.keyboard = env->keyboard;
mouse_handler.instance = this;
mouse_handler.video = env->video;
if (env->keyboard && !env->mouse) {
mouse_virtual.probe();
env->mouse = &mouse_virtual;
}
if (env->mouse) {
env->mouse->set_size(screen_size);
Vec2 pos = env->mouse->get_pos();
env->video->move_pointer(pos);
}
// mouse set_handler first to increase the chances of RETURN release
if (env->mouse) {
env->mouse->set_handler(&mouse_handler);
}
if (env->keyboard) {
env->keyboard->set_handler(&keyboard_handler);
}
main_loop->init();
perfmon.init();
while (app->is_running()) {
app->dispatch_events();
#ifdef JOYDEV_ENABLED
joystick->process_joypads();
#endif
input->flush_buffered_events();
if (Main::iteration() == true)
break;
if (perfmon.is_valid())
perfmon.iteration(get_ticks_msec());
};
perfmon.cleanup();
main_loop->finish();
}
bool is_joy_known(int p_device) {
return input->is_joy_mapped(p_device);
}
String get_joy_guid(int p_device) const {
return input->get_joy_guid_remapped(p_device);
}
OS_FRT() {
current_video_driver = VIDEO_DRIVER_GLES2;
#ifdef PULSEAUDIO_ENABLED
AudioDriverManagerSW::add_driver(&driver_pulseaudio);
#endif
#ifdef ALSA_ENABLED
AudioDriverManagerSW::add_driver(&driver_alsa);
#endif
if (AudioDriverManagerSW::get_driver_count() == 0)
AudioDriverManagerSW::add_driver(&driver_dummy);
mouse_mode = MOUSE_MODE_VISIBLE;
mouse_state = 0;
grab = false;
};
// Module
const char *get_id() const { return "frt_os_unix"; }
bool probe() { return true; }
void cleanup() {}
bool handle_meta(int gd_code, bool pressed) {
switch (gd_code) {
case 'Q':
if (env->video->provides_quit())
return false;
else
app->quit();
break;
default:
return dispatch_handle_meta(gd_code, pressed);
}
return true;
}
// Runnable
void setup_env(Env *env) {
app = App::instance();
this->env = env;
if (!env->video)
fatal("no video module available.");
screen_size = env->video->get_screen_size();
}
void run_() { main_run(); }
int get_exit_code_() { return get_exit_code(); }
};
FRT_REGISTER(OS_FRT)