├── .gitignore ├── EntrypointNativeActivity.java ├── LICENSE ├── README.md ├── entrypoint.h ├── entrypoint_android.c ├── entrypoint_android.h ├── entrypoint_config.h ├── entrypoint_emscripten.c ├── entrypoint_ios.h ├── entrypoint_ios.m ├── entrypoint_osx.m ├── entrypoint_win.c └── example.c /.gitignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | build.* 3 | !build.fox 4 | Makefile 5 | .ninja_* 6 | -------------------------------------------------------------------------------- /EntrypointNativeActivity.java: -------------------------------------------------------------------------------- 1 | // this is just an example 2 | // you will most likely need more stuff to make it work 3 | 4 | package com.beardsvibe; 5 | 6 | import android.content.Intent; 7 | import android.net.Uri; 8 | 9 | public class EntrypointNativeActivity extends android.app.NativeActivity { 10 | public static void openURL(String url) { 11 | activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Dmytro Ivanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # entrypoint 2 | 3 | Lightweight entry point for games. 4 | 5 | ### Why ? 6 | 7 | - [SDL](https://www.libsdl.org/) is too heavy. 8 | - [GLFW](http://www.glfw.org/) is way too OpenGL oriented. 9 | - [TIGR](https://bitbucket.org/rmitton/tigr/src) is not developing :( 10 | 11 | So let's take TIGR as a baseline, offload all rendering part to something like [bgfx](https://github.com/bkaradzic/bgfx) and add iOS/Android support. 12 | 13 | ### Supported platforms 14 | 15 | - Windows 16 | - macOS 17 | - iOS 18 | - Emscripten (a bit rough) 19 | - Android 20 | 21 | ### TODO 22 | 23 | - GamePad input 24 | - Mouse wheel 25 | - iOS keyboard (at least partially) 26 | - MessageBox/allert 27 | - X11 28 | - Wayland 29 | - Small casual OpenGL renderer 30 | 31 | ### iOS 32 | 33 | For iOS just create a new project, and replace everything there with entrypoint, you don't need UI files. 34 | 35 | ### Android 36 | 37 | Use android native activity sample as project template. 38 | 39 | ### Additional 40 | 41 | This library very loosely based on TIGR. 42 | 43 | This is free and unencumbered software released into the public domain. 44 | 45 | Our intent is that anyone is free to copy and use this software, 46 | for any purpose, in any form, and by any means. 47 | 48 | The authors dedicate any and all copyright interest in the software 49 | to the public domain, at their own expense for the betterment of mankind. 50 | 51 | The software is provided "as is", without any kind of warranty, including 52 | any implied warranty. If it breaks, you get to keep both pieces. 53 | -------------------------------------------------------------------------------- /entrypoint.h: -------------------------------------------------------------------------------- 1 | // lightweight entry point for games 2 | // 3 | // how to use: 4 | // - instead of main(...) use int entrypoint_init(int argc, char * argv[]) in your app 5 | // - when you will get control, expect a window to be created and valid 6 | // - don't create run loop on your own (it's simply not possible on some platforms) 7 | // - instead provide entrypoint_loop for loop tick (do updating and rendering there) 8 | // - add entrypoint_deinit for loop finish, but don't expect it to be called on all platforms 9 | // - to save critical data implement entrypoint_might_unload 10 | // 11 | // so yes: 12 | // - this lib does overrides main :( 13 | // - this lib does take control over runloop 14 | // - but hey, on platforms like iOS, Android and Emscripten you don't normally have it anyway 15 | 16 | #pragma once 17 | 18 | #include "entrypoint_config.h" 19 | #include 20 | #include 21 | 22 | #ifdef __cplusplus 23 | extern "C" { 24 | #endif 25 | 26 | // ----------------------------------------------------------------------------- 27 | // please implement this functions in your code 28 | 29 | // initialize your application, returns error code, 0 is ok, != 0 if failure 30 | int32_t entrypoint_init(int32_t argc, char * argv[]); 31 | 32 | // deinitialize your application, returns error code, 0 is ok, != 0 if failure 33 | // don't rely on this to be called because it's not feasible on some platforms 34 | int32_t entrypoint_deinit(); 35 | 36 | // will be called if we application goes into background and might be terminated there 37 | // aka "applicationWillResignActive" and "beforeunload" 38 | // you should save you critical data here as fast as possible, and please don't block 39 | int32_t entrypoint_might_unload(); 40 | 41 | // frame tick for your application, 0 is ok, != 0 if want to close the application 42 | // depending on a platform might be called as fast as possible, or connected to VSYNC 43 | int32_t entrypoint_loop(); 44 | 45 | // ----------------------------------------------------------------------------- 46 | 47 | // you can get all platform handles from ctx 48 | // see entrypoint_ctx_t definition at the bottom of this file 49 | typedef struct entrypoint_ctx_t entrypoint_ctx_t; 50 | entrypoint_ctx_t * ep_ctx(); 51 | 52 | // get size of the window 53 | typedef struct ep_size_t {uint16_t w, h;} ep_size_t; 54 | ep_size_t ep_size(); 55 | 56 | // is current platform built with retina support 57 | bool ep_retina(); 58 | 59 | // get UI margins in pixels, UI elements should not be placed with-in X pixels from screen size 60 | // this is needed for iPhone X and similar devices 61 | typedef struct ep_ui_margins_t {uint16_t l, t, r, b;} ep_ui_margins_t; // left, top, right, bottom 62 | ep_ui_margins_t ep_ui_margins(); 63 | 64 | // ----------------------------------------------------------------------------- 65 | 66 | #ifdef ENTRYPOINT_PROVIDE_TIME 67 | 68 | // returns the amount of time elapsed since ep_delta_time was last called in seconds, or 0 first time 69 | double ep_delta_time(); 70 | 71 | // sleep for provided amount of seconds, not available on emscripten 72 | void ep_sleep(double seconds); 73 | 74 | #endif 75 | 76 | // ----------------------------------------------------------------------------- 77 | 78 | #ifdef ENTRYPOINT_PROVIDE_LOG 79 | 80 | // on all platforms except android you can simply and safely use printf 81 | // use this function if you want consistent logging on all platforms 82 | void ep_log(const char * message, ...); 83 | 84 | #endif 85 | 86 | // ----------------------------------------------------------------------------- 87 | 88 | #ifdef ENTRYPOINT_PROVIDE_INPUT 89 | 90 | // mouse/touch stuff 91 | typedef struct 92 | { 93 | // normal mouse and one finger touch 94 | float x, y; 95 | union 96 | { 97 | uint8_t flags; 98 | struct 99 | { 100 | uint8_t left: 1; 101 | uint8_t middle: 1; 102 | uint8_t right: 1; 103 | }; 104 | }; 105 | 106 | // multitouch feature 107 | struct 108 | { 109 | void * context; 110 | float x, y; 111 | uint8_t touched; 112 | } multitouch[ENTRYPOINT_MAX_MULTITOUCH]; 113 | 114 | // accelerometer data, if available 115 | float acc_x, acc_y, acc_z; 116 | 117 | // TODO mouse wheel, additional buttons 118 | } ep_touch_t; 119 | void ep_touch(ep_touch_t * touch); 120 | 121 | // keys stuff 122 | // key scancodes, for letters/numbers, use ASCII ('A'-'Z' and '0'-'9'). 123 | typedef enum { 124 | EK_PAD0=128,EK_PAD1,EK_PAD2,EK_PAD3,EK_PAD4,EK_PAD5,EK_PAD6,EK_PAD7,EK_PAD8,EK_PAD9, 125 | EK_PADMUL,EK_PADADD,EK_PADENTER,EK_PADSUB,EK_PADDOT,EK_PADDIV, 126 | EK_F1,EK_F2,EK_F3,EK_F4,EK_F5,EK_F6,EK_F7,EK_F8,EK_F9,EK_F10,EK_F11,EK_F12, 127 | EK_BACKSPACE,EK_TAB,EK_RETURN,EK_ALT,EK_PAUSE,EK_CAPSLOCK, 128 | EK_ESCAPE,EK_SPACE,EK_PAGEUP,EK_PAGEDN,EK_END,EK_HOME,EK_LEFT,EK_UP,EK_RIGHT,EK_DOWN, 129 | EK_INSERT,EK_DELETE,EK_LWIN,EK_RWIN,EK_NUMLOCK,EK_SCROLL,EK_LSHIFT,EK_RSHIFT, 130 | EK_LCONTROL,EK_RCONTROL,EK_LALT,EK_RALT,EK_SEMICOLON,EK_EQUALS,EK_COMMA,EK_MINUS, 131 | EK_DOT,EK_SLASH,EK_BACKTICK,EK_LSQUARE,EK_BACKSLASH,EK_RSQUARE,EK_TICK 132 | } ep_key_t; 133 | 134 | // reads the keyboard from a window, returns non-zero if a key is hit or pressed 135 | // key hit tests for the initial press, key down repeats each frame 136 | bool ep_khit(int32_t key); 137 | bool ep_kdown(int32_t key); 138 | // reads character input for a window, returns the UTF-32 value of the last key pressed, or 0 if none 139 | uint32_t ep_kchar(); 140 | 141 | // TODO iOS style keyboard input 142 | 143 | // TODO gamepad 144 | 145 | #endif 146 | 147 | // ----------------------------------------------------------------------------- 148 | 149 | #ifdef ENTRYPOINT_PROVIDE_OPENURL 150 | 151 | // open url in native browser 152 | void ep_openurl(const char * url); 153 | 154 | #endif 155 | 156 | // ----------------------------------------------------------------------------- 157 | // whole ctx structure is usually not needed and it's quite bulky because of system headers 158 | // so if you need it - define ENTRYPOINT_CTX before including this file 159 | #ifdef ENTRYPOINT_CTX 160 | 161 | #ifdef _WIN32 162 | #define WIN32_LEAN_AND_MEAN 163 | #include 164 | #elif defined(__APPLE__) 165 | #include 166 | #include 167 | 168 | // use the following: 169 | // TARGET_OS_IPHONE 170 | // TARGET_OS_OSX 171 | 172 | #ifndef TARGET_OS_OSX 173 | //#warning TARGET_OS_OSX is not available, simulating it 174 | #define TARGET_OS_OSX (!(TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH)) 175 | #endif 176 | #elif defined(EMSCRIPTEN) 177 | #include 178 | #elif defined(__ANDROID__) 179 | #include 180 | #include 181 | #include 182 | #endif 183 | 184 | struct entrypoint_ctx_t 185 | { 186 | // ------------------------------------------------------------------------- 187 | // arguments 188 | 189 | int argc; 190 | char ** argv; 191 | 192 | // ------------------------------------------------------------------------- 193 | // platform stuff 194 | 195 | #ifdef _WIN32 196 | // entry point data: 197 | // - hInstance is GetModuleHandle(0) 198 | // - hPrevInstance is always NULL 199 | // - lpCmdLine can be get as GetCommandLineA() 200 | // - nCmdShow is wShowWindow in GetStartupInfoA(STARTUPINFOA*) 201 | 202 | HWND hwnd; // window handle 203 | RECT rect; // current window rect 204 | RECT rect_saved; // saved window rect (for fullscreen<->windowed transitions) 205 | DWORD dwStyle; // current window style 206 | 207 | #ifdef ENTRYPOINT_PROVIDE_TIME 208 | LARGE_INTEGER prev_qpc_time; 209 | #endif 210 | 211 | union 212 | { 213 | uint8_t flags; 214 | struct 215 | { 216 | uint8_t flag_want_to_close: 1; 217 | uint8_t flag_borderless: 1; 218 | #ifdef ENTRYPOINT_PROVIDE_TIME 219 | uint8_t flag_time_set: 1; 220 | #endif 221 | }; 222 | }; 223 | #elif defined(__APPLE__) 224 | #if TARGET_OS_OSX 225 | void * window; // NSWindow 226 | uint32_t window_count; 227 | bool terminated; 228 | #elif TARGET_OS_IOS 229 | void * view; // EntryPointView: UIView 230 | void * caeagllayer; // EntryPointView.layer 231 | uint16_t view_w, view_h; 232 | union 233 | { 234 | uint8_t flags; 235 | struct 236 | { 237 | uint8_t flag_failed_to_init: 1; 238 | uint8_t flag_anim: 1; 239 | }; 240 | }; 241 | #endif 242 | 243 | #ifdef ENTRYPOINT_PROVIDE_TIME 244 | uint64_t prev_time; 245 | mach_timebase_info_data_t timebase_info; 246 | #endif 247 | #elif defined(EMSCRIPTEN) 248 | union 249 | { 250 | uint8_t flags; 251 | struct 252 | { 253 | uint8_t flag_want_to_close: 1; 254 | #ifdef ENTRYPOINT_PROVIDE_TIME 255 | uint8_t flag_time_set: 1; 256 | #endif 257 | }; 258 | }; 259 | #ifdef ENTRYPOINT_PROVIDE_TIME 260 | struct timeval prev_time; 261 | #endif 262 | #elif defined(__ANDROID__) 263 | struct android_app * app; 264 | ANativeWindow * window; 265 | void * jni_env_entrypoint_thread; 266 | void * jni_env_main_thread; 267 | uint16_t view_w, view_h; 268 | pthread_mutex_t mutex; 269 | pthread_t thread; 270 | void * j_class_loader; // needed for JNI 271 | void * j_find_class_method_id; 272 | union 273 | { 274 | uint8_t flags; 275 | struct 276 | { 277 | uint8_t flag_want_to_close: 1; 278 | #ifdef ENTRYPOINT_PROVIDE_TIME 279 | uint8_t flag_time_set: 1; 280 | #endif 281 | uint8_t thread_running: 1; 282 | }; 283 | }; 284 | #ifdef ENTRYPOINT_PROVIDE_TIME 285 | struct timeval prev_time; 286 | #endif 287 | #endif 288 | 289 | // ------------------------------------------------------------------------- 290 | // input 291 | 292 | #ifdef ENTRYPOINT_PROVIDE_INPUT 293 | uint32_t last_char; 294 | char keys[256], prev[256]; 295 | #if defined(__APPLE__) || defined(EMSCRIPTEN) || defined(__ANDROID__) 296 | ep_touch_t touch; 297 | #endif 298 | #endif 299 | 300 | }; 301 | 302 | #endif 303 | 304 | // ----------------------------------------------------------------------------- 305 | 306 | #ifdef __cplusplus 307 | } 308 | #endif 309 | -------------------------------------------------------------------------------- /entrypoint_android.c: -------------------------------------------------------------------------------- 1 | 2 | #ifdef __ANDROID__ 3 | 4 | // partially based on bgfx entry_android.cpp 5 | 6 | #define ENTRYPOINT_CTX 7 | #include "entrypoint.h" 8 | #include "entrypoint_android.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include // just so we don't need to build it 18 | 19 | static entrypoint_ctx_t ctx = {0}; 20 | entrypoint_ctx_t * ep_ctx() {return &ctx;} 21 | 22 | // ----------------------------------------------------------------------------- 23 | 24 | ep_size_t ep_size() 25 | { 26 | ep_size_t r = {ctx.view_w, ctx.view_h}; 27 | return r; 28 | } 29 | 30 | bool ep_retina() 31 | { 32 | return false; // TODO 33 | } 34 | 35 | ep_ui_margins_t ep_ui_margins() 36 | { 37 | // TODO some phones actually have curved screen, how do we detect that? 38 | ep_ui_margins_t r = {0, 0, 0, 0}; 39 | return r; 40 | } 41 | 42 | // ----------------------------------------------------------------------------- 43 | 44 | static void * entrypoint_thread(void * param); 45 | 46 | static void on_app_cmd(struct android_app * app, int32_t cmd) 47 | { 48 | switch(cmd) 49 | { 50 | case APP_CMD_INPUT_CHANGED: 51 | // Command from main thread: the AInputQueue has changed. Upon processing 52 | // this command, android_app->inputQueue will be updated to the new queue 53 | // (or NULL). 54 | break; 55 | 56 | case APP_CMD_INIT_WINDOW: 57 | // Command from main thread: a new ANativeWindow is ready for use. Upon 58 | // receiving this command, android_app->window will contain the new window 59 | // surface. 60 | if(ctx.window != app->window) 61 | { 62 | ctx.window = app->window; 63 | pthread_mutex_lock(&ctx.mutex); 64 | ctx.view_w = ANativeWindow_getWidth(ctx.window); 65 | ctx.view_h = ANativeWindow_getHeight(ctx.window); 66 | pthread_mutex_unlock(&ctx.mutex); 67 | 68 | if(!ctx.thread_running) 69 | { 70 | ctx.thread_running = true; 71 | pthread_create(&ctx.thread, NULL, entrypoint_thread, NULL); 72 | } 73 | } 74 | break; 75 | 76 | case APP_CMD_TERM_WINDOW: 77 | // Command from main thread: the existing ANativeWindow needs to be 78 | // terminated. Upon receiving this command, android_app->window still 79 | // contains the existing window; after calling android_app_exec_cmd 80 | // it will be set to NULL. 81 | pthread_mutex_lock(&ctx.mutex); 82 | ctx.window = NULL; 83 | pthread_mutex_unlock(&ctx.mutex); 84 | break; 85 | 86 | case APP_CMD_WINDOW_RESIZED: 87 | // Command from main thread: the current ANativeWindow has been resized. 88 | // Please redraw with its new size. 89 | break; 90 | 91 | case APP_CMD_WINDOW_REDRAW_NEEDED: 92 | // Command from main thread: the system needs that the current ANativeWindow 93 | // be redrawn. You should redraw the window before handing this to 94 | // android_app_exec_cmd() in order to avoid transient drawing glitches. 95 | break; 96 | 97 | case APP_CMD_CONTENT_RECT_CHANGED: 98 | // Command from main thread: the content area of the window has changed, 99 | // such as from the soft input window being shown or hidden. You can 100 | // find the new content rect in android_app::contentRect. 101 | break; 102 | 103 | case APP_CMD_GAINED_FOCUS: 104 | // Command from main thread: the app's activity window has gained 105 | // input focus. 106 | break; 107 | 108 | case APP_CMD_LOST_FOCUS: 109 | // Command from main thread: the app's activity window has lost 110 | // input focus. 111 | pthread_mutex_lock(&ctx.mutex); 112 | entrypoint_might_unload(); 113 | pthread_mutex_unlock(&ctx.mutex); 114 | break; 115 | 116 | case APP_CMD_CONFIG_CHANGED: 117 | // Command from main thread: the current device configuration has changed. 118 | break; 119 | 120 | case APP_CMD_LOW_MEMORY: 121 | // Command from main thread: the system is running low on memory. 122 | // Try to reduce your memory use. 123 | pthread_mutex_lock(&ctx.mutex); 124 | entrypoint_might_unload(); 125 | pthread_mutex_unlock(&ctx.mutex); 126 | break; 127 | 128 | case APP_CMD_START: 129 | // Command from main thread: the app's activity has been started. 130 | break; 131 | 132 | case APP_CMD_RESUME: 133 | // Command from main thread: the app's activity has been resumed. 134 | break; 135 | 136 | case APP_CMD_SAVE_STATE: 137 | // Command from main thread: the app should generate a new saved state 138 | // for itself, to restore from later if needed. If you have saved state, 139 | // allocate it with malloc and place it in android_app.savedState with 140 | // the size in android_app.savedStateSize. The will be freed for you 141 | // later. 142 | 143 | pthread_mutex_lock(&ctx.mutex); 144 | entrypoint_might_unload(); 145 | pthread_mutex_unlock(&ctx.mutex); 146 | break; 147 | 148 | case APP_CMD_PAUSE: 149 | // Command from main thread: the app's activity has been paused. 150 | pthread_mutex_lock(&ctx.mutex); 151 | entrypoint_might_unload(); 152 | pthread_mutex_unlock(&ctx.mutex); 153 | break; 154 | 155 | case APP_CMD_STOP: 156 | // Command from main thread: the app's activity has been stopped. 157 | break; 158 | 159 | case APP_CMD_DESTROY: 160 | // Command from main thread: the app's activity is being destroyed, 161 | // and waiting for the app thread to clean up and exit before proceeding. 162 | break; 163 | } 164 | } 165 | 166 | #ifdef ENTRYPOINT_PROVIDE_INPUT 167 | int32_t on_input_event(struct android_app *_app, AInputEvent *_event) 168 | { 169 | const int32_t type = AInputEvent_getType(_event); 170 | const int32_t source = AInputEvent_getSource(_event); 171 | const int32_t actionBits = AMotionEvent_getAction(_event); 172 | 173 | switch (type) 174 | { 175 | case AINPUT_EVENT_TYPE_MOTION: 176 | { 177 | //pthread_mutex_lock(&ctx.mutex); 178 | 179 | int32_t touch_count = AMotionEvent_getPointerCount(_event); 180 | if(touch_count > ENTRYPOINT_MAX_MULTITOUCH) 181 | touch_count = ENTRYPOINT_MAX_MULTITOUCH; 182 | 183 | for(int32_t touch = 0; touch < touch_count; ++touch) 184 | { 185 | ctx.touch.multitouch[touch].x = AMotionEvent_getX(_event, touch); 186 | ctx.touch.multitouch[touch].y = AMotionEvent_getY(_event, touch); 187 | } 188 | 189 | int32_t action = (actionBits & AMOTION_EVENT_ACTION_MASK); 190 | int32_t index = (actionBits & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; 191 | 192 | if(index < ENTRYPOINT_MAX_MULTITOUCH) 193 | { 194 | if(action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_POINTER_DOWN) 195 | ctx.touch.multitouch[index].touched = 1; 196 | else if(action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_POINTER_UP) 197 | ctx.touch.multitouch[index].touched = 0; 198 | } 199 | 200 | ctx.touch.x = ctx.touch.multitouch[0].x; 201 | ctx.touch.y = ctx.touch.multitouch[0].y; 202 | ctx.touch.left = ctx.touch.multitouch[0].touched; 203 | 204 | //pthread_mutex_unlock(&ctx.mutex); 205 | } 206 | break; 207 | 208 | default: 209 | break; 210 | } 211 | 212 | return 0; 213 | } 214 | #endif 215 | 216 | void * entrypoint_thread(void * param) 217 | { 218 | JNIEnv * jni_env_entrypoint_thread = NULL; 219 | (*ctx.app->activity->vm)->AttachCurrentThread(ctx.app->activity->vm, &jni_env_entrypoint_thread, NULL); 220 | ctx.jni_env_entrypoint_thread = jni_env_entrypoint_thread; 221 | 222 | if(entrypoint_init(ctx.argc, ctx.argv)) 223 | { 224 | (*ctx.app->activity->vm)->DetachCurrentThread(ctx.app->activity->vm); 225 | return NULL; 226 | } 227 | 228 | while(!ctx.flag_want_to_close) 229 | { 230 | pthread_mutex_lock(&ctx.mutex); 231 | if(entrypoint_loop()) 232 | { 233 | pthread_mutex_unlock(&ctx.mutex); 234 | break; 235 | } 236 | pthread_mutex_unlock(&ctx.mutex); 237 | } 238 | 239 | if(entrypoint_might_unload()) // TODO is this needed ? 240 | { 241 | (*ctx.app->activity->vm)->DetachCurrentThread(ctx.app->activity->vm); 242 | return NULL; 243 | } 244 | 245 | if(entrypoint_deinit()) 246 | { 247 | (*ctx.app->activity->vm)->DetachCurrentThread(ctx.app->activity->vm); 248 | return NULL; 249 | } 250 | 251 | (*ctx.app->activity->vm)->DetachCurrentThread(ctx.app->activity->vm); 252 | return NULL; 253 | } 254 | 255 | void android_main(struct android_app * app) 256 | { 257 | const char * argv[1] = { "android.so" }; 258 | ctx.argc = 1; 259 | ctx.argv = (char**)argv; 260 | ctx.app = app; 261 | 262 | app->userData = &ctx; 263 | ctx.app->onAppCmd = on_app_cmd; 264 | #ifdef ENTRYPOINT_PROVIDE_INPUT 265 | ctx.app->onInputEvent = on_input_event; 266 | #endif 267 | 268 | ANativeActivity_setWindowFlags(ctx.app->activity, AWINDOW_FLAG_FULLSCREEN | AWINDOW_FLAG_KEEP_SCREEN_ON, 0); 269 | 270 | JNIEnv * jni_env_main_thread = NULL; 271 | (*ctx.app->activity->vm)->AttachCurrentThread(ctx.app->activity->vm, &jni_env_main_thread, NULL); 272 | ctx.jni_env_main_thread = jni_env_main_thread; 273 | 274 | { 275 | JNIEnv * env = ctx.jni_env_main_thread; 276 | 277 | jobject na_class = ctx.app->activity->clazz; 278 | jclass acl = (*env)->GetObjectClass(env, na_class); 279 | jmethodID get_class_loader = (*env)->GetMethodID(env, acl, "getClassLoader", "()Ljava/lang/ClassLoader;"); 280 | jobject cl_obj = (*env)->CallObjectMethod(env, na_class, get_class_loader); 281 | ctx.j_class_loader = (void*)((*env)->NewWeakGlobalRef(env, cl_obj)); 282 | 283 | jclass class_loader = (*env)->FindClass(env, "java/lang/ClassLoader"); 284 | ctx.j_find_class_method_id = (void*)((*env)->GetMethodID(env, class_loader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;")); 285 | } 286 | 287 | ENTRYPOINT_ANDROID_PREPARE_PARAMS; 288 | 289 | pthread_mutex_init(&ctx.mutex, NULL); 290 | 291 | int poll_events = 0; 292 | struct android_poll_source * poll_source = NULL; 293 | while(!ctx.app->destroyRequested) 294 | { 295 | // TODO do we want one or all events? 296 | ALooper_pollAll(-1, NULL, &poll_events, (void**)&poll_source); 297 | 298 | if(poll_source) 299 | poll_source->process(ctx.app, poll_source); 300 | } 301 | 302 | ctx.flag_want_to_close = 1; 303 | pthread_join(ctx.thread, NULL); 304 | 305 | pthread_mutex_destroy(&ctx.mutex); 306 | 307 | // TODO do we need this? 308 | (*ctx.app->activity->vm)->DetachCurrentThread(ctx.app->activity->vm); 309 | 310 | return; 311 | } 312 | 313 | // ----------------------------------------------------------------------------- 314 | #ifdef ENTRYPOINT_PROVIDE_TIME 315 | 316 | double ep_delta_time() 317 | { 318 | if(!ctx.flag_time_set) 319 | { 320 | gettimeofday(&ctx.prev_time, NULL); 321 | ctx.flag_time_set = true; 322 | return 0.0; 323 | } 324 | 325 | struct timeval now; 326 | gettimeofday(&now, NULL); 327 | double elapsed = (now.tv_sec - ctx.prev_time.tv_sec) + ((now.tv_usec - ctx.prev_time.tv_usec) / 1000000.0); 328 | ctx.prev_time = now; 329 | return elapsed; 330 | } 331 | 332 | void ep_sleep(double seconds) 333 | { 334 | usleep((useconds_t)(seconds * 1000000.0)); 335 | } 336 | 337 | #endif 338 | // ----------------------------------------------------------------------------- 339 | #ifdef ENTRYPOINT_PROVIDE_LOG 340 | 341 | void ep_log(const char * message, ...) 342 | { 343 | va_list args; 344 | va_start(args, message); 345 | __android_log_vprint(ANDROID_LOG_INFO, ENTRYPOINT_ANDROID_LOG_TAG, message, args); 346 | va_end(args); 347 | } 348 | 349 | #endif 350 | // ----------------------------------------------------------------------------- 351 | #ifdef ENTRYPOINT_PROVIDE_INPUT 352 | 353 | void ep_touch(ep_touch_t * touch) 354 | { 355 | if(touch) 356 | *touch = ctx.touch; 357 | } 358 | 359 | bool ep_khit(int32_t key) {return false;} 360 | bool ep_kdown(int32_t key) {return false;} 361 | uint32_t ep_kchar() {return 0;} 362 | 363 | #endif 364 | // ----------------------------------------------------------------------------- 365 | #ifdef ENTRYPOINT_PROVIDE_OPENURL 366 | 367 | void ep_openurl(const char * url) 368 | { 369 | ep_jni_method_t r = ep_get_static_java_method("com/beardsvibe/EntrypointNativeActivity", "openURL", "(Ljava/lang/String;)V"); 370 | if(r.found) 371 | { 372 | JNIEnv * e = (JNIEnv*)r.env; 373 | 374 | jstring j_str = (*e)->NewStringUTF(e, url); 375 | (*e)->CallStaticVoidMethod(r.env, r.class_id, r.method_id, j_str); 376 | (*e)->DeleteLocalRef(e, j_str); 377 | } 378 | ep_get_static_java_method_clear(r); 379 | } 380 | 381 | #endif 382 | // ----------------------------------------------------------------------------- 383 | 384 | // jni helper is loosely based on cocos2d-x 385 | ep_jni_method_t ep_get_static_java_method(const char * class_name, const char * method_name, const char * param_code) 386 | { 387 | ep_jni_method_t r = {0}; 388 | JNIEnv * env = ctx.jni_env_entrypoint_thread; 389 | if(!class_name || !method_name || !param_code || !env) 390 | return r; 391 | 392 | jobject j_class_loader = (jobject)ctx.j_class_loader; 393 | jmethodID j_find_class_method_id = (jmethodID)ctx.j_find_class_method_id; 394 | 395 | jstring class_jstr = (*env)->NewStringUTF(env, class_name); 396 | jclass class_id = (jclass)((*env)->CallObjectMethod(env, j_class_loader, j_find_class_method_id, class_jstr)); 397 | if(!class_id) { 398 | (*env)->ExceptionClear(env); 399 | (*env)->DeleteLocalRef(env, class_jstr); 400 | return r; 401 | } 402 | (*env)->DeleteLocalRef(env, class_jstr); 403 | 404 | jmethodID method_id = (*env)->GetStaticMethodID(env, class_id, method_name, param_code); 405 | if(!method_id) { 406 | (*env)->ExceptionClear(env); 407 | return r; 408 | } 409 | 410 | r.found = true; 411 | r.env = (void*)env; 412 | r.class_id = (void*)class_id; 413 | r.method_id = (void*)method_id; 414 | return r; 415 | } 416 | 417 | void ep_get_static_java_method_clear(ep_jni_method_t method) 418 | { 419 | if(!method.found) 420 | return; 421 | 422 | (*((JNIEnv*)method.env))->DeleteLocalRef(method.env, method.class_id); 423 | } 424 | 425 | #endif 426 | -------------------------------------------------------------------------------- /entrypoint_android.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __ANDROID__ 4 | 5 | #include "entrypoint_config.h" 6 | #include 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | typedef struct 13 | { 14 | bool found; 15 | void * env; 16 | void * class_id; 17 | void * method_id; 18 | } ep_jni_method_t; 19 | 20 | ep_jni_method_t ep_get_static_java_method(const char * class_name, const char * method_name, const char * param_code); 21 | void ep_get_static_java_method_clear(ep_jni_method_t method); 22 | 23 | #ifdef __cplusplus 24 | } 25 | #endif 26 | 27 | #endif -------------------------------------------------------------------------------- /entrypoint_config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // configuration for entrypoint lib 4 | 5 | // ----------------------------------------------------------------------------- 6 | // general 7 | 8 | #define ENTRYPOINT_PROVIDE_TIME 9 | #define ENTRYPOINT_PROVIDE_LOG 10 | #define ENTRYPOINT_PROVIDE_INPUT 11 | #define ENTRYPOINT_PROVIDE_OPENURL 12 | 13 | // ----------------------------------------------------------------------------- 14 | // detailed configs 15 | #define ENTRYPOINT_MAX_MULTITOUCH 16 16 | 17 | // ----------------------------------------------------------------------------- 18 | // Windows 19 | 20 | // adds an additional linker flags to use windows subsystem, aka don't create console window 21 | //#define ENTRYPOINT_WINDOWS_SUBSYSTEM 22 | 23 | // forces WinMain 24 | //#define ENTRYPOINT_WINDOWS_WINMAIN 25 | 26 | // if we use WinMain we need Shell32.lib and calloc to get argc/argv, disable this if you don't need them 27 | //#define ENTRYPOINT_WINDOWS_NEED_ARGS 28 | 29 | // add stuff to the linker options, disable this if you plan to provide all flags by yourself 30 | #define ENTRYPOINT_WINDOWS_LINKER_OPT 31 | 32 | // this will be called before following parameters are used 33 | // you might want to use this to read some config file or something 34 | #define ENTRYPOINT_WINDOWS_PREPARE_PARAMS {} 35 | #define ENTRYPOINT_WINDOWS_CLASS L"entrypoint" 36 | #define ENTRYPOINT_WINDOWS_TITLE L"test window" 37 | #define ENTRYPOINT_WINDOWS_WIDTH 1024 38 | #define ENTRYPOINT_WINDOWS_HEIGHT 768 39 | 40 | #define ENTRYPOINT_WINDOWS_MIN_WIDTH 128 41 | #define ENTRYPOINT_WINDOWS_MIN_HEIGHT 128 42 | 43 | // usually not needed for games, but good option for non games to fill empty areas during resize 44 | //#define ENTRYPOINT_WINDOWS_DO_WMPAINT 45 | 46 | // ----------------------------------------------------------------------------- 47 | // macOS 48 | 49 | #define ENTRYPOINT_MACOS_PREPARE_PARAMS {} 50 | #define ENTRYPOINT_MACOS_TITLE @"test window" 51 | #define ENTRYPOINT_MACOS_WIDTH 1024 52 | #define ENTRYPOINT_MACOS_HEIGHT 768 53 | #define ENTRYPOINT_MACOS_START_X 20 54 | #define ENTRYPOINT_MACOS_START_Y 20 55 | 56 | // disable this if you don't want retina support 57 | //#define ENTRYPOINT_MACOS_RETINA 58 | 59 | // ----------------------------------------------------------------------------- 60 | // iOS 61 | 62 | // if you wish we can provide an app delegate for you, it will have UIWindow->UIViewController->UIView with your game 63 | // and the name of app delegate class will be EntryPointAppDelegate 64 | // but please remember that you will need to provide either launch image or launch storyboard to get proper size viewport 65 | // otherwise iOS will think like this is iPhone4 app and will limit your screensize 66 | #define ENTRYPOINT_IOS_APPDELEGATE 67 | 68 | // if you would like we can define a main for you, just please provide us with your app delegate class name as a string 69 | #define ENTRYPOINT_IOS_MAIN 70 | #define ENTRYPOINT_IOS_MAIN_APPDELEGATE_CLASSNAME @"EntryPointAppDelegate" 71 | 72 | // disable this if you don't want retina support 73 | #define ENTRYPOINT_IOS_RETINA 74 | 75 | // disable this if you don't need accelerometer 76 | // WARNING: Remember to fill NSMotionUsageDescription in your Info.plist! 77 | #define ENTRYPOINT_IOS_CM_ACCELEROMETER 78 | #define ENTRYPOINT_IOS_CM_ACCELEROMETER_FREQ 60 // in Hertz 79 | 80 | // ----------------------------------------------------------------------------- 81 | // Android 82 | 83 | #define ENTRYPOINT_ANDROID_PREPARE_PARAMS {} 84 | 85 | // this is used in ep_log to provide a correct log tag 86 | #define ENTRYPOINT_ANDROID_LOG_TAG "EntryPoint" 87 | 88 | // ----------------------------------------------------------------------------- 89 | -------------------------------------------------------------------------------- /entrypoint_emscripten.c: -------------------------------------------------------------------------------- 1 | 2 | #ifdef EMSCRIPTEN 3 | 4 | #define ENTRYPOINT_CTX 5 | #include "entrypoint.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #ifdef ENTRYPOINT_PROVIDE_INPUT 14 | #include 15 | #endif 16 | 17 | static entrypoint_ctx_t ctx = {0}; 18 | entrypoint_ctx_t * ep_ctx() {return &ctx;} 19 | 20 | // ----------------------------------------------------------------------------- 21 | 22 | ep_size_t ep_size() 23 | { 24 | double w = 0.0, h = 0.0; 25 | emscripten_get_element_css_size(NULL, &w, &h); 26 | ep_size_t r; 27 | r.w = w; 28 | r.h = h; 29 | return r; 30 | } 31 | 32 | bool ep_retina() 33 | { 34 | return false; // TODO 35 | } 36 | 37 | ep_ui_margins_t ep_ui_margins() 38 | { 39 | ep_ui_margins_t r = {0, 0, 0, 0}; 40 | return r; 41 | } 42 | 43 | // ----------------------------------------------------------------------------- 44 | 45 | #ifdef ENTRYPOINT_PROVIDE_INPUT 46 | 47 | // ported from SDL2 emscripten fork 48 | // .keyCode to scancode 49 | // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent 50 | // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode 51 | static const uint8_t emscripten_scancode_table[] = { 52 | /* 0 */ 0, 53 | /* 1 */ 0, 54 | /* 2 */ 0, 55 | /* 3 */ 0, 56 | /* 4 */ 0, 57 | /* 5 */ 0, 58 | /* 6 */ 0, 59 | /* 7 */ 0, 60 | /* 8 */ EK_BACKSPACE, 61 | /* 9 */ EK_TAB, 62 | /* 10 */ 0, 63 | /* 11 */ 0, 64 | /* 12 */ 0, 65 | /* 13 */ EK_RETURN, 66 | /* 14 */ 0, 67 | /* 15 */ 0, 68 | /* 16 */ EK_LSHIFT, 69 | /* 17 */ EK_LCONTROL, 70 | /* 18 */ EK_LALT, 71 | /* 19 */ EK_PAUSE, 72 | /* 20 */ EK_CAPSLOCK, 73 | /* 21 */ 0, 74 | /* 22 */ 0, 75 | /* 23 */ 0, 76 | /* 24 */ 0, 77 | /* 25 */ 0, 78 | /* 26 */ 0, 79 | /* 27 */ EK_ESCAPE, 80 | /* 28 */ 0, 81 | /* 29 */ 0, 82 | /* 30 */ 0, 83 | /* 31 */ 0, 84 | /* 32 */ EK_SPACE, 85 | /* 33 */ EK_PAGEUP, 86 | /* 34 */ EK_PAGEDN, 87 | /* 35 */ EK_END, 88 | /* 36 */ EK_HOME, 89 | /* 37 */ EK_LEFT, 90 | /* 38 */ EK_UP, 91 | /* 39 */ EK_RIGHT, 92 | /* 40 */ EK_DOWN, 93 | /* 41 */ 0, 94 | /* 42 */ 0, 95 | /* 43 */ 0, 96 | /* 44 */ 0, 97 | /* 45 */ EK_INSERT, 98 | /* 46 */ EK_DELETE, 99 | /* 47 */ 0, 100 | /* 48 */ '0', 101 | /* 49 */ '1', 102 | /* 50 */ '2', 103 | /* 51 */ '3', 104 | /* 52 */ '4', 105 | /* 53 */ '5', 106 | /* 54 */ '6', 107 | /* 55 */ '7', 108 | /* 56 */ '8', 109 | /* 57 */ '9', 110 | /* 58 */ 0, 111 | /* 59 */ EK_SEMICOLON, 112 | /* 60 */ 0, 113 | /* 61 */ EK_EQUALS, 114 | /* 62 */ 0, 115 | /* 63 */ 0, 116 | /* 64 */ 0, 117 | /* 65 */ 'A', 118 | /* 66 */ 'B', 119 | /* 67 */ 'C', 120 | /* 68 */ 'D', 121 | /* 69 */ 'E', 122 | /* 70 */ 'F', 123 | /* 71 */ 'G', 124 | /* 72 */ 'H', 125 | /* 73 */ 'I', 126 | /* 74 */ 'J', 127 | /* 75 */ 'K', 128 | /* 76 */ 'L', 129 | /* 77 */ 'M', 130 | /* 78 */ 'N', 131 | /* 79 */ 'O', 132 | /* 80 */ 'P', 133 | /* 81 */ 'Q', 134 | /* 82 */ 'R', 135 | /* 83 */ 'S', 136 | /* 84 */ 'T', 137 | /* 85 */ 'U', 138 | /* 86 */ 'V', 139 | /* 87 */ 'W', 140 | /* 88 */ 'X', 141 | /* 89 */ 'Y', 142 | /* 90 */ 'Z', 143 | /* 91 */ EK_LWIN, 144 | /* 92 */ 0, 145 | /* 93 */ 0, // EK_APPLICATION, 146 | /* 94 */ 0, 147 | /* 95 */ 0, 148 | /* 96 */ EK_PAD0, 149 | /* 97 */ EK_PAD1, 150 | /* 98 */ EK_PAD2, 151 | /* 99 */ EK_PAD3, 152 | /* 100 */ EK_PAD4, 153 | /* 101 */ EK_PAD5, 154 | /* 102 */ EK_PAD6, 155 | /* 103 */ EK_PAD7, 156 | /* 104 */ EK_PAD8, 157 | /* 105 */ EK_PAD9, 158 | /* 106 */ EK_PADMUL, 159 | /* 107 */ EK_PADADD, 160 | /* 108 */ 0, // isn't it EK_PADENTER ? 161 | /* 109 */ EK_PADSUB, 162 | /* 110 */ EK_PADDOT, 163 | /* 111 */ EK_PADDIV, 164 | /* 112 */ EK_F1, 165 | /* 113 */ EK_F2, 166 | /* 114 */ EK_F3, 167 | /* 115 */ EK_F4, 168 | /* 116 */ EK_F5, 169 | /* 117 */ EK_F6, 170 | /* 118 */ EK_F7, 171 | /* 119 */ EK_F8, 172 | /* 120 */ EK_F9, 173 | /* 121 */ EK_F10, 174 | /* 122 */ EK_F11, 175 | /* 123 */ EK_F12, 176 | /* 124 */ 0, // EK_F13 177 | /* 125 */ 0, // EK_F14 178 | /* 126 */ 0, // EK_F15 179 | /* 127 */ 0, // EK_F16 180 | /* 128 */ 0, // EK_F17 181 | /* 129 */ 0, // EK_F18 182 | /* 130 */ 0, // EK_F19 183 | /* 131 */ 0, // EK_F20 184 | /* 132 */ 0, // EK_F21 185 | /* 133 */ 0, // EK_F22 186 | /* 134 */ 0, // EK_F23 187 | /* 135 */ 0, // EK_F24 188 | /* 136 */ 0, 189 | /* 137 */ 0, 190 | /* 138 */ 0, 191 | /* 139 */ 0, 192 | /* 140 */ 0, 193 | /* 141 */ 0, 194 | /* 142 */ 0, 195 | /* 143 */ 0, 196 | /* 144 */ EK_NUMLOCK, 197 | /* 145 */ EK_SCROLL, 198 | /* 146 */ 0, 199 | /* 147 */ 0, 200 | /* 148 */ 0, 201 | /* 149 */ 0, 202 | /* 150 */ 0, 203 | /* 151 */ 0, 204 | /* 152 */ 0, 205 | /* 153 */ 0, 206 | /* 154 */ 0, 207 | /* 155 */ 0, 208 | /* 156 */ 0, 209 | /* 157 */ 0, 210 | /* 158 */ 0, 211 | /* 159 */ 0, 212 | /* 160 */ 0, 213 | /* 161 */ 0, 214 | /* 162 */ 0, 215 | /* 163 */ 0, 216 | /* 164 */ 0, 217 | /* 165 */ 0, 218 | /* 166 */ 0, 219 | /* 167 */ 0, 220 | /* 168 */ 0, 221 | /* 169 */ 0, 222 | /* 170 */ 0, 223 | /* 171 */ 0, 224 | /* 172 */ 0, 225 | /* 173 */ EK_MINUS, /*FX*/ 226 | /* 174 */ 0, // EK_VOLUMEDOWN, /*IE, Chrome*/ 227 | /* 175 */ 0, // EK_VOLUMEUP, /*IE, Chrome*/ 228 | /* 176 */ 0, // EK_AUDIONEXT, /*IE, Chrome*/ 229 | /* 177 */ 0, // EK_AUDIOPREV, /*IE, Chrome*/ 230 | /* 178 */ 0, 231 | /* 179 */ 0, // EK_AUDIOPLAY, /*IE, Chrome*/ 232 | /* 180 */ 0, 233 | /* 181 */ 0, // EK_AUDIOMUTE, /*FX*/ 234 | /* 182 */ 0, // EK_VOLUMEDOWN, /*FX*/ 235 | /* 183 */ 0, // EK_VOLUMEUP, /*FX*/ 236 | /* 184 */ 0, 237 | /* 185 */ 0, 238 | /* 186 */ EK_SEMICOLON, /*IE, Chrome, D3E legacy*/ 239 | /* 187 */ EK_EQUALS, /*IE, Chrome, D3E legacy*/ 240 | /* 188 */ EK_COMMA, 241 | /* 189 */ EK_MINUS, /*IE, Chrome, D3E legacy*/ 242 | /* 190 */ EK_DOT, 243 | /* 191 */ EK_SLASH, 244 | /* 192 */ EK_BACKTICK, /*FX, D3E legacy (EK_APOSTROPHE in IE/Chrome)*/ 245 | /* 193 */ 0, 246 | /* 194 */ 0, 247 | /* 195 */ 0, 248 | /* 196 */ 0, 249 | /* 197 */ 0, 250 | /* 198 */ 0, 251 | /* 199 */ 0, 252 | /* 200 */ 0, 253 | /* 201 */ 0, 254 | /* 202 */ 0, 255 | /* 203 */ 0, 256 | /* 204 */ 0, 257 | /* 205 */ 0, 258 | /* 206 */ 0, 259 | /* 207 */ 0, 260 | /* 208 */ 0, 261 | /* 209 */ 0, 262 | /* 210 */ 0, 263 | /* 211 */ 0, 264 | /* 212 */ 0, 265 | /* 213 */ 0, 266 | /* 214 */ 0, 267 | /* 215 */ 0, 268 | /* 216 */ 0, 269 | /* 217 */ 0, 270 | /* 218 */ 0, 271 | /* 219 */ EK_LSQUARE, 272 | /* 220 */ EK_BACKSLASH, 273 | /* 221 */ EK_RSQUARE, 274 | /* 222 */ EK_TICK, /*FX, D3E legacy*/ 275 | }; 276 | 277 | static EM_BOOL _key_cb(int event_type, const EmscriptenKeyboardEvent * key_event, void * notused) 278 | { 279 | if(key_event->keyCode < sizeof(emscripten_scancode_table) / sizeof(emscripten_scancode_table[0])) 280 | { 281 | uint8_t c = emscripten_scancode_table[key_event->keyCode]; 282 | if(key_event->location == DOM_KEY_LOCATION_RIGHT) 283 | { 284 | switch(c) 285 | { 286 | case EK_LSHIFT: c = EK_RSHIFT; break; 287 | case EK_LCONTROL: c = EK_RCONTROL; break; 288 | case EK_LALT: c = EK_RALT; break; 289 | case EK_LWIN: c = EK_RWIN; break; 290 | default: break; 291 | } 292 | } 293 | ctx.keys[c] = event_type == EMSCRIPTEN_EVENT_KEYDOWN ? 1 : 0; 294 | } 295 | 296 | ctx.last_char = key_event->charCode; 297 | 298 | // TODO fix this 299 | // if TEXTINPUT events are enabled we can't prevent keydown or we won't get keypress 300 | // we need to ALWAYS prevent backspace and tab otherwise chrome takes action and does bad navigation UX 301 | //if (event_type == EMSCRIPTEN_EVENT_KEYDOWN && SDL_GetEventState(SDL_TEXTINPUT) == SDL_ENABLE && key_event->keyCode != 8 /* backspace */ && key_event->keyCode != 9 /* tab */) 302 | // return false; 303 | return true; 304 | } 305 | 306 | static EM_BOOL _mouse_move_cb(int event_type, const EmscriptenMouseEvent * mouse_event, void * notused) 307 | { 308 | ctx.touch.x = mouse_event->canvasX; 309 | ctx.touch.y = mouse_event->canvasY; 310 | ctx.touch.multitouch[0].x = ctx.touch.x; 311 | ctx.touch.multitouch[0].y = ctx.touch.y; 312 | 313 | return false; 314 | } 315 | 316 | static EM_BOOL _mouse_key_cb(int event_type, const EmscriptenMouseEvent * mouse_event, void * notused) 317 | { 318 | bool v = event_type == EMSCRIPTEN_EVENT_MOUSEDOWN; 319 | if(mouse_event->button == 0) ctx.touch.left = v; 320 | else if(mouse_event->button == 1) ctx.touch.middle = v; 321 | else if(mouse_event->button == 2) ctx.touch.right = v; 322 | ctx.touch.multitouch[0].touched = ctx.touch.left; 323 | return true; 324 | } 325 | 326 | static EM_BOOL _touch_cb(int event_type, const EmscriptenTouchEvent * touch_event, void * notused) 327 | { 328 | // TODO support multitouch here 329 | if(touch_event->numTouches > 0) 330 | { 331 | ctx.touch.x = touch_event->touches[0].canvasX; 332 | ctx.touch.y = touch_event->touches[0].canvasY; 333 | ctx.touch.multitouch[0].x = ctx.touch.x; 334 | ctx.touch.multitouch[0].y = ctx.touch.y; 335 | } 336 | ctx.touch.left = event_type == EMSCRIPTEN_EVENT_TOUCHSTART || event_type == EMSCRIPTEN_EVENT_TOUCHMOVE; 337 | ctx.touch.multitouch[0].touched = ctx.touch.left; 338 | return true; 339 | } 340 | #endif 341 | 342 | // ----------------------------------------------------------------------------- 343 | 344 | static void _tick() 345 | { 346 | if(ctx.flag_want_to_close) 347 | return; 348 | 349 | if(entrypoint_loop() != 0) 350 | ctx.flag_want_to_close = true; 351 | 352 | #ifdef ENTRYPOINT_PROVIDE_INPUT 353 | memcpy(ctx.prev, ctx.keys, sizeof(ctx.prev)); 354 | #endif 355 | } 356 | 357 | int main(int argc, char * argv[]) 358 | { 359 | ctx.argc = argc; 360 | ctx.argv = argv; 361 | 362 | // TODO emscripten_set_element_css_size ??? 363 | 364 | int32_t result_code = 0; 365 | if((result_code = entrypoint_init(ctx.argc, ctx.argv)) != 0) 366 | return result_code; 367 | 368 | #ifdef ENTRYPOINT_PROVIDE_INPUT 369 | emscripten_set_keypress_callback("#window", NULL, false, _key_cb); 370 | emscripten_set_keydown_callback("#window", NULL, false, _key_cb); 371 | emscripten_set_keyup_callback("#window", NULL, false, _key_cb); 372 | 373 | emscripten_set_mousemove_callback("#canvas", NULL, 0, _mouse_move_cb); 374 | emscripten_set_mousedown_callback("#canvas", NULL, 0, _mouse_key_cb); 375 | emscripten_set_mouseup_callback("#document", NULL, 0, _mouse_key_cb); 376 | 377 | emscripten_set_touchstart_callback("#canvas", NULL, 0, _touch_cb); 378 | emscripten_set_touchend_callback("#canvas", NULL, 0, _touch_cb); 379 | emscripten_set_touchmove_callback("#canvas", NULL, 0, _touch_cb); 380 | emscripten_set_touchcancel_callback("#canvas", NULL, 0, _touch_cb); 381 | #endif 382 | 383 | emscripten_set_main_loop(_tick, -1, 1); 384 | 385 | entrypoint_might_unload(); // TODO add this to onclose 386 | entrypoint_deinit(); 387 | 388 | return 0; 389 | } 390 | 391 | // ----------------------------------------------------------------------------- 392 | 393 | #ifdef ENTRYPOINT_PROVIDE_TIME 394 | 395 | double ep_delta_time() 396 | { 397 | if(!ctx.flag_time_set) 398 | { 399 | gettimeofday(&ctx.prev_time, NULL); 400 | ctx.flag_time_set = true; 401 | return 0.0; 402 | } 403 | 404 | struct timeval now; 405 | gettimeofday(&now, NULL); 406 | double elapsed = (now.tv_sec - ctx.prev_time.tv_sec) + ((now.tv_usec - ctx.prev_time.tv_usec) / 1000000.0); 407 | ctx.prev_time = now; 408 | return elapsed; 409 | } 410 | 411 | void ep_sleep(double seconds) 412 | { 413 | //emscripten_sleep(seconds); // TODO requires EMTERPRETIFY, probably not the wisest idea anyway 414 | } 415 | 416 | #endif 417 | 418 | // ----------------------------------------------------------------------------- 419 | 420 | #ifdef ENTRYPOINT_PROVIDE_LOG 421 | 422 | void ep_log(const char * message, ...) 423 | { 424 | va_list args; 425 | va_start(args, message); 426 | vprintf(message, args); 427 | fflush(stdout); 428 | va_end(args); 429 | } 430 | 431 | #endif 432 | 433 | // ----------------------------------------------------------------------------- 434 | 435 | #ifdef ENTRYPOINT_PROVIDE_INPUT 436 | 437 | void ep_touch(ep_touch_t * touch) 438 | { 439 | if(touch) 440 | *touch = ctx.touch; 441 | } 442 | 443 | bool ep_khit(int32_t key) 444 | { 445 | assert(key < sizeof(ctx.keys)); 446 | if (key >= 'a' && key <= 'z') key = key - 'a' + 'A'; 447 | return ctx.keys[key] && !ctx.prev[key]; 448 | } 449 | 450 | bool ep_kdown(int32_t key) 451 | { 452 | assert(key < sizeof(ctx.keys)); 453 | if (key >= 'a' && key <= 'z') key = key - 'a' + 'A'; 454 | return ctx.keys[key]; 455 | } 456 | 457 | uint32_t ep_kchar() 458 | { 459 | uint32_t k = ctx.last_char; 460 | ctx.last_char = 0; 461 | return k; 462 | } 463 | 464 | #endif 465 | 466 | // ----------------------------------------------------------------------------- 467 | 468 | #ifdef ENTRYPOINT_PROVIDE_OPENURL 469 | 470 | void ep_openurl(const char * url) 471 | { 472 | EM_ASM({ 473 | window.open(UTF8ToString($0), '_blank'); 474 | }, url); 475 | } 476 | 477 | #endif 478 | 479 | // ----------------------------------------------------------------------------- 480 | 481 | #endif 482 | 483 | -------------------------------------------------------------------------------- /entrypoint_ios.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // iOS is a bit different here 4 | // 5 | // indeed we could go SDL way and create everything for you 6 | // but this is exactly why SDL is so bad on iOS - there is more to an app than just OpenGL view 7 | // 8 | // instead what we provide here is a UIView that you can use inside your storyboard/xib/code 9 | // this should allow more easy integration of your code with iOS specific parts like ads/popups/etc 10 | // 11 | // also please note, only one view instance should be used (which is a most common scenario anyway) 12 | 13 | #import 14 | #import 15 | #include "entrypoint_config.h" 16 | 17 | #if defined(ENTRYPOINT_IOS_CM_ACCELEROMETER) 18 | #define ENTRYPOINT_IOS_CM 19 | #endif 20 | 21 | @interface EntryPointView : UIView 22 | 23 | #ifdef ENTRYPOINT_IOS_CM 24 | @property(nonatomic, strong) CMMotionManager * motionManager; 25 | #endif 26 | 27 | @end 28 | 29 | #ifdef ENTRYPOINT_IOS_APPDELEGATE 30 | 31 | @interface EntryPointViewController : UIViewController 32 | @property (strong, nonatomic) EntryPointView * view; 33 | @end 34 | 35 | @interface EntryPointAppDelegate : UIResponder 36 | @property (strong, nonatomic) UIWindow * window; 37 | @end 38 | 39 | #endif 40 | 41 | #ifdef __cplusplus 42 | extern "C" { 43 | #endif 44 | 45 | // call this functions from app delegate or whatever other place where you decide when your game should be running or not 46 | void ep_anim_start(); 47 | void ep_anim_stop(); 48 | 49 | #ifdef __cplusplus 50 | } 51 | #endif 52 | -------------------------------------------------------------------------------- /entrypoint_ios.m: -------------------------------------------------------------------------------- 1 | 2 | #define ENTRYPOINT_CTX 3 | #include "entrypoint.h" 4 | 5 | #if defined(__APPLE__) && TARGET_OS_IOS 6 | #if !__has_feature(objc_arc) 7 | #error hey, we need ARC here 8 | #endif 9 | 10 | #import "entrypoint_ios.h" 11 | 12 | static entrypoint_ctx_t ctx = {0}; 13 | entrypoint_ctx_t * ep_ctx() {return &ctx;} 14 | 15 | // ----------------------------------------------------------------------------- 16 | 17 | @interface EntryPointView () 18 | { 19 | CADisplayLink* m_displayLink; 20 | } 21 | 22 | - (void)start; 23 | - (void)stop; 24 | - (void)tick; 25 | - (ep_ui_margins_t)ui_margins; 26 | 27 | #ifdef ENTRYPOINT_PROVIDE_INPUT 28 | - (void)onTouchAddOrMove:(UITouch *)touch; 29 | - (void)onTouchRemove:(UITouch *)touch; 30 | #endif 31 | @end 32 | 33 | @implementation EntryPointView 34 | 35 | + (Class)layerClass 36 | { 37 | return [CAEAGLLayer class]; 38 | } 39 | 40 | - (void)commonInit 41 | { 42 | ctx.view = (__bridge void *)(self); 43 | ctx.caeagllayer = (__bridge void *)(self.layer); 44 | 45 | if(entrypoint_init(ctx.argc, ctx.argv) != 0) 46 | { 47 | NSLog(@"failed to init entrypoint"); 48 | ctx.flag_failed_to_init = 1; 49 | } 50 | else 51 | ctx.flag_failed_to_init = 0; 52 | 53 | #ifdef ENTRYPOINT_IOS_RETINA 54 | self.contentScaleFactor = [UIScreen mainScreen].scale; 55 | #endif 56 | self.multipleTouchEnabled = true; 57 | 58 | #ifdef ENTRYPOINT_IOS_CM 59 | self.motionManager = [[CMMotionManager alloc] init]; 60 | #endif 61 | #ifdef ENTRYPOINT_IOS_CM_ACCELEROMETER 62 | self.motionManager.accelerometerUpdateInterval = 1.0f / ENTRYPOINT_IOS_CM_ACCELEROMETER_FREQ; 63 | #endif 64 | } 65 | 66 | - (id)initWithFrame:(CGRect)rect 67 | { 68 | if ((self = [super initWithFrame:rect])) 69 | [self commonInit]; 70 | return self; 71 | } 72 | 73 | - (id)initWithCoder:(NSCoder*)coder 74 | { 75 | if ((self = [super initWithCoder:coder])) 76 | [self commonInit]; 77 | return self; 78 | } 79 | 80 | - (void)dealloc 81 | { 82 | if(entrypoint_deinit() != 0) 83 | { 84 | NSLog(@"failed to deinit entrypoint"); // not much we can do here 85 | } 86 | } 87 | 88 | - (void)layoutSubviews 89 | { 90 | ctx.view_w = (uint16_t)(self.contentScaleFactor * self.frame.size.width); 91 | ctx.view_h = (uint16_t)(self.contentScaleFactor * self.frame.size.height); 92 | } 93 | 94 | - (void)start 95 | { 96 | UIGestureRecognizer * gr0 = self.window.gestureRecognizers[0]; 97 | UIGestureRecognizer * gr1 = self.window.gestureRecognizers[1]; 98 | gr0.delaysTouchesBegan = false; 99 | gr1.delaysTouchesBegan = false; 100 | 101 | if(m_displayLink == nil) 102 | { 103 | ctx.flag_anim = 1; 104 | m_displayLink = [self.window.screen displayLinkWithTarget:self selector:@selector(tick)]; 105 | [m_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; 106 | } 107 | 108 | #ifdef ENTRYPOINT_IOS_CM_ACCELEROMETER 109 | if([self.motionManager isAccelerometerAvailable]) 110 | [self.motionManager startAccelerometerUpdates]; 111 | #endif 112 | } 113 | 114 | - (void)stop 115 | { 116 | if(m_displayLink != nil) 117 | { 118 | ctx.flag_anim = 0; 119 | [m_displayLink invalidate]; 120 | m_displayLink = nil; 121 | entrypoint_might_unload(); 122 | } 123 | 124 | #ifdef ENTRYPOINT_IOS_CM_ACCELEROMETER 125 | if([self.motionManager isAccelerometerAvailable]) 126 | [self.motionManager stopAccelerometerUpdates]; 127 | #endif 128 | } 129 | 130 | - (void)tick 131 | { 132 | if(!ctx.flag_failed_to_init && ctx.flag_anim) 133 | { 134 | #if defined(ENTRYPOINT_PROVIDE_INPUT) && defined(ENTRYPOINT_IOS_CM_ACCELEROMETER) 135 | if([self.motionManager isAccelerometerAvailable] && self.motionManager.accelerometerData) 136 | { 137 | ctx.touch.acc_x = self.motionManager.accelerometerData.acceleration.x; 138 | ctx.touch.acc_y = self.motionManager.accelerometerData.acceleration.y; 139 | ctx.touch.acc_z = self.motionManager.accelerometerData.acceleration.z; 140 | } 141 | #endif 142 | 143 | if(entrypoint_loop() != 0) 144 | [self stop]; // an iOS app cannot force itself to close under normal UX flows 145 | } 146 | } 147 | 148 | - (ep_ui_margins_t)ui_margins 149 | { 150 | ep_ui_margins_t r = {0, 0, 0, 0}; 151 | 152 | if ([self respondsToSelector: @selector(safeAreaInsets)]) 153 | { 154 | UIEdgeInsets insets = [self safeAreaInsets]; 155 | 156 | #define CLAMP0(_x) ( (_x) > 0.0f ? (_x) : 0.0f ) 157 | 158 | r.l = self.contentScaleFactor * CLAMP0(insets.left); 159 | r.t = self.contentScaleFactor * CLAMP0(insets.top); 160 | r.r = self.contentScaleFactor * CLAMP0(insets.right); 161 | r.b = self.contentScaleFactor * CLAMP0(insets.bottom); 162 | 163 | #undef CLAMP0 164 | } 165 | 166 | return r; 167 | } 168 | 169 | #ifdef ENTRYPOINT_PROVIDE_INPUT 170 | - (void)onTouchAddOrMove:(UITouch *)touch 171 | { 172 | CGPoint touchLocation = [touch locationInView:self]; 173 | ctx.touch.x = self.contentScaleFactor * touchLocation.x; 174 | ctx.touch.y = self.contentScaleFactor * touchLocation.y; 175 | 176 | void * context = (__bridge void *)(touch); 177 | uint8_t first_free = ENTRYPOINT_MAX_MULTITOUCH; 178 | uint8_t update_index = ENTRYPOINT_MAX_MULTITOUCH; 179 | for(uint8_t i = 0; i < ENTRYPOINT_MAX_MULTITOUCH; ++i) 180 | { 181 | if(ctx.touch.multitouch[i].context == context && ctx.touch.multitouch[i].touched) 182 | { 183 | update_index = i; 184 | break; 185 | } 186 | else if(first_free >= ENTRYPOINT_MAX_MULTITOUCH && ctx.touch.multitouch[i].touched == 0) 187 | first_free = i; 188 | } 189 | 190 | uint8_t index = update_index < ENTRYPOINT_MAX_MULTITOUCH ? update_index : first_free; 191 | 192 | if(index < ENTRYPOINT_MAX_MULTITOUCH) 193 | { 194 | ctx.touch.multitouch[index].context = context; 195 | ctx.touch.multitouch[index].x = ctx.touch.x; 196 | ctx.touch.multitouch[index].y = ctx.touch.y; 197 | ctx.touch.multitouch[index].touched = 1; 198 | } 199 | } 200 | 201 | - (void)onTouchRemove:(UITouch *)touch 202 | { 203 | CGPoint touchLocation = [touch locationInView:self]; 204 | ctx.touch.x = self.contentScaleFactor * touchLocation.x; 205 | ctx.touch.y = self.contentScaleFactor * touchLocation.y; 206 | 207 | void * context = (__bridge void *)(touch); 208 | for(uint8_t i = 0; i < ENTRYPOINT_MAX_MULTITOUCH; ++i) 209 | { 210 | if(ctx.touch.multitouch[i].context == context) 211 | { 212 | ctx.touch.multitouch[i].x = ctx.touch.x; 213 | ctx.touch.multitouch[i].y = ctx.touch.y; 214 | ctx.touch.multitouch[i].touched = 0; 215 | break; 216 | } 217 | } 218 | } 219 | 220 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 221 | { 222 | for(UITouch * touch in touches) 223 | [self onTouchAddOrMove:touch]; 224 | ctx.touch.left = 1; 225 | } 226 | 227 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 228 | { 229 | for(UITouch * touch in touches) 230 | [self onTouchRemove:touch]; 231 | ctx.touch.left = 0; 232 | } 233 | 234 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 235 | { 236 | for(UITouch * touch in touches) 237 | [self onTouchAddOrMove:touch]; 238 | } 239 | 240 | - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event 241 | { 242 | for(UITouch * touch in touches) 243 | [self onTouchRemove:touch]; 244 | ctx.touch.left = 0; 245 | } 246 | #endif 247 | 248 | @end 249 | 250 | // ----------------------------------------------------------------------------- 251 | 252 | void ep_anim_start() 253 | { 254 | if(ctx.view) 255 | [(__bridge EntryPointView*)ctx.view start]; 256 | } 257 | 258 | void ep_anim_stop() 259 | { 260 | if(ctx.view) 261 | [(__bridge EntryPointView*)ctx.view stop]; 262 | } 263 | 264 | // ----------------------------------------------------------------------------- 265 | 266 | ep_size_t ep_size() 267 | { 268 | ep_size_t r; 269 | r.w = ctx.view_w; 270 | r.h = ctx.view_h; 271 | return r; 272 | } 273 | 274 | bool ep_retina() 275 | { 276 | #ifdef ENTRYPOINT_IOS_RETINA 277 | return true; 278 | #else 279 | return false; 280 | #endif 281 | } 282 | 283 | ep_ui_margins_t ep_ui_margins() 284 | { 285 | if(ctx.view) 286 | return [(__bridge EntryPointView*)ctx.view ui_margins]; 287 | else 288 | { 289 | ep_ui_margins_t r = {0, 0, 0, 0}; 290 | return r; 291 | } 292 | } 293 | 294 | // ----------------------------------------------------------------------------- 295 | 296 | #ifdef ENTRYPOINT_PROVIDE_TIME 297 | 298 | double ep_delta_time() 299 | { 300 | if(ctx.timebase_info.denom == 0) 301 | { 302 | mach_timebase_info(&ctx.timebase_info); 303 | ctx.prev_time = mach_absolute_time(); 304 | return 0.0f; 305 | } 306 | 307 | uint64_t current_time = mach_absolute_time(); 308 | double elapsed = (double)(current_time - ctx.prev_time) * ctx.timebase_info.numer / (ctx.timebase_info.denom * 1000000000.0); 309 | ctx.prev_time = current_time; 310 | return elapsed; 311 | } 312 | 313 | void ep_sleep(double seconds) 314 | { 315 | usleep((useconds_t)(seconds * 1000000.0)); 316 | } 317 | 318 | #endif 319 | 320 | // ----------------------------------------------------------------------------- 321 | 322 | #ifdef ENTRYPOINT_PROVIDE_LOG 323 | 324 | void ep_log(const char * message, ...) 325 | { 326 | va_list args; 327 | va_start(args, message); 328 | vprintf(message, args); 329 | fflush(stdout); 330 | va_end(args); 331 | } 332 | 333 | #endif 334 | 335 | // ----------------------------------------------------------------------------- 336 | 337 | #ifdef ENTRYPOINT_PROVIDE_INPUT 338 | 339 | void ep_touch(ep_touch_t * touch) 340 | { 341 | if(touch) 342 | *touch = ctx.touch; 343 | } 344 | 345 | bool ep_khit(int32_t key) {return false;} 346 | bool ep_kdown(int32_t key) {return false;} 347 | uint32_t ep_kchar() {return 0;} 348 | 349 | #endif 350 | 351 | // ----------------------------------------------------------------------------- 352 | 353 | #ifdef ENTRYPOINT_PROVIDE_OPENURL 354 | 355 | void ep_openurl(const char * url) 356 | { 357 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithUTF8String:url]]]; 358 | } 359 | 360 | #endif 361 | 362 | // ----------------------------------------------------------------------------- 363 | 364 | #ifdef ENTRYPOINT_IOS_APPDELEGATE 365 | 366 | @implementation EntryPointViewController 367 | @dynamic view; 368 | - (void)loadView { self.view = [[EntryPointView alloc] initWithFrame:UIScreen.mainScreen.bounds]; } 369 | @end 370 | 371 | // TODO sometimes it complains on "[App] if we're in the real pre-commit handler we can't actually add any new fences due to CA restriction" 372 | @implementation EntryPointAppDelegate 373 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 374 | { 375 | self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; 376 | [self.window makeKeyAndVisible]; 377 | self.window.rootViewController = [[EntryPointViewController alloc] init]; 378 | return YES; 379 | } 380 | - (void)applicationWillResignActive:(UIApplication *)application { ep_anim_stop(); } 381 | - (void)applicationDidEnterBackground:(UIApplication *)application { ep_anim_stop(); } 382 | - (void)applicationWillEnterForeground:(UIApplication *)application { ep_anim_start(); } 383 | - (void)applicationDidBecomeActive:(UIApplication *)application { ep_anim_start(); } 384 | - (void)applicationWillTerminate:(UIApplication *)application { ep_anim_stop(); } 385 | @end 386 | 387 | #endif 388 | 389 | // ----------------------------------------------------------------------------- 390 | 391 | #ifdef ENTRYPOINT_IOS_MAIN 392 | 393 | int main(int argc, char * argv[]) 394 | { 395 | ctx.argc = argc; 396 | ctx.argv = argv; 397 | @autoreleasepool 398 | { 399 | return UIApplicationMain(argc, argv, nil, ENTRYPOINT_IOS_MAIN_APPDELEGATE_CLASSNAME); 400 | } 401 | } 402 | 403 | #endif 404 | 405 | #endif 406 | -------------------------------------------------------------------------------- /entrypoint_osx.m: -------------------------------------------------------------------------------- 1 | 2 | // based on https://github.com/jimon/osx_app_in_plain_c 3 | 4 | #define ENTRYPOINT_CTX 5 | #include "entrypoint.h" 6 | 7 | #if defined(__APPLE__) && TARGET_OS_OSX 8 | #if !__has_feature(objc_arc) 9 | #error hey, we need ARC here 10 | #endif 11 | 12 | #import 13 | 14 | static entrypoint_ctx_t ctx = {0}; 15 | entrypoint_ctx_t * ep_ctx() {return &ctx;} 16 | 17 | // ----------------------------------------------------------------------------- 18 | 19 | @interface AppDelegate : NSObject 20 | -(NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender; 21 | @end 22 | 23 | @implementation AppDelegate 24 | -(NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender 25 | { 26 | ctx.terminated = true; 27 | return NSTerminateCancel; 28 | } 29 | @end 30 | 31 | @interface WindowDelegate : NSObject 32 | -(void)windowWillClose:(NSNotification*)notification; 33 | @end 34 | 35 | @implementation WindowDelegate 36 | -(void)windowWillClose:(NSNotification*)notification 37 | { 38 | (void)notification; 39 | assert(ctx.window_count); 40 | if(--ctx.window_count == 0) 41 | ctx.terminated = true; 42 | } 43 | @end 44 | 45 | // ----------------------------------------------------------------------------- 46 | 47 | #ifdef ENTRYPOINT_PROVIDE_INPUT 48 | static uint8_t _macoskey(uint16_t key) 49 | { 50 | // from Carbon HIToolbox/Events.h 51 | enum 52 | { 53 | kVK_ANSI_A = 0x00, 54 | kVK_ANSI_S = 0x01, 55 | kVK_ANSI_D = 0x02, 56 | kVK_ANSI_F = 0x03, 57 | kVK_ANSI_H = 0x04, 58 | kVK_ANSI_G = 0x05, 59 | kVK_ANSI_Z = 0x06, 60 | kVK_ANSI_X = 0x07, 61 | kVK_ANSI_C = 0x08, 62 | kVK_ANSI_V = 0x09, 63 | kVK_ANSI_B = 0x0B, 64 | kVK_ANSI_Q = 0x0C, 65 | kVK_ANSI_W = 0x0D, 66 | kVK_ANSI_E = 0x0E, 67 | kVK_ANSI_R = 0x0F, 68 | kVK_ANSI_Y = 0x10, 69 | kVK_ANSI_T = 0x11, 70 | kVK_ANSI_1 = 0x12, 71 | kVK_ANSI_2 = 0x13, 72 | kVK_ANSI_3 = 0x14, 73 | kVK_ANSI_4 = 0x15, 74 | kVK_ANSI_6 = 0x16, 75 | kVK_ANSI_5 = 0x17, 76 | kVK_ANSI_Equal = 0x18, 77 | kVK_ANSI_9 = 0x19, 78 | kVK_ANSI_7 = 0x1A, 79 | kVK_ANSI_Minus = 0x1B, 80 | kVK_ANSI_8 = 0x1C, 81 | kVK_ANSI_0 = 0x1D, 82 | kVK_ANSI_RightBracket = 0x1E, 83 | kVK_ANSI_O = 0x1F, 84 | kVK_ANSI_U = 0x20, 85 | kVK_ANSI_LeftBracket = 0x21, 86 | kVK_ANSI_I = 0x22, 87 | kVK_ANSI_P = 0x23, 88 | kVK_ANSI_L = 0x25, 89 | kVK_ANSI_J = 0x26, 90 | kVK_ANSI_Quote = 0x27, 91 | kVK_ANSI_K = 0x28, 92 | kVK_ANSI_Semicolon = 0x29, 93 | kVK_ANSI_Backslash = 0x2A, 94 | kVK_ANSI_Comma = 0x2B, 95 | kVK_ANSI_Slash = 0x2C, 96 | kVK_ANSI_N = 0x2D, 97 | kVK_ANSI_M = 0x2E, 98 | kVK_ANSI_Period = 0x2F, 99 | kVK_ANSI_Grave = 0x32, 100 | kVK_ANSI_KeypadDecimal = 0x41, 101 | kVK_ANSI_KeypadMultiply = 0x43, 102 | kVK_ANSI_KeypadPlus = 0x45, 103 | kVK_ANSI_KeypadClear = 0x47, 104 | kVK_ANSI_KeypadDivide = 0x4B, 105 | kVK_ANSI_KeypadEnter = 0x4C, 106 | kVK_ANSI_KeypadMinus = 0x4E, 107 | kVK_ANSI_KeypadEquals = 0x51, 108 | kVK_ANSI_Keypad0 = 0x52, 109 | kVK_ANSI_Keypad1 = 0x53, 110 | kVK_ANSI_Keypad2 = 0x54, 111 | kVK_ANSI_Keypad3 = 0x55, 112 | kVK_ANSI_Keypad4 = 0x56, 113 | kVK_ANSI_Keypad5 = 0x57, 114 | kVK_ANSI_Keypad6 = 0x58, 115 | kVK_ANSI_Keypad7 = 0x59, 116 | kVK_ANSI_Keypad8 = 0x5B, 117 | kVK_ANSI_Keypad9 = 0x5C, 118 | kVK_Return = 0x24, 119 | kVK_Tab = 0x30, 120 | kVK_Space = 0x31, 121 | kVK_Delete = 0x33, 122 | kVK_Escape = 0x35, 123 | kVK_Command = 0x37, 124 | kVK_Shift = 0x38, 125 | kVK_CapsLock = 0x39, 126 | kVK_Option = 0x3A, 127 | kVK_Control = 0x3B, 128 | kVK_RightShift = 0x3C, 129 | kVK_RightOption = 0x3D, 130 | kVK_RightControl = 0x3E, 131 | kVK_Function = 0x3F, 132 | kVK_F17 = 0x40, 133 | kVK_VolumeUp = 0x48, 134 | kVK_VolumeDown = 0x49, 135 | kVK_Mute = 0x4A, 136 | kVK_F18 = 0x4F, 137 | kVK_F19 = 0x50, 138 | kVK_F20 = 0x5A, 139 | kVK_F5 = 0x60, 140 | kVK_F6 = 0x61, 141 | kVK_F7 = 0x62, 142 | kVK_F3 = 0x63, 143 | kVK_F8 = 0x64, 144 | kVK_F9 = 0x65, 145 | kVK_F11 = 0x67, 146 | kVK_F13 = 0x69, 147 | kVK_F16 = 0x6A, 148 | kVK_F14 = 0x6B, 149 | kVK_F10 = 0x6D, 150 | kVK_F12 = 0x6F, 151 | kVK_F15 = 0x71, 152 | kVK_Help = 0x72, 153 | kVK_Home = 0x73, 154 | kVK_PageUp = 0x74, 155 | kVK_ForwardDelete = 0x75, 156 | kVK_F4 = 0x76, 157 | kVK_End = 0x77, 158 | kVK_F2 = 0x78, 159 | kVK_PageDown = 0x79, 160 | kVK_F1 = 0x7A, 161 | kVK_LeftArrow = 0x7B, 162 | kVK_RightArrow = 0x7C, 163 | kVK_DownArrow = 0x7D, 164 | kVK_UpArrow = 0x7E 165 | }; 166 | 167 | switch(key) 168 | { 169 | case kVK_ANSI_Q: return 'Q'; 170 | case kVK_ANSI_W: return 'W'; 171 | case kVK_ANSI_E: return 'E'; 172 | case kVK_ANSI_R: return 'R'; 173 | case kVK_ANSI_T: return 'T'; 174 | case kVK_ANSI_Y: return 'Y'; 175 | case kVK_ANSI_U: return 'U'; 176 | case kVK_ANSI_I: return 'I'; 177 | case kVK_ANSI_O: return 'O'; 178 | case kVK_ANSI_P: return 'P'; 179 | case kVK_ANSI_A: return 'A'; 180 | case kVK_ANSI_S: return 'S'; 181 | case kVK_ANSI_D: return 'D'; 182 | case kVK_ANSI_F: return 'F'; 183 | case kVK_ANSI_G: return 'G'; 184 | case kVK_ANSI_H: return 'H'; 185 | case kVK_ANSI_J: return 'J'; 186 | case kVK_ANSI_K: return 'K'; 187 | case kVK_ANSI_L: return 'L'; 188 | case kVK_ANSI_Z: return 'Z'; 189 | case kVK_ANSI_X: return 'X'; 190 | case kVK_ANSI_C: return 'C'; 191 | case kVK_ANSI_V: return 'V'; 192 | case kVK_ANSI_B: return 'B'; 193 | case kVK_ANSI_N: return 'N'; 194 | case kVK_ANSI_M: return 'M'; 195 | case kVK_ANSI_0: return '0'; 196 | case kVK_ANSI_1: return '1'; 197 | case kVK_ANSI_2: return '2'; 198 | case kVK_ANSI_3: return '3'; 199 | case kVK_ANSI_4: return '4'; 200 | case kVK_ANSI_5: return '5'; 201 | case kVK_ANSI_6: return '6'; 202 | case kVK_ANSI_7: return '7'; 203 | case kVK_ANSI_8: return '8'; 204 | case kVK_ANSI_9: return '9'; 205 | case kVK_ANSI_Keypad0: return EK_PAD0; 206 | case kVK_ANSI_Keypad1: return EK_PAD1; 207 | case kVK_ANSI_Keypad2: return EK_PAD2; 208 | case kVK_ANSI_Keypad3: return EK_PAD3; 209 | case kVK_ANSI_Keypad4: return EK_PAD4; 210 | case kVK_ANSI_Keypad5: return EK_PAD5; 211 | case kVK_ANSI_Keypad6: return EK_PAD6; 212 | case kVK_ANSI_Keypad7: return EK_PAD7; 213 | case kVK_ANSI_Keypad8: return EK_PAD8; 214 | case kVK_ANSI_Keypad9: return EK_PAD9; 215 | case kVK_ANSI_KeypadMultiply: return EK_PADMUL; 216 | case kVK_ANSI_KeypadPlus: return EK_PADADD; 217 | case kVK_ANSI_KeypadEnter: return EK_PADENTER; 218 | case kVK_ANSI_KeypadMinus: return EK_PADSUB; 219 | case kVK_ANSI_KeypadDecimal: return EK_PADDOT; 220 | case kVK_ANSI_KeypadDivide: return EK_PADDIV; 221 | case kVK_F1: return EK_F1; 222 | case kVK_F2: return EK_F2; 223 | case kVK_F3: return EK_F3; 224 | case kVK_F4: return EK_F4; 225 | case kVK_F5: return EK_F5; 226 | case kVK_F6: return EK_F6; 227 | case kVK_F7: return EK_F7; 228 | case kVK_F8: return EK_F8; 229 | case kVK_F9: return EK_F9; 230 | case kVK_F10: return EK_F10; 231 | case kVK_F11: return EK_F11; 232 | case kVK_F12: return EK_F12; 233 | case kVK_Shift: return EK_LSHIFT; 234 | case kVK_Control: return EK_LCONTROL; 235 | case kVK_Option: return EK_LALT; 236 | case kVK_CapsLock: return EK_CAPSLOCK; 237 | case kVK_Command: return EK_LWIN; 238 | case kVK_Command - 1: return EK_RWIN; 239 | case kVK_RightShift: return EK_RSHIFT; 240 | case kVK_RightControl: return EK_RCONTROL; 241 | case kVK_RightOption: return EK_RALT; 242 | case kVK_Delete: return EK_BACKSPACE; 243 | case kVK_Tab: return EK_TAB; 244 | case kVK_Return: return EK_RETURN; 245 | case kVK_Escape: return EK_ESCAPE; 246 | case kVK_Space: return EK_SPACE; 247 | case kVK_PageUp: return EK_PAGEUP; 248 | case kVK_PageDown: return EK_PAGEDN; 249 | case kVK_End: return EK_END; 250 | case kVK_Home: return EK_HOME; 251 | case kVK_LeftArrow: return EK_LEFT; 252 | case kVK_UpArrow: return EK_UP; 253 | case kVK_RightArrow: return EK_RIGHT; 254 | case kVK_DownArrow: return EK_DOWN; 255 | case kVK_Help: return EK_INSERT; 256 | case kVK_ForwardDelete: return EK_DELETE; 257 | case kVK_F14: return EK_SCROLL; 258 | case kVK_F15: return EK_PAUSE; 259 | case kVK_ANSI_KeypadClear: return EK_NUMLOCK; 260 | case kVK_ANSI_Semicolon: return EK_SEMICOLON; 261 | case kVK_ANSI_Equal: return EK_EQUALS; 262 | case kVK_ANSI_Comma: return EK_COMMA; 263 | case kVK_ANSI_Minus: return EK_MINUS; 264 | case kVK_ANSI_Slash: return EK_SLASH; 265 | case kVK_ANSI_Backslash: return EK_BACKSLASH; 266 | case kVK_ANSI_Grave: return EK_BACKTICK; 267 | case kVK_ANSI_Quote: return EK_TICK; 268 | case kVK_ANSI_LeftBracket: return EK_LSQUARE; 269 | case kVK_ANSI_RightBracket: return EK_RSQUARE; 270 | case kVK_ANSI_Period: return EK_DOT; 271 | default: return 0; 272 | } 273 | } 274 | #endif 275 | 276 | static void _onevent(NSEvent * event) 277 | { 278 | if(!event) 279 | return; 280 | 281 | NSEventType eventType = [event type]; 282 | switch(eventType) 283 | { 284 | #ifdef ENTRYPOINT_PROVIDE_INPUT 285 | case NSEventTypeMouseMoved: 286 | case NSEventTypeLeftMouseDragged: 287 | case NSEventTypeRightMouseDragged: 288 | case NSEventTypeOtherMouseDragged: 289 | { 290 | NSWindow * current_window = [NSApp keyWindow]; 291 | NSRect adjust_frame = [[current_window contentView] frame]; 292 | NSPoint p = [current_window mouseLocationOutsideOfEventStream]; 293 | 294 | // map input to content view rect 295 | if(p.x < 0) 296 | p.x = 0; 297 | else if(p.x > adjust_frame.size.width) 298 | p.x = adjust_frame.size.width; 299 | if(p.y < 0) 300 | p.y = 0; 301 | else if(p.y > adjust_frame.size.height) 302 | p.y = adjust_frame.size.height; 303 | 304 | p.y = adjust_frame.size.height - p.y; 305 | 306 | // map input to pixels 307 | NSRect r = {p.x, p.y, 0, 0}; 308 | #ifdef ENTRYPOINT_MACOS_RETINA 309 | r = [[current_window contentView] convertRectToBacking:r]; 310 | #endif 311 | p = r.origin; 312 | 313 | ctx.touch.x = p.x; 314 | ctx.touch.y = p.y; 315 | ctx.touch.multitouch[0].x = p.x; 316 | ctx.touch.multitouch[0].y = p.y; 317 | break; 318 | } 319 | case NSEventTypeLeftMouseDown: ctx.touch.left = 1; ctx.touch.multitouch[0].touched = true; break; 320 | case NSEventTypeLeftMouseUp: ctx.touch.left = 0; ctx.touch.multitouch[0].touched = false; break; 321 | case NSEventTypeRightMouseDown: ctx.touch.right = 1; break; 322 | case NSEventTypeRightMouseUp: ctx.touch.right = 0; break; 323 | case NSEventTypeOtherMouseDown: 324 | if([event buttonNumber] == 2) // number == 2 is a middle button 325 | ctx.touch.middle = 1; 326 | break; 327 | case NSEventTypeOtherMouseUp: 328 | if([event buttonNumber] == 2) // number == 2 is a middle button 329 | ctx.touch.middle = 0; 330 | break; 331 | // case NSScrollWheel: 332 | // { 333 | // CGFloat deltaX = [event scrollingDeltaX]; 334 | // CGFloat deltaY = [event scrollingDeltaY]; 335 | // 336 | // BOOL precisionScrolling = [event hasPreciseScrollingDeltas]; 337 | // if(precisionScrolling) 338 | // { 339 | // deltaX *= 0.1f; // similar to glfw 340 | // deltaY *= 0.1f; 341 | // } 342 | // 343 | // if(fabs(deltaX) > 0.0f || fabs(deltaY) > 0.0f) 344 | // printf("mouse scroll wheel delta %f %f\n", deltaX, deltaY); 345 | // break; 346 | // } 347 | case NSEventTypeFlagsChanged: 348 | { 349 | NSEventModifierFlags modifiers = [event modifierFlags]; 350 | 351 | // based on NSEventModifierFlags 352 | struct 353 | { 354 | union 355 | { 356 | struct 357 | { 358 | uint8_t alpha_shift:1; 359 | uint8_t shift:1; 360 | uint8_t control:1; 361 | uint8_t alternate:1; 362 | uint8_t command:1; 363 | uint8_t numeric_pad:1; 364 | uint8_t help:1; 365 | uint8_t function:1; 366 | }; 367 | uint8_t mask; 368 | }; 369 | } keys; 370 | 371 | keys.mask = (modifiers & NSEventModifierFlagDeviceIndependentFlagsMask) >> 16; 372 | 373 | // TODO L,R variation of keys? 374 | ctx.keys[EK_LCONTROL] = keys.alpha_shift; 375 | ctx.keys[EK_LSHIFT] = keys.shift; 376 | ctx.keys[EK_LCONTROL] = keys.control; 377 | ctx.keys[EK_RCONTROL] = keys.alpha_shift; 378 | ctx.keys[EK_RSHIFT] = keys.shift; 379 | ctx.keys[EK_RCONTROL] = keys.control; 380 | ctx.keys[EK_ALT] = keys.alternate; 381 | ctx.keys[EK_LWIN] = keys.command; 382 | ctx.keys[EK_RWIN] = keys.command; 383 | break; 384 | } 385 | case NSEventTypeKeyDown: 386 | { 387 | NSString * characters = [event characters]; 388 | NSData * utf32 = [characters dataUsingEncoding:NSUTF32StringEncoding]; 389 | uint32_t * text = (uint32_t*)[utf32 bytes]; 390 | if(text) 391 | ctx.last_char = text[text[0] == 0xfeff ? 1 : 0]; 392 | ctx.keys[_macoskey([event keyCode])] = 1; 393 | break; 394 | } 395 | case NSEventTypeKeyUp: 396 | ctx.keys[_macoskey([event keyCode])] = 0; 397 | break; 398 | #endif 399 | default: 400 | break; 401 | } 402 | 403 | if(eventType != NSEventTypeKeyDown && eventType != NSEventTypeKeyUp) 404 | [NSApp sendEvent:event]; 405 | 406 | // if user closes the window we might need to terminate asap 407 | if(!ctx.terminated) 408 | [NSApp updateWindows]; 409 | } 410 | 411 | static void _process_events() 412 | { 413 | NSEvent * event = nil; 414 | while((event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES]) && !ctx.terminated) 415 | _onevent(event); 416 | } 417 | 418 | ep_size_t ep_size() 419 | { 420 | ep_size_t r; 421 | 422 | NSWindow * current_window = [NSApp keyWindow]; 423 | NSRect rect = [[current_window contentView] frame]; 424 | #ifdef ENTRYPOINT_MACOS_RETINA 425 | rect = [[current_window contentView] convertRectToBacking:rect]; 426 | #endif 427 | r.w = rect.size.width; 428 | r.h = rect.size.height; 429 | 430 | return r; 431 | } 432 | 433 | bool ep_retina() 434 | { 435 | #ifdef ENTRYPOINT_MACOS_RETINA 436 | return true; 437 | #else 438 | return false; 439 | #endif 440 | } 441 | 442 | ep_ui_margins_t ep_ui_margins() 443 | { 444 | ep_ui_margins_t r = {0, 0, 0, 0}; 445 | return r; 446 | } 447 | 448 | // ----------------------------------------------------------------------------- 449 | 450 | static bool _infocus() {return [NSApp keyWindow] == ctx.window;} 451 | 452 | int main(int argc, char * argv[]) 453 | { 454 | @autoreleasepool 455 | { 456 | ctx.argc = argc; 457 | ctx.argv = argv; 458 | 459 | ENTRYPOINT_MACOS_PREPARE_PARAMS; 460 | 461 | // poke it first to get things going 462 | [NSApplication sharedApplication]; 463 | [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; 464 | AppDelegate * dg =[[AppDelegate alloc] init]; // needs to be on a separate line 465 | [NSApp setDelegate:dg]; 466 | [NSApp finishLaunching]; // only needed if we don't use [NSApp run] 467 | 468 | // create main menu 469 | NSMenu * menubar = [[NSMenu alloc] init]; 470 | NSMenuItem * app_menu_item = [[NSMenuItem alloc] init]; 471 | [menubar addItem:app_menu_item]; 472 | [NSApp setMainMenu:menubar]; 473 | NSMenu * app_menu = [[NSMenu alloc] init]; 474 | NSString * app_name = [[NSProcessInfo processInfo] processName]; 475 | NSString * quit_title = [@"Quit " stringByAppendingString:app_name]; 476 | NSMenuItem * menuitem = [[NSMenuItem alloc] initWithTitle:quit_title action:@selector(terminate:) keyEquivalent:@"q"]; 477 | [app_menu addItem:menuitem]; 478 | [app_menu_item setSubmenu:app_menu]; 479 | 480 | // create the window 481 | NSWindow * window = [[NSWindow alloc] 482 | initWithContentRect:NSMakeRect(0, 0, ENTRYPOINT_MACOS_WIDTH, ENTRYPOINT_MACOS_HEIGHT) 483 | styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable 484 | backing:NSBackingStoreBuffered 485 | defer:NO]; 486 | ctx.window = (__bridge void *)(window); // we still own it here, but put a reference for everyone else 487 | [window setReleasedWhenClosed:NO]; 488 | ctx.window_count = 1; 489 | WindowDelegate * wdg = [[WindowDelegate alloc] init]; 490 | [window setDelegate:wdg]; 491 | 492 | // configure the view 493 | NSView * content_view = [window contentView]; 494 | #ifdef ENTRYPOINT_MACOS_RETINA 495 | [content_view setWantsBestResolutionOpenGLSurface:YES]; 496 | #endif 497 | 498 | [window cascadeTopLeftFromPoint:NSMakePoint(ENTRYPOINT_MACOS_START_X, ENTRYPOINT_MACOS_START_Y)]; 499 | [window setTitle:ENTRYPOINT_MACOS_TITLE]; 500 | [window makeKeyAndOrderFront:window]; 501 | [window setAcceptsMouseMovedEvents:YES]; 502 | [window setBackgroundColor:[NSColor blackColor]]; 503 | 504 | [NSApp activateIgnoringOtherApps:YES]; // TODO do we really need this? 505 | 506 | _process_events(); 507 | 508 | int32_t result_code = 0; 509 | if((result_code = entrypoint_init(ctx.argc, ctx.argv)) != 0) 510 | return result_code; 511 | 512 | while(entrypoint_loop() == 0 && ctx.terminated == false) 513 | { 514 | #ifdef ENTRYPOINT_PROVIDE_INPUT 515 | if(_infocus()) 516 | memcpy(ctx.prev, ctx.keys, sizeof(ctx.prev)); 517 | #endif 518 | 519 | _process_events(); 520 | } 521 | 522 | if((result_code = entrypoint_might_unload()) != 0) 523 | return result_code; 524 | 525 | if((result_code = entrypoint_deinit()) != 0) 526 | return result_code; 527 | 528 | return 0; 529 | } 530 | } 531 | 532 | 533 | // ----------------------------------------------------------------------------- 534 | 535 | #ifdef ENTRYPOINT_PROVIDE_TIME 536 | 537 | double ep_delta_time() 538 | { 539 | if(ctx.timebase_info.denom == 0) 540 | { 541 | mach_timebase_info(&ctx.timebase_info); 542 | ctx.prev_time = mach_absolute_time(); 543 | return 0.0f; 544 | } 545 | 546 | uint64_t current_time = mach_absolute_time(); 547 | double elapsed = (double)(current_time - ctx.prev_time) * ctx.timebase_info.numer / (ctx.timebase_info.denom * 1000000000.0); 548 | ctx.prev_time = current_time; 549 | return elapsed; 550 | } 551 | 552 | void ep_sleep(double seconds) 553 | { 554 | usleep((useconds_t)(seconds * 1000000.0)); 555 | } 556 | 557 | #endif 558 | 559 | // ----------------------------------------------------------------------------- 560 | 561 | #ifdef ENTRYPOINT_PROVIDE_LOG 562 | 563 | void ep_log(const char * message, ...) 564 | { 565 | va_list args; 566 | va_start(args, message); 567 | vprintf(message, args); 568 | fflush(stdout); 569 | va_end(args); 570 | } 571 | 572 | #endif 573 | 574 | // ----------------------------------------------------------------------------- 575 | 576 | #ifdef ENTRYPOINT_PROVIDE_INPUT 577 | 578 | void ep_touch(ep_touch_t * touch) 579 | { 580 | if(touch) 581 | *touch = ctx.touch; 582 | } 583 | 584 | bool ep_khit(int32_t key) 585 | { 586 | if(!_infocus()) 587 | return 0; 588 | assert(key < sizeof(ctx.keys)); 589 | if (key >= 'a' && key <= 'z') key = key - 'a' + 'A'; 590 | return ctx.keys[key] && !ctx.prev[key]; 591 | } 592 | 593 | bool ep_kdown(int32_t key) 594 | { 595 | if(!_infocus()) 596 | return 0; 597 | assert(key < sizeof(ctx.keys)); 598 | if (key >= 'a' && key <= 'z') key = key - 'a' + 'A'; 599 | return ctx.keys[key]; 600 | } 601 | 602 | uint32_t ep_kchar() 603 | { 604 | if(!_infocus()) 605 | return 0; 606 | uint32_t k = ctx.last_char; 607 | ctx.last_char = 0; 608 | return k; 609 | } 610 | 611 | #endif 612 | 613 | // ----------------------------------------------------------------------------- 614 | 615 | #ifdef ENTRYPOINT_PROVIDE_OPENURL 616 | 617 | void ep_openurl(const char * url) 618 | { 619 | // TODO 620 | } 621 | 622 | #endif 623 | 624 | // ----------------------------------------------------------------------------- 625 | 626 | #endif 627 | -------------------------------------------------------------------------------- /entrypoint_win.c: -------------------------------------------------------------------------------- 1 | 2 | // TODO: 3 | // - would be nice to have proper fullscreen mode 4 | 5 | #ifdef _WIN32 6 | 7 | #define ENTRYPOINT_CTX 8 | #include "entrypoint.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #if defined(ENTRYPOINT_WINDOWS_NEED_ARGS) && defined(ENTRYPOINT_WINDOWS_WINMAIN) 14 | #include 15 | #endif 16 | 17 | static entrypoint_ctx_t ctx = {0}; 18 | entrypoint_ctx_t * ep_ctx() {return &ctx;} 19 | 20 | // ----------------------------------------------------------------------------- 21 | 22 | #ifdef ENTRYPOINT_WINDOWS_LINKER_OPT 23 | #pragma comment(lib, "User32.lib") 24 | #ifdef ENTRYPOINT_WINDOWS_SUBSYSTEM 25 | #ifdef _MSC_VER 26 | #pragma comment(linker, "/subsystem:windows") 27 | #ifdef ENTRYPOINT_WINDOWS_WINMAIN 28 | #ifdef ENTRYPOINT_WINDOWS_NEED_ARGS 29 | #pragma comment(lib, "Shell32.lib") 30 | #endif 31 | #else 32 | // use main directly 33 | #pragma comment(linker, "/ENTRY:mainCRTStartup") 34 | #endif 35 | #endif 36 | #endif 37 | #endif 38 | 39 | // ----------------------------------------------------------------------------- 40 | 41 | static void _fail(const char * msg) 42 | { 43 | fprintf(stderr, "%s - failed, error code %u\n", msg, (uint32_t)GetLastError()); 44 | } 45 | 46 | // ----------------------------------------------------------------------------- 47 | 48 | static void _enter_borderless() 49 | { 50 | if(ctx.flag_borderless) 51 | return; 52 | 53 | if(!GetWindowRect(ctx.hwnd, &ctx.rect_saved)) 54 | { 55 | _fail("get window rect"); 56 | return; 57 | } 58 | 59 | MONITORINFO mi = {sizeof(mi)}; 60 | if(!GetMonitorInfo(MonitorFromWindow(ctx.hwnd, MONITOR_DEFAULTTONEAREST), &mi)) 61 | { 62 | _fail("get monitor info"); 63 | return; 64 | } 65 | 66 | DWORD old_style = ctx.dwStyle; 67 | ctx.dwStyle = WS_VISIBLE | WS_POPUP; 68 | if(!SetWindowLong(ctx.hwnd, GWL_STYLE, ctx.dwStyle)) 69 | { 70 | ctx.dwStyle = old_style; 71 | _fail("set window style"); 72 | return; 73 | } 74 | 75 | if(!SetWindowPos(ctx.hwnd, HWND_TOP, 76 | mi.rcMonitor.left, 77 | mi.rcMonitor.top, 78 | mi.rcMonitor.right - mi.rcMonitor.left, 79 | mi.rcMonitor.bottom - mi.rcMonitor.top, 80 | 0)) 81 | { 82 | _fail("set window pos"); 83 | return; 84 | } 85 | 86 | ctx.flag_borderless = 1; 87 | } 88 | 89 | static void _leave_borderless() 90 | { 91 | if(!ctx.flag_borderless) 92 | return; 93 | 94 | DWORD old_style = ctx.dwStyle; 95 | ctx.dwStyle = WS_VISIBLE | WS_OVERLAPPEDWINDOW; 96 | if(!SetWindowLong(ctx.hwnd, GWL_STYLE, ctx.dwStyle)) 97 | { 98 | ctx.dwStyle = old_style; 99 | _fail("set window style"); 100 | return; 101 | } 102 | 103 | if(!SetWindowPos(ctx.hwnd, NULL, 104 | ctx.rect_saved.left, 105 | ctx.rect_saved.top, 106 | ctx.rect_saved.right - ctx.rect_saved.left, 107 | ctx.rect_saved.bottom - ctx.rect_saved.top, 108 | 0)) 109 | { 110 | _fail("set window pos"); 111 | return; 112 | } 113 | 114 | ctx.flag_borderless = 0; 115 | } 116 | 117 | // ----------------------------------------------------------------------------- 118 | 119 | LRESULT CALLBACK epWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 120 | { 121 | switch(message) 122 | { 123 | case WM_PAINT: 124 | { 125 | #ifdef ENTRYPOINT_WINDOWS_DO_WMPAINT 126 | PAINTSTRUCT ps; 127 | HDC hdc = BeginPaint(ctx.hwnd, &ps); 128 | FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1)); 129 | EndPaint(ctx.hwnd, &ps); 130 | #endif 131 | ValidateRect(hWnd, NULL); 132 | return 0; 133 | } 134 | case WM_CLOSE: 135 | ctx.flag_want_to_close = 1; 136 | return 0; 137 | case WM_GETMINMAXINFO: 138 | { 139 | MINMAXINFO * info = (MINMAXINFO*)lParam; 140 | info->ptMinTrackSize.x = ENTRYPOINT_WINDOWS_MIN_WIDTH; 141 | info->ptMinTrackSize.y = ENTRYPOINT_WINDOWS_MIN_HEIGHT; 142 | return 0; 143 | } 144 | case WM_SIZING: 145 | return TRUE; 146 | case WM_SIZE: 147 | // if someone tried to maximize us (e.g. via shortcut launch options), prefer instead to be borderless 148 | if (wParam == SIZE_MAXIMIZED) 149 | { 150 | ShowWindow(ctx.hwnd, SW_NORMAL); 151 | _enter_borderless(); 152 | } 153 | return 0; 154 | #ifdef ENTRYPOINT_PROVIDE_INPUT 155 | case WM_ACTIVATE: 156 | memset(ctx.keys, 0, sizeof(ctx.keys)); 157 | memset(ctx.prev, 0, sizeof(ctx.prev)); 158 | ctx.last_char = 0; 159 | return 0; 160 | case WM_CHAR: 161 | if(wParam == '\r') 162 | wParam = '\n'; 163 | ctx.last_char = (uint32_t)wParam; 164 | break; 165 | case WM_MENUCHAR: 166 | if(LOWORD(wParam) == VK_RETURN) // disable beep on Alt+Enter 167 | return MNC_CLOSE << 16; 168 | break; 169 | case WM_SYSKEYDOWN: 170 | if(wParam == VK_RETURN) 171 | { 172 | // Alt+Enter 173 | if(ctx.dwStyle & WS_POPUP) 174 | _leave_borderless(); 175 | else 176 | _enter_borderless(); 177 | return 0; 178 | } // fall through 179 | case WM_KEYDOWN: 180 | ctx.keys[wParam] = 1; 181 | break; 182 | case WM_SYSKEYUP: 183 | case WM_KEYUP: 184 | ctx.keys[wParam] = 0; 185 | break; 186 | #endif 187 | default: 188 | break; 189 | } 190 | return DefWindowProcW(hWnd, message, wParam, lParam); 191 | } 192 | 193 | // ----------------------------------------------------------------------------- 194 | 195 | ep_size_t ep_size() 196 | { 197 | ep_size_t r; 198 | GetClientRect(ctx.hwnd, &ctx.rect); 199 | r.w = (uint16_t)(ctx.rect.right - ctx.rect.left); 200 | r.h = (uint16_t)(ctx.rect.bottom - ctx.rect.top); 201 | return r; 202 | } 203 | 204 | bool ep_retina() 205 | { 206 | return false; // TODO 207 | } 208 | 209 | ep_ui_margins_t ep_ui_margins() 210 | { 211 | ep_ui_margins_t r = {0, 0, 0, 0}; 212 | return r; 213 | } 214 | 215 | // ----------------------------------------------------------------------------- 216 | 217 | #ifdef ENTRYPOINT_WINDOWS_NEED_ARGS 218 | static void _getargs() 219 | { 220 | LPWSTR * wargv = CommandLineToArgvW(GetCommandLineW(), &ctx.argc); 221 | if(wargv) 222 | { 223 | ctx.argv = (char**)calloc(ctx.argc + 1, sizeof(char*)); 224 | for(int i = 0; i < ctx.argc; ++i) 225 | { 226 | int len = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, 0, 0, NULL, NULL); 227 | if(len) 228 | { 229 | ctx.argv[i] = (char*)calloc(len, sizeof(char)); 230 | WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, ctx.argv[i], len, NULL, NULL); 231 | } 232 | else 233 | ctx.argv[i] = (char*)calloc(1, sizeof(char)); 234 | } 235 | LocalFree(wargv); 236 | } 237 | } 238 | static void _freeargs() 239 | { 240 | for(int i = 0; i < ctx.argc; ++i) 241 | free(ctx.argv[i]); 242 | if(ctx.argv) 243 | free(ctx.argv); 244 | ctx.argc = 0; 245 | ctx.argv = NULL; 246 | } 247 | #else 248 | static void _getargs(){} // noop 249 | static void _freeargs(){} 250 | #endif 251 | 252 | // ----------------------------------------------------------------------------- 253 | 254 | static int32_t _freewindow(int32_t errorcode) 255 | { 256 | if(!DestroyWindow(ctx.hwnd)) 257 | { 258 | _fail("destroy window"); 259 | _freeargs(); 260 | return 1; 261 | } 262 | 263 | ctx.hwnd = 0; 264 | 265 | if(!UnregisterClassW(ENTRYPOINT_WINDOWS_CLASS, GetModuleHandle(NULL))) 266 | { 267 | _fail("unregister class"); 268 | _freeargs(); 269 | return 1; 270 | } 271 | 272 | _freeargs(); 273 | return errorcode; 274 | } 275 | 276 | #ifdef ENTRYPOINT_WINDOWS_WINMAIN 277 | #ifdef UNICODE 278 | int CALLBACK wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) 279 | #else 280 | int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) 281 | #endif 282 | { 283 | ctx.argc = 0; 284 | ctx.argv = NULL; 285 | (void)hInstance; 286 | (void)hPrevInstance; 287 | (void)lpCmdLine; 288 | (void)nCmdShow; 289 | _getargs(); 290 | 291 | #else 292 | int main(int argc, char * argv[]) 293 | { 294 | ctx.argc = argc; 295 | ctx.argv = argv; 296 | #endif 297 | 298 | ENTRYPOINT_WINDOWS_PREPARE_PARAMS; 299 | 300 | WNDCLASSEXW wcex = {0}; 301 | wcex.cbSize = sizeof(WNDCLASSEXW); 302 | wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; 303 | wcex.lpfnWndProc = epWndProc; 304 | wcex.hInstance = GetModuleHandle(NULL); 305 | wcex.hIcon = NULL; 306 | wcex.hCursor = LoadCursor(NULL, IDC_ARROW); 307 | wcex.lpszClassName = ENTRYPOINT_WINDOWS_CLASS; 308 | if(!RegisterClassExW(&wcex)) 309 | { 310 | _fail("register class"); 311 | _freeargs(); 312 | return 1; 313 | } 314 | 315 | DWORD dwStyle = WS_OVERLAPPEDWINDOW; 316 | RECT rc = {0, 0, ENTRYPOINT_WINDOWS_WIDTH, ENTRYPOINT_WINDOWS_HEIGHT}; 317 | AdjustWindowRect(&rc, dwStyle, FALSE); 318 | 319 | ctx.hwnd = CreateWindowW(ENTRYPOINT_WINDOWS_CLASS, ENTRYPOINT_WINDOWS_TITLE, dwStyle, 320 | CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, 321 | NULL, NULL, wcex.hInstance, NULL); 322 | 323 | if(!ctx.hwnd) 324 | { 325 | _fail("create window"); 326 | _freeargs(); 327 | return 1; 328 | } 329 | 330 | ShowWindow(ctx.hwnd, SW_SHOW); 331 | SetForegroundWindow(ctx.hwnd); 332 | SetFocus(ctx.hwnd); 333 | 334 | int32_t result_code = 0; 335 | if((result_code = entrypoint_init(ctx.argc, ctx.argv)) != 0) 336 | { 337 | _fail("entrypoint init"); 338 | return _freewindow(result_code); 339 | } 340 | 341 | while(entrypoint_loop() == 0 && ctx.flag_want_to_close == false) 342 | { 343 | #ifdef ENTRYPOINT_PROVIDE_INPUT 344 | memcpy(ctx.prev, ctx.keys, sizeof(ctx.prev)); 345 | #endif 346 | 347 | MSG msg; 348 | while(PeekMessage(&msg, ctx.hwnd, 0, 0, PM_REMOVE)) 349 | { 350 | if(msg.message == WM_QUIT) 351 | { 352 | ctx.flag_want_to_close = 1; 353 | break; 354 | } 355 | TranslateMessage(&msg); 356 | DispatchMessage(&msg); 357 | } 358 | 359 | GetClientRect(ctx.hwnd, &ctx.rect); 360 | } 361 | 362 | if((result_code = entrypoint_might_unload()) != 0) 363 | { 364 | _fail("entrypoint might unload"); 365 | return _freewindow(result_code); 366 | } 367 | 368 | if((result_code = entrypoint_deinit()) != 0) 369 | { 370 | _fail("entrypoint deinit"); 371 | return _freewindow(result_code); 372 | } 373 | 374 | return 0; 375 | } 376 | 377 | #endif 378 | 379 | // ----------------------------------------------------------------------------- 380 | #ifdef ENTRYPOINT_PROVIDE_TIME 381 | 382 | double ep_delta_time() 383 | { 384 | LARGE_INTEGER qpc, freq; 385 | QueryPerformanceCounter(&qpc); 386 | QueryPerformanceFrequency(&freq); 387 | 388 | if(ctx.flag_time_set) 389 | { 390 | ULONGLONG diff = qpc.QuadPart - ctx.prev_qpc_time.QuadPart; 391 | ctx.prev_qpc_time = qpc; 392 | return diff / (double)freq.QuadPart; 393 | } 394 | else 395 | { 396 | ctx.flag_time_set = 1; 397 | ctx.prev_qpc_time = qpc; 398 | return 0; 399 | } 400 | } 401 | 402 | void ep_sleep(double seconds) 403 | { 404 | if(seconds > 0.0f) 405 | Sleep((uint32_t)(seconds * 1000)); 406 | } 407 | 408 | #endif 409 | 410 | // ----------------------------------------------------------------------------- 411 | 412 | #ifdef ENTRYPOINT_PROVIDE_LOG 413 | 414 | void ep_log(const char * message, ...) 415 | { 416 | va_list args; 417 | va_start(args, message); 418 | vprintf(message, args); 419 | fflush(stdout); 420 | va_end(args); 421 | } 422 | 423 | #endif 424 | 425 | // ----------------------------------------------------------------------------- 426 | 427 | #ifdef ENTRYPOINT_PROVIDE_INPUT 428 | 429 | void ep_touch(ep_touch_t * touch) 430 | { 431 | if(!touch) 432 | return; 433 | 434 | POINT pt; 435 | GetCursorPos(&pt); 436 | ScreenToClient(ctx.hwnd, &pt); 437 | GetClientRect(ctx.hwnd, &ctx.rect); 438 | touch->x = (float)(pt.x - ctx.rect.left); 439 | touch->y = (float)(pt.y - ctx.rect.top); 440 | 441 | touch->flags = 0; 442 | if(GetFocus() == ctx.hwnd) 443 | { 444 | if(GetAsyncKeyState(VK_LBUTTON) & 0x8000) touch->left = 1; 445 | if(GetAsyncKeyState(VK_MBUTTON) & 0x8000) touch->middle = 1; 446 | if(GetAsyncKeyState(VK_RBUTTON) & 0x8000) touch->right = 1; 447 | } 448 | 449 | touch->multitouch[0].x = touch->x; 450 | touch->multitouch[0].y = touch->y; 451 | touch->multitouch[0].touched = touch->left; 452 | } 453 | 454 | int32_t _vkkey(int32_t key) 455 | { 456 | if (key >= 'A' && key <= 'Z') return key; 457 | if (key >= 'a' && key <= 'z') return key - 'a' + 'A'; 458 | if (key >= '0' && key <= '9') return key; 459 | switch (key) { 460 | case EK_BACKSPACE: return VK_BACK; 461 | case EK_TAB: return VK_TAB; 462 | case EK_RETURN: return VK_RETURN; 463 | case EK_ALT: return VK_MENU; 464 | case EK_PAUSE: return VK_PAUSE; 465 | case EK_CAPSLOCK: return VK_CAPITAL; 466 | case EK_ESCAPE: return VK_ESCAPE; 467 | case EK_SPACE: return VK_SPACE; 468 | case EK_PAGEUP: return VK_PRIOR; 469 | case EK_PAGEDN: return VK_NEXT; 470 | case EK_END: return VK_END; 471 | case EK_HOME: return VK_HOME; 472 | case EK_LEFT: return VK_LEFT; 473 | case EK_UP: return VK_UP; 474 | case EK_RIGHT: return VK_RIGHT; 475 | case EK_DOWN: return VK_DOWN; 476 | case EK_INSERT: return VK_INSERT; 477 | case EK_DELETE: return VK_DELETE; 478 | case EK_LWIN: return VK_LWIN; 479 | case EK_RWIN: return VK_RWIN; 480 | //casEKTK_APPS: return VK_APPS; // this key doesn't exist on OS X 481 | case EK_PAD0: return VK_NUMPAD0; 482 | case EK_PAD1: return VK_NUMPAD1; 483 | case EK_PAD2: return VK_NUMPAD2; 484 | case EK_PAD3: return VK_NUMPAD3; 485 | case EK_PAD4: return VK_NUMPAD4; 486 | case EK_PAD5: return VK_NUMPAD5; 487 | case EK_PAD6: return VK_NUMPAD6; 488 | case EK_PAD7: return VK_NUMPAD7; 489 | case EK_PAD8: return VK_NUMPAD8; 490 | case EK_PAD9: return VK_NUMPAD9; 491 | case EK_PADMUL: return VK_MULTIPLY; 492 | case EK_PADADD: return VK_ADD; 493 | case EK_PADENTER: return VK_SEPARATOR; 494 | case EK_PADSUB: return VK_SUBTRACT; 495 | case EK_PADDOT: return VK_DECIMAL; 496 | case EK_PADDIV: return VK_DIVIDE; 497 | case EK_F1: return VK_F1; 498 | case EK_F2: return VK_F2; 499 | case EK_F3: return VK_F3; 500 | case EK_F4: return VK_F4; 501 | case EK_F5: return VK_F5; 502 | case EK_F6: return VK_F6; 503 | case EK_F7: return VK_F7; 504 | case EK_F8: return VK_F8; 505 | case EK_F9: return VK_F9; 506 | case EK_F10: return VK_F10; 507 | case EK_F11: return VK_F11; 508 | case EK_F12: return VK_F12; 509 | case EK_NUMLOCK: return VK_NUMLOCK; 510 | case EK_SCROLL: return VK_SCROLL; 511 | case EK_LSHIFT: return VK_LSHIFT; 512 | case EK_RSHIFT: return VK_RSHIFT; 513 | case EK_LCONTROL: return VK_LCONTROL; 514 | case EK_RCONTROL: return VK_RCONTROL; 515 | case EK_LALT: return VK_LMENU; 516 | case EK_RALT: return VK_RMENU; 517 | case EK_SEMICOLON: return VK_OEM_1; 518 | case EK_EQUALS: return VK_OEM_PLUS; 519 | case EK_COMMA: return VK_OEM_COMMA; 520 | case EK_MINUS: return VK_OEM_MINUS; 521 | case EK_DOT: return VK_OEM_PERIOD; 522 | case EK_SLASH: return VK_OEM_2; 523 | case EK_BACKTICK: return VK_OEM_3; 524 | case EK_LSQUARE: return VK_OEM_4; 525 | case EK_BACKSLASH: return VK_OEM_5; 526 | case EK_RSQUARE: return VK_OEM_6; 527 | case EK_TICK: return VK_OEM_7; 528 | } 529 | return 0; 530 | } 531 | 532 | bool ep_khit(int32_t key) 533 | { 534 | if(GetFocus() != ctx.hwnd) 535 | return 0; 536 | int k = _vkkey(key); 537 | return ctx.keys[k] && !ctx.prev[k]; 538 | } 539 | 540 | bool ep_kdown(int32_t key) 541 | { 542 | if(GetFocus() != ctx.hwnd) 543 | return 0; 544 | int k = _vkkey(key); 545 | return ctx.keys[k]; 546 | } 547 | 548 | uint32_t ep_kchar() 549 | { 550 | if(GetFocus() != ctx.hwnd) 551 | return 0; 552 | uint32_t k = ctx.last_char; 553 | ctx.last_char = 0; 554 | return k; 555 | } 556 | 557 | #endif 558 | 559 | // ----------------------------------------------------------------------------- 560 | 561 | #ifdef ENTRYPOINT_PROVIDE_OPENURL 562 | 563 | #include 564 | 565 | void ep_openurl(const char * url) 566 | { 567 | ShellExecute(0, 0, url, 0, 0 , SW_SHOW ); 568 | } 569 | 570 | #endif 571 | 572 | -------------------------------------------------------------------------------- /example.c: -------------------------------------------------------------------------------- 1 | 2 | #include "entrypoint.h" 3 | #include 4 | 5 | int32_t entrypoint_init(int32_t argc, char * argv[]) 6 | { 7 | printf("why, hello there!\n"); 8 | return 0; 9 | } 10 | 11 | int32_t entrypoint_deinit() 12 | { 13 | printf("see you later!\n"); 14 | return 0; 15 | } 16 | 17 | int32_t entrypoint_might_unload() 18 | { 19 | printf("saving crucial data!\n"); 20 | return 0; 21 | } 22 | 23 | int32_t entrypoint_loop() 24 | { 25 | #ifdef ENTRYPOINT_PROVIDE_INPUT 26 | ep_touch_t t; 27 | ep_touch(&t); 28 | printf("touch %f %f %u %u %u\n", t.x, t.y, t.left, t.middle, t.right); 29 | 30 | uint32_t k = ep_kchar(); 31 | if(k) 32 | printf("key %u\n", k); 33 | 34 | if(ep_kdown(EK_ESCAPE)) 35 | return 1; 36 | #endif 37 | 38 | #ifdef ENTRYPOINT_PROVIDE_TIME 39 | printf("dt %f\n", ep_delta_time()); 40 | ep_sleep(0.016f); 41 | #endif 42 | 43 | return 0; 44 | } 45 | --------------------------------------------------------------------------------