require "mspcore";
require "sigc++-2.0";
- if_arch "!windows & !darwin"
+ if_arch "!windows & !darwin & !android"
{
require "xlib";
};
};
if_feature "opengl"
{
- require "opengl";
+ if_arch "android"
+ {
+ require "opengles";
+ };
+ if_arch "!android"
+ {
+ require "opengl";
+ };
};
- if_arch "!windows & !darwin"
+ if_arch "!windows & !darwin & !android"
{
feature "xrandr" "Include support for video mode switching with XRandR";
if_feature "xrandr"
standard CC "c99";
};
};
- if_arch "!windows & !darwin"
+ if_arch "android"
+ {
+ overlay "android";
+ if_feature "opengl"
+ {
+ overlay "egl_android";
+ };
+ };
+ if_arch "!windows & !darwin & !android"
{
overlay "x11";
if_feature "opengl"
--- /dev/null
+#include <msp/core/application.h>
+#include <msp/core/mainthread.h>
+#include "display.h"
+#include "display_private.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Graphics {
+
+Display::Display(const string &)
+{
+ Android::MainThread *thread = reinterpret_cast<Android::MainThread *>(Application::get_data());
+ if(!thread->is_starting_up())
+ throw logic_error("Display must be created during startup");
+
+ priv = new Private;
+ priv->input_queue = 0;
+ priv->native_window = 0;
+
+ thread->signal_native_window_created.connect(sigc::mem_fun(priv, &PlatformDisplayPrivate::native_window_created));
+ thread->signal_native_window_resized.connect(sigc::mem_fun(priv, &PlatformDisplayPrivate::native_window_resized));
+ thread->signal_native_window_destroyed.connect(sigc::mem_fun(priv, &PlatformDisplayPrivate::native_window_destroyed));
+ thread->signal_input_queue_created.connect(sigc::mem_fun(priv, &PlatformDisplayPrivate::input_queue_created));
+ thread->signal_input_queue_destroyed.connect(sigc::mem_fun(priv, &PlatformDisplayPrivate::input_queue_destroyed));
+ thread->resume_startup();
+}
+
+Display::~Display()
+{
+ delete priv;
+}
+
+void Display::set_mode(const VideoMode &, bool)
+{
+ throw runtime_error("video mode switching not supported");
+}
+
+bool Display::process_events()
+{
+ MutexLock lock(priv->event_mutex);
+ if(!priv->input_queue)
+ return false;
+
+ Window::Event event;
+ if(!priv->events.empty())
+ {
+ event = priv->events.front();
+ priv->events.pop_front();
+ }
+ else if(AInputQueue_getEvent(priv->input_queue, &event.aevent)>=0)
+ {
+ if(AInputQueue_preDispatchEvent(priv->input_queue, event.aevent))
+ return true;
+
+ event.type = INPUT_EVENT;
+ }
+ else
+ return false;
+
+ bool handled = false;
+ if(!priv->windows.empty())
+ handled = priv->windows.begin()->second->event(event);
+
+ if(event.type==INPUT_EVENT)
+ AInputQueue_finishEvent(priv->input_queue, event.aevent, handled);
+ else if(event.type==WINDOW_CREATED)
+ priv->window_mutex.lock();
+ else if(event.type==WINDOW_DESTROYED)
+ priv->window_mutex.unlock();
+
+ return true;
+}
+
+void Display::check_error()
+{
+}
+
+
+void PlatformDisplayPrivate::push_event(Msp::Graphics::AndroidEventType type)
+{
+ MutexLock lock(event_mutex);
+ Window::Event event;
+ event.type = type;
+ events.push_back(event);
+}
+
+void PlatformDisplayPrivate::native_window_created(ANativeWindow *window)
+{
+ native_window = window;
+ push_event(WINDOW_CREATED);
+}
+
+void PlatformDisplayPrivate::native_window_resized(ANativeWindow *)
+{
+ push_event(WINDOW_RESIZED);
+}
+
+void PlatformDisplayPrivate::native_window_destroyed(ANativeWindow *)
+{
+ push_event(WINDOW_DESTROYED);
+ MutexLock lock(window_mutex);
+ native_window = 0;
+}
+
+void PlatformDisplayPrivate::input_queue_created(AInputQueue *queue)
+{
+ MutexLock lock(event_mutex);
+ input_queue = queue;
+}
+
+void PlatformDisplayPrivate::input_queue_destroyed(AInputQueue *)
+{
+ MutexLock lock(event_mutex);
+ input_queue = 0;
+}
+} // namespace Graphics
+} // namespace Msp
--- /dev/null
+#ifndef MSP_GRAPHICS_DISPLAY_PLATFORM_H_
+#define MSP_GRAPHICS_DISPLAY_PLATFORM_H_
+
+#include <android/input.h>
+#include <android/native_window.h>
+#include <msp/core/mutex.h>
+#include "window_private.h"
+
+namespace Msp {
+namespace Graphics {
+
+typedef void *DisplayHandle;
+
+struct PlatformDisplayPrivate
+{
+ AInputQueue *input_queue;
+ std::list<Window::Event> events;
+ Mutex event_mutex;
+ WindowHandle native_window;
+ Mutex window_mutex;
+
+ void push_event(AndroidEventType);
+ void native_window_created(ANativeWindow *);
+ void native_window_resized(ANativeWindow *);
+ void native_window_destroyed(ANativeWindow *);
+ void input_queue_created(AInputQueue *);
+ void input_queue_destroyed(AInputQueue *);
+};
+
+} // namespace Graphics
+} // namespace Msp
+
+#endif
--- /dev/null
+#include "display.h"
+#include "display_private.h"
+#include "window.h"
+#include "window_private.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Graphics {
+
+void Window::platform_init()
+{
+ priv->window = 0;
+}
+
+void Window::platform_cleanup()
+{
+}
+
+void Window::set_title(const string &)
+{
+}
+
+void Window::platform_reconfigure(bool)
+{
+}
+
+void Window::show_cursor(bool)
+{
+}
+
+void Window::warp_pointer(int, int)
+{
+}
+
+void Window::platform_show()
+{
+}
+
+void Window::platform_hide()
+{
+}
+
+bool Window::event(const Event &evnt)
+{
+ switch(evnt.type)
+ {
+ case INPUT_EVENT:
+ signal_input_event.emit(evnt);
+ break;
+ case WINDOW_CREATED:
+ priv->window = display.get_private().native_window;
+ display.remove_window(*this);
+ display.add_window(*this);
+ priv->signal_window_acquired.emit(priv->window);
+ break;
+ case WINDOW_RESIZED:
+ options.width = ANativeWindow_getWidth(priv->window);
+ options.height = ANativeWindow_getHeight(priv->window);
+ signal_resize.emit(options.width, options.height);
+ break;
+ case WINDOW_DESTROYED:
+ priv->signal_window_lost.emit();
+ priv->window = 0;
+ display.remove_window(*this);
+ display.add_window(*this);
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace Graphics
+} // namespace Msp
--- /dev/null
+#ifndef MSP_GRAPHICS_WINDOW_PLATFORM_H_
+#define MSP_GRAPHICS_WINDOW_PLATFORM_H_
+
+#include <android/input.h>
+#include <android/native_window.h>
+
+namespace Msp {
+namespace Graphics {
+
+typedef ANativeWindow *WindowHandle;
+
+enum AndroidEventType
+{
+ INPUT_EVENT,
+ WINDOW_CREATED,
+ WINDOW_RESIZED,
+ WINDOW_DESTROYED
+};
+
+struct PlatformWindowPrivate
+{
+ mutable sigc::signal<void, WindowHandle> signal_window_acquired;
+ mutable sigc::signal<void> signal_window_lost;
+};
+
+struct PlatformEvent
+{
+ AndroidEventType type;
+ AInputEvent *aevent;
+};
+
+} // namespace Graphics
+} // namespace Msp
+
+#endif
--- /dev/null
+#include <vector>
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <android/native_window.h>
+#include "display.h"
+#include "glcontext.h"
+#include "window_private.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Graphics {
+
+struct GLContext::Private
+{
+ EGLDisplay display;
+ EGLConfig config;
+ EGLSurface surface;
+ EGLContext context;
+
+ void attach(WindowHandle);
+ void detach();
+};
+
+
+void GLContext::platform_init(const GLOptions &opts)
+{
+ EGLDisplay egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if(egl_display==EGL_NO_DISPLAY)
+ throw runtime_error("no egl display");
+
+ if(!eglInitialize(egl_display, 0, 0))
+ throw runtime_error("could not initialize egl");
+
+ vector<int> attribs;
+
+ attribs.push_back(EGL_COLOR_BUFFER_TYPE);
+ attribs.push_back(EGL_RGB_BUFFER);
+ attribs.push_back(EGL_RENDERABLE_TYPE);
+ attribs.push_back(EGL_OPENGL_ES2_BIT);
+ attribs.push_back(EGL_SURFACE_TYPE);
+ attribs.push_back(EGL_WINDOW_BIT);
+ attribs.push_back(EGL_DEPTH_SIZE);
+ attribs.push_back(1);
+
+ if(opts.alpha)
+ {
+ attribs.push_back(EGL_ALPHA_SIZE);
+ attribs.push_back(1);
+ }
+
+ if(opts.stencil)
+ {
+ attribs.push_back(EGL_STENCIL_SIZE);
+ attribs.push_back(1);
+ }
+
+ if(opts.multisample>0)
+ {
+ attribs.push_back(EGL_SAMPLE_BUFFERS);
+ attribs.push_back(1);
+ attribs.push_back(EGL_SAMPLES);
+ attribs.push_back(opts.multisample);
+ }
+
+ attribs.push_back(EGL_NONE);
+
+ EGLConfig config;
+ int num_configs;
+ if(!eglChooseConfig(egl_display, &attribs.front(), &config, 1, &num_configs))
+ {
+ eglTerminate(egl_display);
+ throw unsupported_gl_mode(opts);
+ }
+
+ priv = new Private;
+ priv->display = egl_display;
+ priv->config = config;
+
+ /* Must wait for the window to be available, because the calling code
+ expects GL to be usable after creation of the context. */
+ WindowHandle native_window;
+ const Window::Private &window_priv = window.get_private();
+ while(!(native_window = window_priv.window))
+ display.tick();
+
+ int format;
+ eglGetConfigAttrib(priv->display, config, EGL_NATIVE_VISUAL_ID, &format);
+ ANativeWindow_setBuffersGeometry(native_window, 0, 0, format);
+
+ priv->surface = eglCreateWindowSurface(priv->display, config, native_window, 0);
+
+ eglBindAPI(EGL_OPENGL_ES_API);
+ int context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+ priv->context = eglCreateContext(priv->display, config, 0, context_attribs);
+
+ eglMakeCurrent(priv->display, priv->surface, priv->surface, priv->context);
+
+ window_priv.signal_window_acquired.connect(sigc::mem_fun(priv, &Private::attach));
+ window_priv.signal_window_lost.connect(sigc::mem_fun(priv, &Private::detach));
+}
+
+GLContext::~GLContext()
+{
+ eglDestroyContext(priv->display, priv->context);
+ if(priv->surface!=EGL_NO_SURFACE)
+ eglDestroySurface(priv->display, priv->surface);
+ eglTerminate(priv->display);
+ delete priv;
+}
+
+void GLContext::swap_buffers()
+{
+ eglSwapBuffers(priv->display, priv->surface);
+}
+
+void GLContext::window_resized(unsigned w, unsigned h)
+{
+ glViewport(0, 0, w, h);
+}
+
+
+void GLContext::Private::attach(WindowHandle native_window)
+{
+ surface = eglCreateWindowSurface(display, config, native_window, 0);
+ eglMakeCurrent(display, surface, surface, context);
+}
+
+void GLContext::Private::detach()
+{
+ eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ eglDestroySurface(display, surface);
+}
+
+} // namespace Graphics
+} // namespace Msp
--- /dev/null
+#include <android/input.h>
+#include <msp/graphics/window_private.h>
+#include "keyboard.h"
+#include "keys_private.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Input {
+
+string Keyboard::get_button_name(unsigned btn) const
+{
+ return Device::get_button_name(btn);
+}
+
+void Keyboard::input_event(const Graphics::Window::Event &event)
+{
+ int type = AInputEvent_getType(event.aevent);
+ if(type!=AINPUT_EVENT_TYPE_KEY)
+ return;
+
+ int action = AKeyEvent_getAction(event.aevent);
+ switch(action)
+ {
+ case AKEY_EVENT_ACTION_DOWN:
+ case AKEY_EVENT_ACTION_UP:
+ if(unsigned key = key_from_sys(AKeyEvent_getKeyCode(event.aevent)))
+ set_button_state(key, action==AKEY_EVENT_ACTION_DOWN, true);
+ break;
+ default:;
+ }
+}
+
+} // namespace Input
+} // namespace Msp
--- /dev/null
+#include <android/keycodes.h>
+#include "keys.h"
+
+namespace Msp {
+namespace Input {
+
+unsigned sys_keymap[N_KEYS_] =
+{
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ AKEYCODE_DEL, AKEYCODE_TAB, AKEYCODE_ENTER, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, AKEYCODE_ESCAPE, 0, 0, 0, 0,
+
+ AKEYCODE_SPACE, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ AKEYCODE_0, AKEYCODE_1, AKEYCODE_2, AKEYCODE_3, AKEYCODE_4, AKEYCODE_5, AKEYCODE_6, AKEYCODE_7,
+ AKEYCODE_8, AKEYCODE_9, 0, 0, 0, 0, 0, 0,
+
+ 0, AKEYCODE_A, AKEYCODE_B, AKEYCODE_C, AKEYCODE_D, AKEYCODE_E, AKEYCODE_F, AKEYCODE_G,
+ AKEYCODE_H, AKEYCODE_I, AKEYCODE_J, AKEYCODE_K, AKEYCODE_L, AKEYCODE_M, AKEYCODE_N, AKEYCODE_O,
+ AKEYCODE_P, AKEYCODE_Q, AKEYCODE_R, AKEYCODE_S, AKEYCODE_T, AKEYCODE_U, AKEYCODE_V, AKEYCODE_W,
+ AKEYCODE_X, AKEYCODE_Y, AKEYCODE_Z, 0, 0, 0, 0, 0,
+
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+
+ AKEYCODE_SOFT_LEFT, AKEYCODE_SOFT_RIGHT, 0, 0, 0, 0, 0, 0,
+ AKEYCODE_MOVE_HOME, AKEYCODE_MOVE_END, AKEYCODE_PAGE_UP, AKEYCODE_PAGE_DOWN, AKEYCODE_INSERT, AKEYCODE_FORWARD_DEL, 0, 0,
+ 0, AKEYCODE_F1, AKEYCODE_F2, AKEYCODE_F3, AKEYCODE_F4, AKEYCODE_F5, AKEYCODE_F6, AKEYCODE_F7,
+ AKEYCODE_F8, AKEYCODE_F9, AKEYCODE_F10, AKEYCODE_F11, AKEYCODE_F12, 0, 0, 0,
+
+ AKEYCODE_SHIFT_LEFT, AKEYCODE_SHIFT_RIGHT, AKEYCODE_CTRL_LEFT, AKEYCODE_CTRL_RIGHT, AKEYCODE_ALT_LEFT, AKEYCODE_ALT_RIGHT, 0, 0,
+ AKEYCODE_CAPS_LOCK, AKEYCODE_SCROLL_LOCK, AKEYCODE_NUM_LOCK, 0, 0, 0, 0, 0,
+ AKEYCODE_NUMPAD_0, AKEYCODE_NUMPAD_1, AKEYCODE_NUMPAD_2, AKEYCODE_NUMPAD_3, AKEYCODE_NUMPAD_4, AKEYCODE_NUMPAD_5, AKEYCODE_NUMPAD_6, AKEYCODE_NUMPAD_7,
+ AKEYCODE_NUMPAD_8, AKEYCODE_NUMPAD_9, AKEYCODE_NUMPAD_ADD, AKEYCODE_NUMPAD_SUBTRACT, AKEYCODE_NUMPAD_MULTIPLY, AKEYCODE_NUMPAD_DIVIDE, AKEYCODE_NUMPAD_DOT, 0,
+
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+} // namespace Input
+} // namespace Msp
--- /dev/null
+#include <msp/graphics/window_private.h>
+#include "mouse.h"
+
+namespace Msp {
+namespace Input {
+
+void Mouse::input_event(const Graphics::Window::Event &event)
+{
+ int type = AInputEvent_getType(event.aevent);
+ if(type!=AINPUT_EVENT_TYPE_MOTION)
+ return;
+
+ /* Emulate a mouse with the touch events of a single finger. If more
+ fingers appear while the first one is held down, they are ignored, even if
+ the first finger is released. */
+
+ int action = AMotionEvent_getAction(event.aevent);
+ int action_pointer = (action&AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)>>AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+ action &= AMOTION_EVENT_ACTION_MASK;
+
+ int pointer_count = AMotionEvent_getPointerCount(event.aevent);
+ int pointer_zero = -1;
+ for(int i=0; (i<pointer_count && pointer_zero<0); ++i)
+ if(AMotionEvent_getPointerId(event.aevent, i)==0)
+ pointer_zero = i;
+
+ if(pointer_zero>=0)
+ {
+ float x = AMotionEvent_getX(event.aevent, pointer_zero);
+ float y = AMotionEvent_getY(event.aevent, pointer_zero);
+ set_axis_value(0, x*2/window.get_width()-1, true);
+ set_axis_value(1, 1-y*2/window.get_height(), true);
+ }
+
+ switch(action)
+ {
+ case AMOTION_EVENT_ACTION_DOWN:
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ case AMOTION_EVENT_ACTION_POINTER_UP:
+ if(action_pointer==0)
+ set_button_state(1, action==AMOTION_EVENT_ACTION_DOWN, true);
+ break;
+ default:;
+ }
+}
+
+} // namespace Input
+} // namespace Msp