├── platform ├── android │ ├── Sources │ │ ├── assets │ │ │ └── asset.txt │ │ └── res │ │ │ ├── mipmap │ │ │ └── icon.png │ │ │ └── values │ │ │ └── strings.xml │ ├── README.md │ ├── android_usb_devices.h │ ├── AndroidManifest.xml │ ├── AndroidManifest.xml.template │ ├── Makefile │ ├── android_usb_devices.c │ ├── android_native_app_glue.h │ └── android_native_app_glue.c └── wasm │ ├── template.ht │ ├── Makefile │ ├── subst.c │ └── template.js ├── src ├── fisiks.c ├── include │ ├── typedef.h │ ├── anim.h │ ├── event.h │ ├── keycode.h │ ├── color.h │ ├── grid.h │ ├── util.h │ └── os_generic.h ├── util.c ├── anim.c ├── event.c ├── main.c └── grid.c ├── README.md ├── .gitignore ├── LICENSE ├── nobuild.c ├── index.html └── nobuild.h /platform/android/Sources/assets/asset.txt: -------------------------------------------------------------------------------- 1 | Test asset file 2 | -------------------------------------------------------------------------------- /platform/android/README.md: -------------------------------------------------------------------------------- 1 | [CNLohr's guide](https://github.com/cnlohr/rawdrawandroid#steps-for-gui-less-install-windows-wsl) -------------------------------------------------------------------------------- /src/fisiks.c: -------------------------------------------------------------------------------- 1 | #include "main.c" 2 | #include "util.c" 3 | #include "grid.c" 4 | #include "anim.c" 5 | #include "event.c" 6 | -------------------------------------------------------------------------------- /platform/android/Sources/res/mipmap/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ciremun/fisiks/HEAD/platform/android/Sources/res/mipmap/icon.png -------------------------------------------------------------------------------- /src/include/typedef.h: -------------------------------------------------------------------------------- 1 | #ifndef _TYPEDEF_H_ 2 | #define _TYPEDEF_H_ 3 | 4 | typedef unsigned long long int u64; 5 | 6 | #endif // _TYPEDEF_H_ 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fisiks 2 | 3 | ## Build 4 | 5 | ### Windows, Linux 6 | 7 | cc nobuild.c -o nobuild && ./nobuild run 8 | 9 | ### other 10 | 11 | cd platform/${platform} 12 | make 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | makecapk/ 3 | build/ 4 | platform/wasm/index.html 5 | fisiks 6 | *.keystore 7 | *.apk 8 | *.obj 9 | *.exe 10 | *.old 11 | *.idsig 12 | opt.js 13 | subst 14 | main.wasm 15 | nobuild -------------------------------------------------------------------------------- /platform/android/Sources/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | fisiks 4 | fisiks 5 | org.ciremun.fisiks 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/include/anim.h: -------------------------------------------------------------------------------- 1 | #ifndef _ANIM_H_ 2 | #define _ANIM_H_ 3 | 4 | #include 5 | 6 | #define FADE_IN 0 7 | #define FADE_OUT 1 8 | #define IDLE 2 9 | #define HIDDEN 3 10 | 11 | typedef struct 12 | { 13 | uint32_t color; 14 | double duration; 15 | double start; 16 | int state; 17 | } Animation; 18 | 19 | void change_animation_state(Animation *a, int new_state); 20 | void display_message(char *msg); 21 | void set_fade_color(Animation *a); 22 | void draw_messages(); 23 | 24 | #endif // _ANIM_H_ 25 | -------------------------------------------------------------------------------- /src/include/event.h: -------------------------------------------------------------------------------- 1 | #ifndef _EVENT_H_ 2 | #define _EVENT_H_ 3 | 4 | #include "util.h" 5 | 6 | void EXPORT("HandleKey") HandleKey(int keycode, int bDown); 7 | void EXPORT("HandleButton") HandleButton(int x, int y, int button, int bDown); 8 | void HandleDestroy(); 9 | 10 | #ifndef __wasm__ 11 | void HandleSuspend(); 12 | void HandleResume(); 13 | #endif // __wasm__ 14 | 15 | typedef struct 16 | { 17 | int mouse_x; 18 | int mouse_y; 19 | int lmb_down; 20 | int rmb_down; 21 | } Controls; 22 | 23 | #endif // _EVENT_H_ 24 | -------------------------------------------------------------------------------- /platform/android/android_usb_devices.h: -------------------------------------------------------------------------------- 1 | //Copyright 2020 <>< Charles Lohr, You may use this file and library freely under the MIT/x11, NewBSD or ColorChord Licenses. 2 | 3 | #ifndef _ANDROID_USB_DEVICES_H 4 | #define _ANDROID_USB_DEVICES_H 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | int RequestPermissionOrGetConnectionFD( char * debug_status, uint16_t vid, uint16_t pid ); 11 | void DisconnectUSB(); //Disconnect from USB 12 | 13 | extern jobject deviceConnection; 14 | extern int deviceConnectionFD; 15 | 16 | #endif 17 | 18 | -------------------------------------------------------------------------------- /src/include/keycode.h: -------------------------------------------------------------------------------- 1 | #ifndef _KEYCODE_H_ 2 | #define _KEYCODE_H_ 3 | 4 | #define SPACE_KEY 32 5 | #define ZERO_KEY 48 6 | #define ONE_KEY 49 7 | #define TWO_KEY 50 8 | #define NINE_KEY 57 9 | 10 | 11 | #ifdef __wasm__ 12 | #define R_KEY 82 13 | #define LMB_KEY 0 14 | #elif defined(__ANDROID__) 15 | #define LMB_KEY 0 16 | #define R_KEY 114 17 | #else 18 | #define LMB_KEY 1 19 | #define R_KEY 114 20 | #endif // __wasm__ 21 | 22 | 23 | #if defined(_WIN32) || defined(__wasm__) 24 | #define MINUS_KEY 189 25 | #define PLUS_KEY 187 26 | #define RMB_KEY 2 27 | #else 28 | #define MINUS_KEY 45 29 | #define PLUS_KEY 43 30 | #define EQ_KEY 61 31 | #define RMB_KEY 3 32 | #endif // _WIN32 33 | #endif // _KEYCODE_H_ 34 | -------------------------------------------------------------------------------- /src/include/color.h: -------------------------------------------------------------------------------- 1 | #ifndef _COLOR_H_ 2 | #define _COLOR_H_ 3 | 4 | #ifdef __wasm__ 5 | #define SWAPS(v) ((v>>24)&0xff) | ((v<<8)&0xff0000) | ((v>>8)&0xff00) | ((v<<24)&0xff000000) 6 | #define COLOR(c) SWAPS(c) 7 | #else 8 | #define COLOR(c) c 9 | #endif // __wasm__ 10 | 11 | #define TRANSPARENT_ 0XFFFFFF00 12 | #define WHITE COLOR(0XFFFFFFFF) 13 | #define BLACK COLOR(0X000000FF) 14 | #define CAMEL COLOR(0xB88B4AFF) 15 | #define FLAX COLOR(0xDDCA7DFF) 16 | #define GOLDEN_BROWN COLOR(0xA27035FF) 17 | #define CAFE_NOIR COLOR(0x533E2DFF) 18 | #define RAISIN_BLACK COLOR(0x242331FF) 19 | #define CARAMEL COLOR(0xFCBF49FF) 20 | #define MAX_YELLOW_RED COLOR(0xFEBE5DFF) 21 | #define SAND MAX_YELLOW_RED 22 | #define STATIC_SAND CARAMEL 23 | 24 | #endif // _COLOR_H_ 25 | -------------------------------------------------------------------------------- /platform/wasm/template.ht: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | fisiks 7 | 11 | 12 | 13 | 14 | 17 | GitHub 18 | 19 | 20 | -------------------------------------------------------------------------------- /platform/android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /platform/android/AndroidManifest.xml.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 <>< CNLohr 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 | -------------------------------------------------------------------------------- /src/include/grid.h: -------------------------------------------------------------------------------- 1 | #ifndef _GRID_H_ 2 | #define _GRID_H_ 3 | 4 | #define DEFAULT_CELL_SIZE 32 5 | 6 | #define GRID_SIZE(gs) (sizeof(Cell) * grid.rows * grid.cols) 7 | 8 | typedef enum 9 | { 10 | EMPTY = 0, 11 | ALIVE, 12 | STATIC, 13 | } CellState; 14 | 15 | typedef enum 16 | { 17 | TOP = 1 << 0, 18 | RIGHT = 1 << 1, 19 | BOTTOM = 1 << 2, 20 | LEFT = 1 << 3, 21 | ALL = (1 << 8) - 1, 22 | } Side; 23 | 24 | typedef struct 25 | { 26 | uint32_t color; 27 | CellState state; 28 | } Cell; 29 | 30 | typedef struct 31 | { 32 | int rows; 33 | int cols; 34 | Cell *cells; 35 | } Grid; 36 | 37 | void change_grid_size(int new_rows, int new_cols); 38 | void draw_cell(Cell cell, int x, int y); 39 | void draw_cells(); 40 | void cell_index(int x, int y, int *cell_x, int *cell_y); 41 | int row_exists(int cell_y); 42 | int col_exists(int cell_x); 43 | void toggle_cell(CellState state, int x, int y, uint32_t color); 44 | void apply_game_rules(int x, int y); 45 | void set_adjacent_cells_state_if_not_empty(Grid* grid, CellState state, int x, int y, uint8_t flags); 46 | void set_cell_state_at_if_not_empty(Grid *grid, uint64_t idx, CellState state); 47 | 48 | #endif // _GRID_H_ 49 | -------------------------------------------------------------------------------- /src/include/util.h: -------------------------------------------------------------------------------- 1 | #ifndef _UTIL_H_ 2 | #define _UTIL_H_ 3 | 4 | #ifdef __wasm__ 5 | #include 6 | #endif // __wasm__ 7 | 8 | #include "typedef.h" 9 | 10 | #ifndef min 11 | #define min(x, y) ((x) < (y) ? (x) : (y)) 12 | #endif // min 13 | 14 | #ifndef max 15 | #define max(x, y) ((x) > (y) ? (x) : (y)) 16 | #endif // min 17 | 18 | #ifdef __wasm__ 19 | #define EXPORT(s) __attribute__((export_name(s))) 20 | #else 21 | #define EXPORT(s) 22 | #endif // __wasm__ 23 | 24 | #ifdef __wasm__ 25 | #define malloc fisiks_malloc 26 | #define calloc fisiks_calloc 27 | #define realloc fisiks_realloc 28 | #define strlen fisiks_strlen 29 | #define memset fisiks_memset 30 | #define memcpy fisiks_memcpy 31 | #endif // __wasm__ 32 | 33 | #ifdef __wasm__ 34 | double OGGetAbsoluteTime(); 35 | u64 fisiks_strlen(const char *s); 36 | void *fisiks_memset(void *dest, int val, u64 len); 37 | void *fisiks_memcpy(void *dst, void const *src, u64 size); 38 | void *fisiks_malloc(u64 size); 39 | void *fisiks_calloc(u64 num, u64 size); 40 | void *fisiks_realloc(void *old_mem, u64 size); 41 | void print(double idebug); 42 | float ceilf(float i); 43 | #endif // __wasm__ 44 | 45 | void draw_text(const char *text, int x, int y, int font_size); 46 | 47 | typedef struct 48 | { 49 | u64 length; 50 | char *content; 51 | } String; 52 | 53 | #endif // _UTIL_H_ 54 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | #ifdef __wasm__ 4 | 5 | extern unsigned char __heap_base; 6 | 7 | char *heap = (char *)&__heap_base; 8 | 9 | u64 fisiks_strlen(const char *s) 10 | { 11 | u64 sz = 0; 12 | while (s[sz] != '\0') 13 | sz++; 14 | return sz; 15 | } 16 | 17 | void *fisiks_memset(void *dest, int val, u64 len) 18 | { 19 | unsigned char *ptr = dest; 20 | while (len-- > 0) 21 | *ptr++ = val; 22 | return dest; 23 | } 24 | 25 | void *fisiks_memcpy(void *dst, void const *src, u64 size) 26 | { 27 | unsigned char *source = (unsigned char *)src; 28 | unsigned char *dest = (unsigned char *)dst; 29 | while (size--) 30 | *dest++ = *source++; 31 | return dst; 32 | } 33 | 34 | void *fisiks_malloc(u64 size) 35 | { 36 | heap += size; 37 | return heap - size; 38 | } 39 | 40 | void *fisiks_calloc(u64 num, u64 size) 41 | { 42 | return fisiks_malloc(num * size); 43 | } 44 | 45 | void *fisiks_realloc(void *old_mem, u64 size) 46 | { 47 | // since we only have a grid 48 | u64 old_size = GRID_SIZE(grid); 49 | if (size <= old_size) 50 | { 51 | heap -= old_size; 52 | return heap; 53 | } 54 | void *new_mem = fisiks_malloc(size); 55 | fisiks_memcpy(new_mem, old_mem, old_size); 56 | return new_mem; 57 | } 58 | #else 59 | void print(double idebug) 60 | { 61 | (void)idebug; 62 | } 63 | #endif // __wasm__ 64 | 65 | void draw_text(const char *text, int x, int y, int font_size) 66 | { 67 | CNFGPenX = x; 68 | CNFGPenY = y; 69 | CNFGDrawText(text, font_size); 70 | } 71 | -------------------------------------------------------------------------------- /platform/wasm/Makefile: -------------------------------------------------------------------------------- 1 | all : subst index.html 2 | 3 | #For tools (Works in Ubuntu 20.04 (Including WSL), Mint) 4 | # sudo apt-get install clang-10 lld-10 binaryen 5 | # sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-10 20 6 | # sudo update-alternatives --install /usr/bin/wasm-ld wasm-ld /usr/bin/wasm-ld-10 20 7 | 8 | #node-uglify lld clang-10 lld-10 binaryen 9 | 10 | #Path to rawdraw 11 | CFLAGS:=-I.. 12 | 13 | CLANG?=clang 14 | WASMOPT?=wasm-opt 15 | TERSER?=terser 16 | TERSERFLAGS?= -ecma 2017 -d RAWDRAW_USE_LOOP_FUNCTION=false -d RAWDRAW_NEED_BLITTER=true 17 | 18 | CFLAGS+=-DWASM -nostdlib --target=wasm32 \ 19 | -I../../src/include \ 20 | -flto -Oz \ 21 | -Wl,--lto-O3 \ 22 | -Wl,--no-entry \ 23 | -Wl,--allow-undefined \ 24 | -Wl,--import-memory 25 | 26 | WOFLAGS:=--asyncify --pass-arg=asyncify-imports@bynsyncify.* --pass-arg=asyncify-ignore-indirect 27 | 28 | opt.js : template.js main.wasm 29 | cat main.wasm | base64 | sed -e "$$ ! {/./s/$$/ \\\\/}" > blob_b64; 30 | ./subst template.js -s -f BLOB blob_b64 -o mid.js 31 | #Comment the below line out if you don't want to uglify the javascript. 32 | $(TERSER) $(TERSERFLAGS) mid.js -o opt.js 33 | rm mid.js blob_b64 34 | 35 | index.html : template.ht opt.js 36 | ./subst template.ht -s -f JAVASCRIPT_DATA opt.js -o $@ 37 | 38 | subst : subst.c 39 | cc -o $@ $^ 40 | 41 | main.wasm: ../../src/fisiks.c 42 | $(CLANG) $(CFLAGS) $^ -o $@ 43 | $(WASMOPT) $(WOFLAGS) -Oz main.wasm -o main.wasm 44 | #wasm-objdump -d main.wasm > main.disassembly.txt 45 | 46 | clean: 47 | rm -rf main.wasm opt.js index.html blob_b64 48 | -------------------------------------------------------------------------------- /nobuild.c: -------------------------------------------------------------------------------- 1 | #define NOBUILD_IMPLEMENTATION 2 | #include "nobuild.h" 3 | 4 | #include 5 | #include 6 | 7 | #define CFLAGS "-Wall", "-Wextra", "-pedantic", "-std=c11", "-O3", "-g0", "-Isrc/include" 8 | #define MSVC_CFLAGS "/nologo", "/W3", "/std:c11", "/O2", "/Isrc/include/" 9 | 10 | #ifdef _WIN32 11 | #define DEFAULT_CC "cl" 12 | #define RUN(executable) CMD(".\\" executable ".exe") 13 | #else 14 | #define DEFAULT_CC "gcc" 15 | #define RUN(executable) CMD("./" executable) 16 | #endif // _WIN32 17 | 18 | #define SET_COMPILER_EXECUTABLE(env_var, runtime_var, default_executable) \ 19 | do \ 20 | { \ 21 | char *c = getenv(env_var); \ 22 | if (c == NULL) \ 23 | memcpy(runtime_var, default_executable, \ 24 | sizeof(default_executable)); \ 25 | else \ 26 | strcpy(runtime_var, c); \ 27 | } while (0) 28 | 29 | char cc[32]; 30 | 31 | void build() 32 | { 33 | SET_COMPILER_EXECUTABLE("cc", cc, DEFAULT_CC); 34 | if (strcmp(cc, "cl") == 0) 35 | { 36 | CMD(cc, MSVC_CFLAGS, "src/fisiks.c", "/Fe:", "fisiks"); 37 | } 38 | else 39 | { 40 | #ifdef _WIN32 41 | CMD(cc, CFLAGS, "src/fisiks.c", "-o", "fisiks", "-lGdi32"); 42 | #else 43 | CMD(cc, CFLAGS, "src/fisiks.c", "-o", "fisiks", "-lX11"); 44 | #endif // _WIN32 45 | } 46 | } 47 | 48 | void fmt() 49 | { 50 | CMD("astyle", "src/*.c", "src/include/*.h", "-n", "-r", "--style=allman"); 51 | } 52 | 53 | int main(int argc, char **argv) 54 | { 55 | GO_REBUILD_URSELF(argc, argv); 56 | 57 | if (argc > 1) 58 | { 59 | if (strcmp(argv[1], "run") == 0) 60 | { 61 | build(); 62 | RUN("fisiks"); 63 | return 0; 64 | } 65 | if (strcmp(argv[1], "fmt") == 0) 66 | { 67 | fmt(); 68 | return 0; 69 | } 70 | } 71 | 72 | build(); 73 | return 0; 74 | } -------------------------------------------------------------------------------- /src/anim.c: -------------------------------------------------------------------------------- 1 | #include "anim.h" 2 | 3 | extern int font_size; 4 | extern String message; 5 | 6 | Animation pause_a = 7 | { 8 | .color = WHITE, 9 | .duration = .5, 10 | .start = 0.0, 11 | .state = HIDDEN, 12 | }; 13 | 14 | Animation message_a = 15 | { 16 | .color = WHITE, 17 | .duration = 1.0, 18 | .start = 0.0, 19 | .state = HIDDEN, 20 | }; 21 | 22 | void change_animation_state(Animation *a, int new_state) 23 | { 24 | a->start = OGGetAbsoluteTime(); 25 | a->state = new_state; 26 | } 27 | 28 | void display_message(char *msg) 29 | { 30 | u64 message_length = strlen(msg); 31 | memset(message.content, 0, MAX_MESSAGE_SIZE); 32 | memcpy(message.content, msg, message_length + 1); 33 | message.length = message_length; 34 | message_a.start = OGGetAbsoluteTime(); 35 | change_animation_state(&message_a, FADE_IN); 36 | } 37 | 38 | void set_fade_color(Animation *a) 39 | { 40 | uint32_t new_color = 0; 41 | switch (a->state) 42 | { 43 | case FADE_IN: 44 | { 45 | double s_passed = absolute_time - a->start; 46 | if (s_passed >= a->duration) 47 | { 48 | a->state = IDLE; 49 | new_color = a->color; 50 | } 51 | else 52 | { 53 | new_color = (uint32_t)((a->color & TRANSPARENT_) 54 | + (s_passed / a->duration) * 255); 55 | } 56 | } 57 | break; 58 | case FADE_OUT: 59 | { 60 | double s_passed = absolute_time - a->start; 61 | if (s_passed >= a->duration) 62 | { 63 | a->state = HIDDEN; 64 | new_color = a->color & TRANSPARENT_; 65 | } 66 | else 67 | { 68 | new_color 69 | = (uint32_t)((a->color & TRANSPARENT_) 70 | + ((a->duration - s_passed) / a->duration) * 255); 71 | } 72 | } 73 | break; 74 | case IDLE: 75 | new_color = a->color; 76 | break; 77 | default: 78 | break; 79 | } 80 | CNFGColor(COLOR(new_color)); 81 | } 82 | 83 | void draw_messages() 84 | { 85 | if (pause_a.state != HIDDEN) 86 | { 87 | set_fade_color(&pause_a); 88 | draw_text("Paused", w - paused_t_width, 10, font_size); 89 | } 90 | if (message_a.state != HIDDEN) 91 | { 92 | if (message_a.start && absolute_time - message_a.start > 2) 93 | { 94 | message_a.start = 0; 95 | change_animation_state(&message_a, FADE_OUT); 96 | } 97 | set_fade_color(&message_a); 98 | draw_text(message.content, w / 2 - (int)message.length * 30, 120, font_size); 99 | } 100 | if (reset_t) 101 | { 102 | if (absolute_time - reset_t <= 1) 103 | { 104 | CNFGColor(WHITE); 105 | draw_text("Reset", 10, 10, font_size); 106 | } 107 | else 108 | { 109 | reset_t = 0; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/event.c: -------------------------------------------------------------------------------- 1 | #include "event.h" 2 | #include "util.h" 3 | #include "color.h" 4 | 5 | Controls controls = 6 | { 7 | .mouse_x = -1, 8 | .mouse_y = -1, 9 | .lmb_down = 0, 10 | .rmb_down = 0, 11 | }; 12 | 13 | void EXPORT("HandleKey") HandleKey(int keycode, int bDown) 14 | { 15 | if (bDown) 16 | switch (keycode) 17 | { 18 | case SPACE_KEY: 19 | paused = !paused; 20 | switch (pause_a.state) 21 | { 22 | case FADE_IN: 23 | pause_a.state = FADE_OUT; 24 | break; 25 | case FADE_OUT: 26 | pause_a.state = FADE_IN; 27 | break; 28 | case IDLE: 29 | change_animation_state(&pause_a, FADE_OUT); 30 | break; 31 | case HIDDEN: 32 | change_animation_state(&pause_a, FADE_IN); 33 | break; 34 | } 35 | break; 36 | case R_KEY: 37 | memcpy(cells_count_buffer, "0", 2); 38 | memset(grid.cells, 0, GRID_SIZE(grid)); 39 | memset(next_grid.cells, 0, GRID_SIZE(grid)); 40 | reset_t = (int)OGGetAbsoluteTime(); 41 | break; 42 | case MINUS_KEY: 43 | { 44 | int new_rows = grid.rows / 2; 45 | int new_cols = grid.cols / 2; 46 | if (new_rows <= 0 || new_cols <= 0) 47 | { 48 | display_message("too smol"); 49 | return; 50 | } 51 | change_grid_size(new_rows, new_cols); 52 | } 53 | break; 54 | #if !defined(_WIN32) && !defined(__wasm__) 55 | case EQ_KEY: 56 | #endif 57 | case PLUS_KEY: 58 | change_grid_size(ceilf(grid.rows * 1.5f), ceilf(grid.cols * 1.5f)); 59 | break; 60 | } 61 | #ifdef __ANDROID__ 62 | else 63 | { 64 | switch (keycode) 65 | { 66 | case 10: 67 | keyboard_up = 0; 68 | AndroidDisplayKeyboard(keyboard_up); 69 | break; 70 | case 4: 71 | AndroidSendToBack(1); 72 | break; 73 | } 74 | } 75 | #endif // __ANDROID__ 76 | } 77 | 78 | void EXPORT("HandleButton") HandleButton(int x, int y, int button, int bDown) 79 | { 80 | if (bDown) 81 | { 82 | #ifdef __ANDROID__ 83 | if ((w - 100 <= x && x <= w) && (0 <= y && y <= 100)) 84 | { 85 | keyboard_up = !keyboard_up; 86 | AndroidDisplayKeyboard(keyboard_up); 87 | return; 88 | } 89 | #endif // __ANDROID__ 90 | } 91 | controls.lmb_down = bDown && (button == LMB_KEY); 92 | controls.rmb_down = bDown && (button == RMB_KEY); 93 | controls.mouse_x = x; 94 | controls.mouse_y = y; 95 | } 96 | 97 | void EXPORT("HandleMotion") HandleMotion(int x, int y, int mask) 98 | { 99 | #ifndef __ANDROID__ 100 | if (!mask) 101 | return; 102 | #endif // __ANDROID__ 103 | controls.mouse_x = x; 104 | controls.mouse_y = y; 105 | 106 | #ifdef __ANDROID__ 107 | toggle_cell(ALIVE, x, y, SAND); 108 | #endif // __ANDROID__ 109 | } 110 | 111 | void HandleDestroy() {} 112 | 113 | void EXPORT("OnResize") OnResize(int new_width, int new_height) 114 | { 115 | if (!grid.cells) 116 | return; 117 | 118 | w = new_width; 119 | h = new_height; 120 | #ifdef __wasm__ 121 | CNFGSetup(WINDOW_NAME, w, h); 122 | #endif // __wasm__ 123 | cell_height = cell_width = min(w, h) / DEFAULT_CELL_SIZE; 124 | grid.rows = w / cell_width; 125 | grid.cols = h / cell_height; 126 | next_grid.rows = grid.rows; 127 | next_grid.cols = grid.cols; 128 | 129 | for (int y = 0; y < grid.cols; ++y) 130 | for (int x = 0; x < grid.rows; ++x) 131 | if (grid.cells[grid.cols * x + y].state != EMPTY) 132 | grid.cells[grid.cols * x + y].state = ALIVE; 133 | } 134 | 135 | #ifndef __wasm__ 136 | void HandleSuspend() 137 | { 138 | suspended = 1; 139 | } 140 | void HandleResume() 141 | { 142 | suspended = 0; 143 | } 144 | #else 145 | void EXPORT("touchend") touchend(void) 146 | { 147 | controls.lmb_down = 0; 148 | } 149 | #endif // __wasm__ 150 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011-2020 <>< Charles Lohr - Under the MIT/x11 or NewBSD 2 | // License you choose. 3 | // NO WARRANTY! NO GUARANTEE OF SUPPORT! USE AT YOUR OWN RISK 4 | 5 | #ifdef _MSC_VER 6 | #pragma comment(lib, "gdi32") 7 | #pragma comment(lib, "User32") 8 | #endif // _MSC_VER 9 | 10 | #ifndef __wasm__ 11 | #include "os_generic.h" 12 | #include 13 | #include 14 | #include 15 | #endif // __wasm__ 16 | 17 | #define STB_SPRINTF_IMPLEMENTATION 18 | #include "stb_sprintf.h" 19 | 20 | void OnResize(int new_width, int new_height); 21 | 22 | #define CNFG_IMPLEMENTATION 23 | #include "rawdraw_sf.h" 24 | 25 | #include "event.h" 26 | #include "anim.h" 27 | #include "grid.h" 28 | #include "color.h" 29 | #include "keycode.h" 30 | #include "typedef.h" 31 | #include "util.h" 32 | 33 | #define WINDOW_NAME "fisiks" 34 | #define MAX_MESSAGE_SIZE 256 35 | 36 | int paused = 0; 37 | int reset_t = 0; 38 | int update_cells_count = 1; 39 | 40 | short w, h; 41 | int cell_width, cell_height; 42 | double absolute_time; 43 | 44 | char message_buffer[MAX_MESSAGE_SIZE] = {0}; 45 | char cells_count_buffer[MAX_MESSAGE_SIZE] = {0}; 46 | 47 | String message = 48 | { 49 | .content = message_buffer, 50 | .length = 0, 51 | }; 52 | 53 | extern Animation message_a; 54 | extern Animation pause_a; 55 | extern Controls controls; 56 | extern Grid grid; 57 | extern Grid next_grid; 58 | 59 | volatile int suspended; 60 | 61 | #ifdef __ANDROID__ 62 | static int keyboard_up; 63 | int font_size = 20; 64 | int paused_t_width = 350; 65 | #else 66 | int font_size = 10; 67 | int paused_t_width = 200; 68 | #endif // __ANDROID__ 69 | 70 | void setup_window() 71 | { 72 | #ifdef __ANDROID__ 73 | CNFGSetupFullscreen(WINDOW_NAME, 0); 74 | CNFGGetDimensions(&w, &h); 75 | #elif defined(__wasm__) 76 | CNFGGetDimensions(&w, &h); 77 | CNFGSetup(WINDOW_NAME, w, h); 78 | #else 79 | w = 1600; 80 | h = 800; 81 | CNFGSetup(WINDOW_NAME, w, h); 82 | #endif // __ANDROID__ 83 | } 84 | 85 | int EXPORT("main") main() 86 | { 87 | CNFGBGColor = BLACK; 88 | setup_window(); 89 | 90 | cell_height = cell_width = min(w, h) / DEFAULT_CELL_SIZE; 91 | grid.rows = w / cell_width; 92 | grid.cols = h / cell_height; 93 | next_grid.rows = grid.rows; 94 | next_grid.cols = grid.cols; 95 | 96 | grid.cells = calloc(1, GRID_SIZE(grid)); 97 | next_grid.cells = calloc(1, GRID_SIZE(grid)); 98 | 99 | next_grid.cells[grid.cols * 14 + 2] = (Cell) 100 | { 101 | .state = ALIVE, .color = SAND 102 | }; 103 | next_grid.cells[grid.cols * 16 + 2] = (Cell) 104 | { 105 | .state = ALIVE, .color = SAND 106 | }; 107 | next_grid.cells[grid.cols * 15 + 3] = (Cell) 108 | { 109 | .state = ALIVE, .color = SAND 110 | }; 111 | next_grid.cells[grid.cols * 14 + 4] = (Cell) 112 | { 113 | .state = ALIVE, .color = SAND 114 | }; 115 | next_grid.cells[grid.cols * 16 + 4] = (Cell) 116 | { 117 | .state = ALIVE, .color = SAND 118 | }; 119 | next_grid.cells[grid.cols * 14 + 6] = (Cell) 120 | { 121 | .state = ALIVE, .color = SAND 122 | }; 123 | next_grid.cells[grid.cols * 16 + 6] = (Cell) 124 | { 125 | .state = ALIVE, .color = SAND 126 | }; 127 | next_grid.cells[grid.cols * 15 + 7] = (Cell) 128 | { 129 | .state = ALIVE, .color = SAND 130 | }; 131 | next_grid.cells[grid.cols * 15 + 8] = (Cell) 132 | { 133 | .state = ALIVE, .color = SAND 134 | }; 135 | 136 | display_message("fisiks"); 137 | 138 | #ifdef RAWDRAW_USE_LOOP_FUNCTION 139 | return 0; 140 | } 141 | int EXPORT("loop") loop() 142 | { 143 | #else 144 | while (1) 145 | #endif 146 | { 147 | CNFGClearFrame(); 148 | CNFGHandleInput(); 149 | 150 | #ifndef __wasm__ 151 | if (!paused) 152 | OGUSleep(5000); 153 | #endif // __wasm__ 154 | 155 | if (controls.lmb_down) 156 | toggle_cell(ALIVE, controls.mouse_x, controls.mouse_y, SAND); 157 | else if (controls.rmb_down) 158 | toggle_cell(EMPTY, controls.mouse_x, controls.mouse_y, 0); 159 | 160 | draw_cells(); 161 | 162 | absolute_time = OGGetAbsoluteTime(); 163 | 164 | draw_messages(); 165 | 166 | if (update_cells_count) 167 | { 168 | update_cells_count = 0; 169 | int cells_count = 0; 170 | for (int y = 0; y < grid.cols; ++y) 171 | for (int x = 0; x < grid.rows; ++x) 172 | if (grid.cells[grid.cols * x + y].state != EMPTY) 173 | cells_count++; 174 | stbsp_snprintf(cells_count_buffer, MAX_MESSAGE_SIZE, "%d", cells_count); 175 | } 176 | 177 | CNFGColor(WHITE); 178 | draw_text(cells_count_buffer, 179 | #ifdef __ANDROID__ 180 | 30, 181 | h - 110 182 | #else 183 | 10, 184 | h - 50 185 | #endif // __ANDROID__ 186 | , font_size); 187 | 188 | CNFGSwapBuffers(); 189 | } 190 | 191 | return (0); 192 | } 193 | -------------------------------------------------------------------------------- /platform/wasm/subst.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 zNoctum 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | * software and associated documentation files (the "Software"), to deal in the Software 6 | * without restriction, including without limitation the rights to use, copy, modify, merge, 7 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 8 | * to whom the Software is furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in all copies or 11 | * substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 14 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 15 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 16 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 17 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 18 | * DEALINGS IN THE SOFTWARE. 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | struct table_entry { 26 | size_t keysize; 27 | char *key; 28 | char *value; 29 | }; 30 | 31 | #define SWALLOW 1 32 | 33 | char *readfile(const char *filename, int flags) 34 | { 35 | size_t size; 36 | char *buffer; 37 | FILE *file = fopen(filename, "r"); 38 | 39 | if (file == NULL) { 40 | printf("Failed to open '%s'!\n", filename); 41 | exit(1); 42 | } 43 | 44 | fseek(file, 0, SEEK_END); 45 | size = ftell(file); 46 | rewind(file); 47 | 48 | buffer = malloc(size); 49 | 50 | fread(buffer, size, 1, file); 51 | 52 | buffer[size - 1] = 0x00; 53 | 54 | fclose(file); 55 | return buffer; 56 | } 57 | 58 | void print_help() 59 | { 60 | printf( 61 | "Usage: subst [TEMPLATE-FILE] [VARIABLES]\n" 62 | "\n" 63 | "Options:\n" 64 | " -s: Swallow following newline in files\n" 65 | " -o: Define output file\n" 66 | " -f: Define variable to replace '-f VARNAME FILEPATH'\n" 67 | " -d: Define variable to replace '-f VARNAME VARCONTENT'\n" 68 | " -h: Print this message\n" 69 | ); 70 | } 71 | 72 | 73 | char *outfile_name = NULL; 74 | char *outfile = NULL; 75 | size_t outfile_size; 76 | size_t outfile_current_size; 77 | int outfile_set = 0; 78 | void putchar_in_output(char c) 79 | { 80 | if (outfile_set) { 81 | if (outfile == NULL) { 82 | outfile = malloc(sizeof(char) * 1024); 83 | outfile_size = 1024; 84 | } 85 | if (outfile_current_size == outfile_size) { 86 | outfile_size *= 2; 87 | outfile = realloc(outfile, outfile_size); 88 | } 89 | outfile[outfile_current_size++] = c; 90 | } else { 91 | putchar(c); 92 | fflush(stdout); 93 | } 94 | } 95 | 96 | 97 | int main(int argc, char *argv[]) 98 | { 99 | int flags = 0; 100 | size_t i; 101 | size_t table_counter = 0; 102 | struct table_entry *table = malloc((argc - 2) / 3 * sizeof(struct table_entry)); 103 | char *infile; 104 | char *ident_begin; 105 | char *table_str; 106 | size_t ident_len; 107 | 108 | if (argc < 2) { 109 | printf("No input file was specified\n"); 110 | exit(1); 111 | } 112 | 113 | if (argv[1][0] == '-' && argv[1][1] == 'h') { 114 | print_help(); 115 | return 0; 116 | } 117 | 118 | 119 | infile = readfile(argv[1], 0); 120 | 121 | read_args: 122 | for (i = 2; i < argc; i++) { 123 | if (argv[i][0] == '-') { 124 | switch(argv[i][1]) { 125 | case 's': 126 | if ((flags & SWALLOW) == 0) { 127 | flags |= SWALLOW; 128 | goto read_args; 129 | } 130 | break; 131 | case 'o': 132 | outfile_name = argv[++i]; 133 | outfile_set = 1; 134 | break; 135 | case 'd': 136 | if (i++ == argc) { 137 | printf("supplied not enough arguments\n"); 138 | } 139 | table[table_counter].keysize = strlen(argv[i]); 140 | table[table_counter].key = argv[i]; 141 | i++; 142 | table[table_counter].value = argv[i]; 143 | table_counter++; 144 | break; 145 | case 'f': 146 | if (i++ == argc) { 147 | printf("supplied not enough arguments\n"); 148 | } 149 | table[table_counter].keysize = strlen(argv[i]); 150 | table[table_counter].key = argv[i]; 151 | i++; 152 | table[table_counter].value = readfile(argv[i], flags); 153 | table_counter++; 154 | break; 155 | case 'h': 156 | print_help(); 157 | return 0; 158 | } 159 | } 160 | 161 | } 162 | 163 | while (*infile) { 164 | if (!(*infile == '$' && infile[1] == '{')) { 165 | putchar_in_output(*infile); 166 | infile++; 167 | continue; 168 | } 169 | infile += 2; 170 | ident_begin = infile; 171 | ident_len = 0; 172 | while (*infile != '}') { 173 | if (*infile == 0x00) { 174 | printf("Unclosed variable refrence!\n"); 175 | exit(1); 176 | } 177 | infile++; 178 | ident_len++; 179 | } 180 | for (i = 0; i < table_counter; i++) { 181 | if (table[i].keysize != ident_len) 182 | continue; 183 | if (strncmp(table[i].key, ident_begin, ident_len) != 0) 184 | continue; 185 | table_str = table[i].value; 186 | while (*table_str) 187 | putchar_in_output(*table_str++); 188 | } 189 | 190 | infile++; 191 | } 192 | 193 | if (outfile_set) { 194 | FILE* file = fopen(outfile_name, "w"); 195 | if (file == NULL) { 196 | printf("Failed to open %s for output!\n", outfile_name); 197 | exit(1); 198 | } 199 | fwrite(outfile, outfile_current_size, 1, file); 200 | fclose(file); 201 | } 202 | return 0; 203 | } 204 | -------------------------------------------------------------------------------- /src/grid.c: -------------------------------------------------------------------------------- 1 | #include "grid.h" 2 | 3 | Grid grid = {0}; 4 | Grid next_grid = {0}; 5 | 6 | // TODO: null check 7 | void change_grid_size(int new_rows, int new_cols) 8 | { 9 | cell_height = cell_width = min(w, h) / min(new_rows, new_cols); 10 | if (cell_width <= 0) cell_width = 1; 11 | if (cell_height <= 0) cell_height = 1; 12 | grid.rows = w / cell_width; 13 | grid.cols = h / cell_height; 14 | next_grid.rows = grid.rows; 15 | next_grid.cols = grid.cols; 16 | grid.cells = realloc(grid.cells, GRID_SIZE(grid)); 17 | next_grid.cells = realloc(next_grid.cells, GRID_SIZE(grid)); 18 | memset(grid.cells, 0, GRID_SIZE(grid)); 19 | memset(next_grid.cells, 0, GRID_SIZE(grid)); 20 | message.length = stbsp_snprintf(message.content, MAX_MESSAGE_SIZE, "Grid Size: %dx%d", new_rows, new_cols); 21 | message_a.start = OGGetAbsoluteTime(); 22 | change_animation_state(&message_a, FADE_IN); 23 | memcpy(cells_count_buffer, "0", 2); 24 | } 25 | 26 | void draw_cell(Cell cell, int x, int y) 27 | { 28 | int cell_x = x * cell_width; 29 | int cell_y = y * cell_height; 30 | 31 | CNFGColor(cell.color); 32 | 33 | CNFGTackRectangle(cell_x, cell_y, cell_x + cell_width, 34 | cell_y + cell_height); 35 | } 36 | 37 | void draw_cells() 38 | { 39 | for (int y = 0; y < grid.cols; ++y) 40 | { 41 | for (int x = 0; x < grid.rows; ++x) 42 | { 43 | if (!paused) 44 | apply_game_rules(x, y); 45 | switch (grid.cells[grid.cols * x + y].state) 46 | { 47 | case ALIVE: 48 | case STATIC: 49 | draw_cell(grid.cells[grid.cols * x + y], x, y); 50 | default: 51 | break; 52 | } 53 | } 54 | } 55 | memcpy(grid.cells, next_grid.cells, GRID_SIZE(grid)); 56 | } 57 | 58 | void cell_index(int x, int y, int *cell_x, int *cell_y) 59 | { 60 | *cell_x = x / (w / grid.rows); 61 | *cell_y = y / (h / grid.cols); 62 | } 63 | 64 | int row_exists(int cell_y) 65 | { 66 | return cell_y >= 0 && cell_y < grid.rows; 67 | } 68 | 69 | int col_exists(int cell_x) 70 | { 71 | return cell_x >= 0 && cell_x < grid.cols; 72 | } 73 | 74 | void toggle_cell(CellState state, int x, int y, uint32_t color) 75 | { 76 | int cell_x, cell_y; 77 | cell_index(x, y, &cell_x, &cell_y); 78 | if (state == EMPTY) 79 | { 80 | if (grid.cells[grid.cols * cell_x + cell_y].state == EMPTY) 81 | return; 82 | set_adjacent_cells_state_if_not_empty(&next_grid, ALIVE, cell_x, cell_y, ALL); 83 | } 84 | else 85 | { 86 | if (grid.cells[grid.cols * cell_x + cell_y].state != EMPTY) 87 | return; 88 | } 89 | next_grid.cells[grid.cols * cell_x + cell_y] = (Cell) 90 | { 91 | .state = state, .color = color 92 | }; 93 | update_cells_count = 1; 94 | } 95 | 96 | void apply_game_rules(int x, int y) 97 | { 98 | if (grid.cells[grid.cols * x + y].state == ALIVE) 99 | { 100 | if (col_exists(y + 1)) 101 | { 102 | // sand 103 | if (grid.cells[grid.cols * x + y + 1].state == EMPTY) 104 | { 105 | next_grid.cells[grid.cols * x + y].state = EMPTY; 106 | next_grid.cells[grid.cols * x + y + 1] = (Cell) 107 | { 108 | .state = grid.cells[grid.cols * x + y].state, 109 | .color = SAND, 110 | }; 111 | set_adjacent_cells_state_if_not_empty(&next_grid, ALIVE, x, y, TOP | LEFT | RIGHT); 112 | } 113 | else 114 | { 115 | // bottom left cell is free 116 | if (row_exists(x - 1) && grid.cells[grid.cols * (x - 1) + y + 1].state == EMPTY) 117 | { 118 | next_grid.cells[grid.cols * x + y].state = EMPTY; 119 | next_grid.cells[grid.cols * (x - 1) + y + 1] = (Cell) 120 | { 121 | .state = ALIVE, 122 | .color = SAND, 123 | }; 124 | set_adjacent_cells_state_if_not_empty(&next_grid, ALIVE, x, y, ALL); 125 | } 126 | // bottom right cell is free 127 | else if (row_exists(x + 1) && grid.cells[grid.cols * (x + 1) + y + 1].state == EMPTY) 128 | { 129 | next_grid.cells[grid.cols * x + y].state = EMPTY; 130 | next_grid.cells[grid.cols * (x + 1) + y + 1] = (Cell) 131 | { 132 | .state = ALIVE, 133 | .color = SAND, 134 | }; 135 | set_adjacent_cells_state_if_not_empty(&next_grid, ALIVE, x, y, ALL); 136 | } 137 | else 138 | { 139 | next_grid.cells[grid.cols * x + y] = (Cell) 140 | { 141 | .state = STATIC, 142 | .color = STATIC_SAND, 143 | }; 144 | set_adjacent_cells_state_if_not_empty(&next_grid, ALIVE, x, y, ALL); 145 | } 146 | } 147 | } 148 | else 149 | { 150 | next_grid.cells[grid.cols * x + y] = (Cell) 151 | { 152 | .state = STATIC, 153 | .color = STATIC_SAND, 154 | }; 155 | set_adjacent_cells_state_if_not_empty(&next_grid, ALIVE, x, y, ALL); 156 | } 157 | } 158 | } 159 | 160 | void set_cell_state_at_if_not_empty(Grid *grid, uint64_t idx, CellState state) 161 | { 162 | Cell *cell = &grid->cells[idx]; 163 | if (cell->state != EMPTY) cell->state = state; 164 | } 165 | 166 | void set_adjacent_cells_state_if_not_empty(Grid* grid, CellState state, int x, int y, uint8_t flags) 167 | { 168 | if (flags & TOP) 169 | set_cell_state_at_if_not_empty(grid, grid->cols * x + y - 1, state); 170 | if (flags & RIGHT) 171 | set_cell_state_at_if_not_empty(grid, grid->cols * (x + 1) + y, state); 172 | if (flags & BOTTOM) 173 | set_cell_state_at_if_not_empty(grid, grid->cols * x + y + 1, state); 174 | if (flags & LEFT) 175 | set_cell_state_at_if_not_empty(grid, grid->cols * (x - 1) + y, state); 176 | } 177 | -------------------------------------------------------------------------------- /platform/android/Makefile: -------------------------------------------------------------------------------- 1 | #Copyright (c) 2019-2020 <>< Charles Lohr - Under the MIT/x11 or NewBSD License you choose. 2 | # NO WARRANTY! NO GUARANTEE OF SUPPORT! USE AT YOUR OWN RISK 3 | 4 | all : makecapk.apk 5 | 6 | SRC?=../../src/fisiks.c 7 | 8 | linux : 9 | $(CC) -I../../src/include $(SRC) -lX11 -o fisiks 10 | 11 | .PHONY : push run 12 | 13 | # WARNING WARNING WARNING! YOU ABSOLUTELY MUST OVERRIDE THE PROJECT NAME 14 | # you should also override these parameters, get your own signatre file and make your own manifest. 15 | APPNAME?=fisiks 16 | LABEL?=$(APPNAME) 17 | APKFILE ?= $(APPNAME).apk 18 | PACKAGENAME?=org.ciremun.$(APPNAME) 19 | RAWDRAWANDROID?=. 20 | RAWDRAWANDROIDSRCS=$(RAWDRAWANDROID)/android_native_app_glue.c 21 | 22 | #We've tested it with android version 22, 24, 28, 29 and 30. 23 | #You can target something like Android 28, but if you set ANDROIDVERSION to say 22, then 24 | #Your app should (though not necessarily) support all the way back to Android 22. 25 | ANDROIDVERSION?=29 26 | ANDROIDTARGET?=$(ANDROIDVERSION) 27 | #Default is to be strip down, but your app can override it. 28 | CFLAGS?=-I../../src/include -ffunction-sections -Os -fdata-sections -Wall -fvisibility=hidden 29 | LDFLAGS?=-Wl,--gc-sections -s 30 | ANDROID_FULLSCREEN?=y 31 | ADB?=adb 32 | UNAME := $(shell uname) 33 | 34 | 35 | 36 | 37 | 38 | ANDROIDSRCS:= $(SRC) $(RAWDRAWANDROIDSRCS) 39 | 40 | #if you have a custom Android Home location you can add it to this list. 41 | #This makefile will select the first present folder. 42 | 43 | 44 | ifeq ($(UNAME), Linux) 45 | OS_NAME = linux-x86_64 46 | endif 47 | ifeq ($(UNAME), Darwin) 48 | OS_NAME = darwin-x86_64 49 | endif 50 | ifeq ($(OS), Windows_NT) 51 | OS_NAME = windows-x86_64 52 | endif 53 | 54 | # Search list for where to try to find the SDK 55 | SDK_LOCATIONS += $(ANDROID_HOME) $(ANDROID_SDK_ROOT) ~/Android/Sdk $(HOME)/Library/Android/sdk 56 | 57 | #Just a little Makefile witchcraft to find the first SDK_LOCATION that exists 58 | #Then find an ndk folder and build tools folder in there. 59 | ANDROIDSDK?=$(firstword $(foreach dir, $(SDK_LOCATIONS), $(basename $(dir) ) ) ) 60 | NDK?=$(firstword $(ANDROID_NDK) $(ANDROID_NDK_HOME) $(wildcard $(ANDROIDSDK)/ndk/*) $(wildcard $(ANDROIDSDK)/ndk-bundle/*) ) 61 | BUILD_TOOLS?=$(lastword $(wildcard $(ANDROIDSDK)/build-tools/*) ) 62 | 63 | # fall back to default Android SDL installation location if valid NDK was not found 64 | ifeq ($(NDK),) 65 | ANDROIDSDK := ~/Android/Sdk 66 | endif 67 | 68 | # Verify if directories are detected 69 | ifeq ($(ANDROIDSDK),) 70 | $(error ANDROIDSDK directory not found) 71 | endif 72 | ifeq ($(NDK),) 73 | $(error NDK directory not found) 74 | endif 75 | ifeq ($(BUILD_TOOLS),) 76 | $(error BUILD_TOOLS directory not found) 77 | endif 78 | 79 | testsdk : 80 | @echo "SDK:\t\t" $(ANDROIDSDK) 81 | @echo "NDK:\t\t" $(NDK) 82 | @echo "Build Tools:\t" $(BUILD_TOOLS) 83 | 84 | CFLAGS+=-Os -DANDROID -DAPPNAME=\"$(APPNAME)\" 85 | ifeq (ANDROID_FULLSCREEN,y) 86 | CFLAGS +=-DANDROID_FULLSCREEN 87 | endif 88 | CFLAGS+= -I$(RAWDRAWANDROID)/rawdraw -I$(NDK)/sysroot/usr/include -I$(NDK)/sysroot/usr/include/android -I$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/sysroot/usr/include/android -fPIC -I$(RAWDRAWANDROID) -DANDROIDVERSION=$(ANDROIDVERSION) 89 | LDFLAGS += -lm -lGLESv3 -lEGL -landroid -llog 90 | LDFLAGS += -shared -uANativeActivity_onCreate 91 | 92 | CC_ARM64:=$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/bin/aarch64-linux-android$(ANDROIDVERSION)-clang 93 | CC_ARM32:=$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/bin/armv7a-linux-androideabi$(ANDROIDVERSION)-clang 94 | CC_x86:=$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/bin/i686-linux-android$(ANDROIDVERSION)-clang 95 | CC_x86_64=$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/bin/x86_64-linux-android$(ANDROIDVERSION)-clang 96 | AAPT:=$(BUILD_TOOLS)/aapt 97 | 98 | # Which binaries to build? Just comment/uncomment these lines: 99 | TARGETS += makecapk/lib/arm64-v8a/lib$(APPNAME).so 100 | TARGETS += makecapk/lib/armeabi-v7a/lib$(APPNAME).so 101 | #TARGETS += makecapk/lib/x86/lib$(APPNAME).so 102 | #TARGETS += makecapk/lib/x86_64/lib$(APPNAME).so 103 | 104 | CFLAGS_ARM64:=-m64 105 | CFLAGS_ARM32:=-mfloat-abi=softfp -m32 106 | CFLAGS_x86:=-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32 107 | CFLAGS_x86_64:=-march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel 108 | STOREPASS?=password 109 | DNAME:="CN=example.com, OU=ID, O=Example, L=Doe, S=John, C=GB" 110 | KEYSTOREFILE:=my-release-key.keystore 111 | ALIASNAME?=standkey 112 | 113 | keystore : $(KEYSTOREFILE) 114 | 115 | $(KEYSTOREFILE) : 116 | keytool -genkey -v -keystore $(KEYSTOREFILE) -alias $(ALIASNAME) -keyalg RSA -keysize 2048 -validity 10000 -storepass $(STOREPASS) -keypass $(STOREPASS) -dname $(DNAME) 117 | 118 | folders: 119 | mkdir -p makecapk/lib/arm64-v8a 120 | mkdir -p makecapk/lib/armeabi-v7a 121 | mkdir -p makecapk/lib/x86 122 | mkdir -p makecapk/lib/x86_64 123 | 124 | makecapk/lib/arm64-v8a/lib$(APPNAME).so : $(ANDROIDSRCS) 125 | mkdir -p makecapk/lib/arm64-v8a 126 | $(CC_ARM64) $(CFLAGS) $(CFLAGS_ARM64) -o $@ $^ -L$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/sysroot/usr/lib/aarch64-linux-android/$(ANDROIDVERSION) $(LDFLAGS) 127 | 128 | makecapk/lib/armeabi-v7a/lib$(APPNAME).so : $(ANDROIDSRCS) 129 | mkdir -p makecapk/lib/armeabi-v7a 130 | $(CC_ARM32) $(CFLAGS) $(CFLAGS_ARM32) -o $@ $^ -L$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/sysroot/usr/lib/arm-linux-androideabi/$(ANDROIDVERSION) $(LDFLAGS) 131 | 132 | makecapk/lib/x86/lib$(APPNAME).so : $(ANDROIDSRCS) 133 | mkdir -p makecapk/lib/x86 134 | $(CC_x86) $(CFLAGS) $(CFLAGS_x86) -o $@ $^ -L$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/sysroot/usr/lib/i686-linux-android/$(ANDROIDVERSION) $(LDFLAGS) 135 | 136 | makecapk/lib/x86_64/lib$(APPNAME).so : $(ANDROIDSRCS) 137 | mkdir -p makecapk/lib/x86_64 138 | $(CC_x86) $(CFLAGS) $(CFLAGS_x86_64) -o $@ $^ -L$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/sysroot/usr/lib/x86_64-linux-android/$(ANDROIDVERSION) $(LDFLAGS) 139 | 140 | #We're really cutting corners. You should probably use resource files.. Replace android:label="@string/app_name" and add a resource file. 141 | #Then do this -S Sources/res on the aapt line. 142 | #For icon support, add -S makecapk/res to the aapt line. also, android:icon="@mipmap/icon" to your application line in the manifest. 143 | #If you want to strip out about 800 bytes of data you can remove the icon and strings. 144 | 145 | #Notes for the past: These lines used to work, but don't seem to anymore. Switched to newer jarsigner. 146 | #(zipalign -c -v 8 makecapk.apk)||true #This seems to not work well. 147 | #jarsigner -verify -verbose -certs makecapk.apk 148 | 149 | 150 | 151 | makecapk.apk : $(TARGETS) $(EXTRA_ASSETS_TRIGGER) AndroidManifest.xml 152 | mkdir -p makecapk/assets 153 | cp -r Sources/assets/* makecapk/assets 154 | rm -rf temp.apk 155 | $(AAPT) package -f -F temp.apk -I $(ANDROIDSDK)/platforms/android-$(ANDROIDVERSION)/android.jar -M AndroidManifest.xml -S Sources/res -A makecapk/assets -v --target-sdk-version $(ANDROIDTARGET) 156 | unzip -o temp.apk -d makecapk 157 | rm -rf makecapk.apk 158 | cd makecapk && zip -D9r ../makecapk.apk . && zip -D0r ../makecapk.apk ./resources.arsc ./AndroidManifest.xml 159 | jarsigner -sigalg SHA1withRSA -digestalg SHA1 -verbose -keystore $(KEYSTOREFILE) -storepass $(STOREPASS) makecapk.apk $(ALIASNAME) 160 | rm -rf $(APKFILE) 161 | $(BUILD_TOOLS)/zipalign -v 4 makecapk.apk $(APKFILE) 162 | #Using the apksigner in this way is only required on Android 30+ 163 | $(BUILD_TOOLS)/apksigner sign --key-pass pass:$(STOREPASS) --ks-pass pass:$(STOREPASS) --ks $(KEYSTOREFILE) $(APKFILE) 164 | rm -rf temp.apk 165 | rm -rf makecapk.apk 166 | @ls -l $(APKFILE) 167 | 168 | manifest: AndroidManifest.xml 169 | 170 | AndroidManifest.xml : 171 | rm -rf AndroidManifest.xml 172 | PACKAGENAME=$(PACKAGENAME) \ 173 | ANDROIDVERSION=$(ANDROIDVERSION) \ 174 | ANDROIDTARGET=$(ANDROIDTARGET) \ 175 | APPNAME=$(APPNAME) \ 176 | LABEL=$(LABEL) envsubst '$$ANDROIDTARGET $$ANDROIDVERSION $$APPNAME $$PACKAGENAME $$LABEL' \ 177 | < AndroidManifest.xml.template > AndroidManifest.xml 178 | 179 | 180 | uninstall : 181 | ($(ADB) uninstall $(PACKAGENAME))||true 182 | 183 | push : makecapk.apk 184 | @echo "Installing" $(PACKAGENAME) 185 | $(ADB) install -r $(APKFILE) 186 | 187 | run : push 188 | $(eval ACTIVITYNAME:=$(shell $(AAPT) dump badging $(APKFILE) | grep "launchable-activity" | cut -f 2 -d"'")) 189 | $(ADB) shell am start -n $(PACKAGENAME)/$(ACTIVITYNAME) 190 | 191 | clean : 192 | rm -rf temp.apk makecapk.apk makecapk $(APKFILE) 193 | 194 | -------------------------------------------------------------------------------- /platform/android/android_usb_devices.c: -------------------------------------------------------------------------------- 1 | //Copyright 2020 <>< Charles Lohr, You may use this file and library freely under the MIT/x11, NewBSD or ColorChord Licenses. 2 | 3 | #include "android_usb_devices.h" 4 | #include "rawdraw_sf.h" 5 | #include "os_generic.h" 6 | 7 | double dTimeOfUSBFail; 8 | double dTimeOfLastAsk; 9 | jobject deviceConnection = 0; 10 | int deviceConnectionFD = 0; 11 | extern struct android_app * gapp; 12 | 13 | void DisconnectUSB() 14 | { 15 | deviceConnectionFD = 0; 16 | dTimeOfUSBFail = OGGetAbsoluteTime(); 17 | } 18 | 19 | int RequestPermissionOrGetConnectionFD( char * ats, uint16_t vid, uint16_t pid ) 20 | { 21 | //Don't permit 22 | if( OGGetAbsoluteTime() - dTimeOfUSBFail < 1 ) 23 | { 24 | ats+=sprintf(ats, "Comms failed. Waiting to reconnect." ); 25 | return -1; 26 | } 27 | 28 | struct android_app* app = gapp; 29 | const struct JNINativeInterface * env = 0; 30 | const struct JNINativeInterface ** envptr = &env; 31 | const struct JNIInvokeInterface ** jniiptr = app->activity->vm; 32 | const struct JNIInvokeInterface * jnii = *jniiptr; 33 | jnii->AttachCurrentThread( jniiptr, &envptr, NULL); 34 | env = (*envptr); 35 | 36 | // Retrieves NativeActivity. 37 | jobject lNativeActivity = gapp->activity->clazz; 38 | 39 | //https://stackoverflow.com/questions/13280581/using-android-to-communicate-with-a-usb-hid-device 40 | 41 | //UsbManager manager = (UsbManager)getSystemService(Context.USB_SERVICE); 42 | jclass ClassContext = env->FindClass( envptr, "android/content/Context" ); 43 | jfieldID lid_USB_SERVICE = env->GetStaticFieldID( envptr, ClassContext, "USB_SERVICE", "Ljava/lang/String;" ); 44 | jobject USB_SERVICE = env->GetStaticObjectField( envptr, ClassContext, lid_USB_SERVICE ); 45 | 46 | jmethodID MethodgetSystemService = env->GetMethodID( envptr, ClassContext, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;" ); 47 | jobject manager = env->CallObjectMethod( envptr, lNativeActivity, MethodgetSystemService, USB_SERVICE); 48 | //Actually returns an android/hardware/usb/UsbManager 49 | jclass ClassUsbManager = env->FindClass( envptr, "android/hardware/usb/UsbManager" ); 50 | 51 | //HashMap deviceList = mManager.getDeviceList(); 52 | jmethodID MethodgetDeviceList = env->GetMethodID( envptr, ClassUsbManager, "getDeviceList", "()Ljava/util/HashMap;" ); 53 | jobject deviceList = env->CallObjectMethod( envptr, manager, MethodgetDeviceList ); 54 | 55 | //Iterator deviceIterator = deviceList.values().iterator(); 56 | jclass ClassHashMap = env->FindClass( envptr, "java/util/HashMap" ); 57 | jmethodID Methodvalues = env->GetMethodID( envptr, ClassHashMap, "values", "()Ljava/util/Collection;" ); 58 | jobject deviceListCollection = env->CallObjectMethod( envptr, deviceList, Methodvalues ); 59 | jclass ClassCollection = env->FindClass( envptr, "java/util/Collection" ); 60 | jmethodID Methoditerator = env->GetMethodID( envptr, ClassCollection, "iterator", "()Ljava/util/Iterator;" ); 61 | jobject deviceListIterator = env->CallObjectMethod( envptr, deviceListCollection, Methoditerator ); 62 | jclass ClassIterator = env->FindClass( envptr, "java/util/Iterator" ); 63 | 64 | //while (deviceIterator.hasNext()) 65 | jmethodID MethodhasNext = env->GetMethodID( envptr, ClassIterator, "hasNext", "()Z" ); 66 | jboolean bHasNext = env->CallBooleanMethod( envptr, deviceListIterator, MethodhasNext ); 67 | 68 | ats+=sprintf(ats, "Has Devices: %d\n", bHasNext ); 69 | 70 | jmethodID Methodnext = env->GetMethodID( envptr, ClassIterator, "next", "()Ljava/lang/Object;" ); 71 | 72 | jclass ClassUsbDevice = env->FindClass( envptr, "android/hardware/usb/UsbDevice" ); 73 | jclass ClassUsbInterface = env->FindClass( envptr, "android/hardware/usb/UsbInterface" ); 74 | jclass ClassUsbEndpoint = env->FindClass( envptr, "android/hardware/usb/UsbEndpoint" ); 75 | jclass ClassUsbDeviceConnection = env->FindClass( envptr, "android/hardware/usb/UsbDeviceConnection" ); 76 | jmethodID MethodgetDeviceName = env->GetMethodID( envptr, ClassUsbDevice, "getDeviceName", "()Ljava/lang/String;" ); 77 | jmethodID MethodgetVendorId = env->GetMethodID( envptr, ClassUsbDevice, "getVendorId", "()I" ); 78 | jmethodID MethodgetProductId = env->GetMethodID( envptr, ClassUsbDevice, "getProductId", "()I" ); 79 | jmethodID MethodgetInterfaceCount = env->GetMethodID( envptr, ClassUsbDevice, "getInterfaceCount", "()I" ); 80 | jmethodID MethodgetInterface = env->GetMethodID( envptr, ClassUsbDevice, "getInterface", "(I)Landroid/hardware/usb/UsbInterface;" ); 81 | 82 | jmethodID MethodgetEndpointCount = env->GetMethodID( envptr, ClassUsbInterface, "getEndpointCount", "()I" ); 83 | jmethodID MethodgetEndpoint = env->GetMethodID( envptr, ClassUsbInterface, "getEndpoint", "(I)Landroid/hardware/usb/UsbEndpoint;" ); 84 | 85 | jmethodID MethodgetAddress = env->GetMethodID( envptr, ClassUsbEndpoint, "getAddress", "()I" ); 86 | jmethodID MethodgetMaxPacketSize = env->GetMethodID( envptr, ClassUsbEndpoint, "getMaxPacketSize", "()I" ); 87 | 88 | jobject matchingDevice = 0; 89 | jobject matchingInterface = 0; 90 | 91 | while( bHasNext ) 92 | { 93 | // UsbDevice device = deviceIterator.next(); 94 | // Log.i(TAG,"Model: " + device.getDeviceName()); 95 | jobject device = env->CallObjectMethod( envptr, deviceListIterator, Methodnext ); 96 | uint16_t vendorId = env->CallIntMethod( envptr, device, MethodgetVendorId ); 97 | uint16_t productId = env->CallIntMethod( envptr, device, MethodgetProductId ); 98 | int ifaceCount = env->CallIntMethod( envptr, device, MethodgetInterfaceCount ); 99 | const char *strdevname = env->GetStringUTFChars(envptr, env->CallObjectMethod( envptr, device, MethodgetDeviceName ), 0); 100 | ats+=sprintf(ats, "%s,%04x:%04x(%d)\n", strdevname, 101 | vendorId, 102 | productId, ifaceCount ); 103 | 104 | if( vendorId == vid && productId == pid ) 105 | { 106 | if( ifaceCount ) 107 | { 108 | matchingDevice = device; 109 | matchingInterface = env->CallObjectMethod( envptr, device, MethodgetInterface, 0 ); 110 | } 111 | } 112 | 113 | bHasNext = env->CallBooleanMethod( envptr, deviceListIterator, MethodhasNext ); 114 | } 115 | 116 | jobject matchingEp = 0; 117 | 118 | if( matchingInterface ) 119 | { 120 | //matchingInterface is of type android/hardware/usb/UsbInterface 121 | int epCount = env->CallIntMethod( envptr, matchingInterface, MethodgetEndpointCount ); 122 | ats+=sprintf(ats, "Found device %d eps\n", epCount ); 123 | int i; 124 | for( i = 0; i < epCount; i++ ) 125 | { 126 | jobject endpoint = env->CallObjectMethod( envptr, matchingInterface, MethodgetEndpoint, i ); 127 | jint epnum = env->CallIntMethod( envptr, endpoint, MethodgetAddress ); 128 | jint mps = env->CallIntMethod( envptr, endpoint, MethodgetMaxPacketSize ); 129 | if( epnum == 0x02 ) matchingEp = endpoint; 130 | ats+=sprintf(ats, "%p: %02x: MPS: %d (%c)\n", endpoint, epnum, mps, (matchingEp == endpoint)?'*':' ' ); 131 | } 132 | } 133 | 134 | jmethodID MethodopenDevice = env->GetMethodID( envptr, ClassUsbManager, "openDevice", "(Landroid/hardware/usb/UsbDevice;)Landroid/hardware/usb/UsbDeviceConnection;" ); 135 | jmethodID MethodrequestPermission = env->GetMethodID( envptr, ClassUsbManager, "requestPermission", "(Landroid/hardware/usb/UsbDevice;Landroid/app/PendingIntent;)V" ); 136 | jmethodID MethodhasPermission = env->GetMethodID( envptr, ClassUsbManager, "hasPermission", "(Landroid/hardware/usb/UsbDevice;)Z" ); 137 | jmethodID MethodclaimInterface = env->GetMethodID( envptr, ClassUsbDeviceConnection, "claimInterface", "(Landroid/hardware/usb/UsbInterface;Z)Z" ); 138 | jmethodID MethodsetInterface = env->GetMethodID( envptr, ClassUsbDeviceConnection, "setInterface", "(Landroid/hardware/usb/UsbInterface;)Z" ); 139 | jmethodID MethodgetFileDescriptor = env->GetMethodID( envptr, ClassUsbDeviceConnection, "getFileDescriptor", "()I" ); 140 | //jmethodID MethodbulkTransfer = env->GetMethodID( envptr, ClassUsbDeviceConnection, "bulkTransfer", "(Landroid/hardware/usb/UsbEndpoint;[BII)I" ); 141 | 142 | //see https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/hardware/usb/UsbDeviceConnection.java 143 | //Calls: native_bulk_request -> android_hardware_UsbDeviceConnection_bulk_request -> usb_device_bulk_transfer 144 | // UsbEndpoint endpoint, byte[] buffer, int length, int timeout 145 | //bulkTransfer(UsbEndpoint endpoint, byte[] buffer, int length, int timeout) 146 | 147 | //UsbDeviceConnection bulkTransfer 148 | 149 | if( matchingEp && matchingDevice ) 150 | { 151 | //UsbDeviceConnection deviceConnection = manager.openDevice( device ) 152 | deviceConnection = env->CallObjectMethod( envptr, manager, MethodopenDevice, matchingDevice ); 153 | jint epnum = env->CallIntMethod( envptr, matchingEp, MethodgetAddress ); 154 | 155 | if( !deviceConnection ) 156 | { 157 | // hasPermission(UsbDevice device) 158 | 159 | if( OGGetAbsoluteTime() - dTimeOfLastAsk < 5 ) 160 | { 161 | ats+=sprintf(ats, "Asked for permission. Waiting to ask again." ); 162 | } 163 | else if( env->CallBooleanMethod( envptr, manager, MethodhasPermission, matchingDevice ) ) 164 | { 165 | ats+=sprintf(ats, "Has permission - disconnected?" ); 166 | } 167 | else 168 | { 169 | //android.app.PendingIntent currently setting to 0 (null) seems not to cause crashes, but does force lock screen to happen. 170 | //Because the screen locks we need to do a much more complicated operation, generating a PendingIntent. See Below. 171 | // env->CallVoidMethod( envptr, manager, MethodrequestPermission, matchingDevice, 0 ); 172 | 173 | //This part mimiced off of: 174 | //https://www.programcreek.com/java-api-examples/?class=android.hardware.usb.UsbManager&method=requestPermission 175 | // manager.requestPermission(device, PendingIntent.getBroadcast(context, 0, new Intent(MainActivity.ACTION_USB_PERMISSION), 0)); 176 | jclass ClassPendingIntent = env->FindClass( envptr, "android/app/PendingIntent" ); 177 | jclass ClassIntent = env->FindClass(envptr, "android/content/Intent"); 178 | jmethodID newIntent = env->GetMethodID(envptr, ClassIntent, "", "(Ljava/lang/String;)V"); 179 | jstring ACTION_USB_PERMISSION = env->NewStringUTF( envptr, "com.android.recipes.USB_PERMISSION" ); 180 | jobject intentObject = env->NewObject(envptr, ClassIntent, newIntent, ACTION_USB_PERMISSION); 181 | 182 | jmethodID MethodgetBroadcast = env->GetStaticMethodID( envptr, ClassPendingIntent, "getBroadcast", 183 | "(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;" ); 184 | jobject pi = env->CallStaticObjectMethod( envptr, ClassPendingIntent, MethodgetBroadcast, lNativeActivity, 0, intentObject, 0 ); 185 | 186 | //This actually requests permission. 187 | env->CallVoidMethod( envptr, manager, MethodrequestPermission, matchingDevice, pi ); 188 | dTimeOfLastAsk = OGGetAbsoluteTime(); 189 | } 190 | } 191 | else 192 | { 193 | //Because we want to read and write to an interrupt endpoint, we need to claim the interface - it seems setting interfaces is insufficient here. 194 | jboolean claimOk = env->CallBooleanMethod( envptr, deviceConnection, MethodclaimInterface, matchingInterface, 1 ); 195 | //jboolean claimOk = env->CallBooleanMethod( envptr, deviceConnection, MethodsetInterface, matchingInterface ); 196 | //jboolean claimOk = 1; 197 | if( claimOk ) 198 | { 199 | deviceConnectionFD = env->CallIntMethod( envptr, deviceConnection, MethodgetFileDescriptor ); 200 | } 201 | 202 | ats+=sprintf(ats, "DC: %p; Claim: %d; FD: %d\n", deviceConnection, claimOk, deviceConnectionFD ); 203 | } 204 | 205 | } 206 | 207 | jnii->DetachCurrentThread( jniiptr ); 208 | return (!deviceConnectionFD)?-5:0; 209 | } 210 | 211 | -------------------------------------------------------------------------------- /platform/android/android_native_app_glue.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | #ifndef _ANDROID_NATIVE_APP_GLUE_H 19 | #define _ANDROID_NATIVE_APP_GLUE_H 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | /** 34 | * The native activity interface provided by 35 | * is based on a set of application-provided callbacks that will be called 36 | * by the Activity's main thread when certain events occur. 37 | * 38 | * This means that each one of this callbacks _should_ _not_ block, or they 39 | * risk having the system force-close the application. This programming 40 | * model is direct, lightweight, but constraining. 41 | * 42 | * The 'android_native_app_glue' static library is used to provide a different 43 | * execution model where the application can implement its own main event 44 | * loop in a different thread instead. Here's how it works: 45 | * 46 | * 1/ The application must provide a function named "android_main()" that 47 | * will be called when the activity is created, in a new thread that is 48 | * distinct from the activity's main thread. 49 | * 50 | * 2/ android_main() receives a pointer to a valid "android_app" structure 51 | * that contains references to other important objects, e.g. the 52 | * ANativeActivity object instance the application is running in. 53 | * 54 | * 3/ the "android_app" object holds an ALooper instance that already 55 | * listens to two important things: 56 | * 57 | * - activity lifecycle events (e.g. "pause", "resume"). See APP_CMD_XXX 58 | * declarations below. 59 | * 60 | * - input events coming from the AInputQueue attached to the activity. 61 | * 62 | * Each of these correspond to an ALooper identifier returned by 63 | * ALooper_pollOnce with values of LOOPER_ID_MAIN and LOOPER_ID_INPUT, 64 | * respectively. 65 | * 66 | * Your application can use the same ALooper to listen to additional 67 | * file-descriptors. They can either be callback based, or with return 68 | * identifiers starting with LOOPER_ID_USER. 69 | * 70 | * 4/ Whenever you receive a LOOPER_ID_MAIN or LOOPER_ID_INPUT event, 71 | * the returned data will point to an android_poll_source structure. You 72 | * can call the process() function on it, and fill in android_app->onAppCmd 73 | * and android_app->onInputEvent to be called for your own processing 74 | * of the event. 75 | * 76 | * Alternatively, you can call the low-level functions to read and process 77 | * the data directly... look at the process_cmd() and process_input() 78 | * implementations in the glue to see how to do this. 79 | * 80 | * See the sample named "native-activity" that comes with the NDK with a 81 | * full usage example. Also look at the JavaDoc of NativeActivity. 82 | */ 83 | 84 | struct android_app; 85 | 86 | /** 87 | * Data associated with an ALooper fd that will be returned as the "outData" 88 | * when that source has data ready. 89 | */ 90 | struct android_poll_source { 91 | // The identifier of this source. May be LOOPER_ID_MAIN or 92 | // LOOPER_ID_INPUT. 93 | int32_t id; 94 | 95 | // The android_app this ident is associated with. 96 | struct android_app* app; 97 | 98 | // Function to call to perform the standard processing of data from 99 | // this source. 100 | void (*process)(struct android_app* app, struct android_poll_source* source); 101 | }; 102 | 103 | /** 104 | * This is the interface for the standard glue code of a threaded 105 | * application. In this model, the application's code is running 106 | * in its own thread separate from the main thread of the process. 107 | * It is not required that this thread be associated with the Java 108 | * VM, although it will need to be in order to make JNI calls any 109 | * Java objects. 110 | */ 111 | struct android_app { 112 | // The application can place a pointer to its own state object 113 | // here if it likes. 114 | void* userData; 115 | 116 | // Fill this in with the function to process main app commands (APP_CMD_*) 117 | void (*onAppCmd)(struct android_app* app, int32_t cmd); 118 | 119 | // Fill this in with the function to process input events. At this point 120 | // the event has already been pre-dispatched, and it will be finished upon 121 | // return. Return 1 if you have handled the event, 0 for any default 122 | // dispatching. 123 | int32_t (*onInputEvent)(struct android_app* app, AInputEvent* event); 124 | 125 | // The ANativeActivity object instance that this app is running in. 126 | ANativeActivity* activity; 127 | 128 | // The current configuration the app is running in. 129 | AConfiguration* config; 130 | 131 | // This is the last instance's saved state, as provided at creation time. 132 | // It is NULL if there was no state. You can use this as you need; the 133 | // memory will remain around until you call android_app_exec_cmd() for 134 | // APP_CMD_RESUME, at which point it will be freed and savedState set to NULL. 135 | // These variables should only be changed when processing a APP_CMD_SAVE_STATE, 136 | // at which point they will be initialized to NULL and you can malloc your 137 | // state and place the information here. In that case the memory will be 138 | // freed for you later. 139 | void* savedState; 140 | size_t savedStateSize; 141 | 142 | // The ALooper associated with the app's thread. 143 | ALooper* looper; 144 | 145 | // When non-NULL, this is the input queue from which the app will 146 | // receive user input events. 147 | AInputQueue* inputQueue; 148 | 149 | // When non-NULL, this is the window surface that the app can draw in. 150 | ANativeWindow* window; 151 | 152 | // Current content rectangle of the window; this is the area where the 153 | // window's content should be placed to be seen by the user. 154 | ARect contentRect; 155 | 156 | // Current state of the app's activity. May be either APP_CMD_START, 157 | // APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP; see below. 158 | int activityState; 159 | 160 | // This is non-zero when the application's NativeActivity is being 161 | // destroyed and waiting for the app thread to complete. 162 | int destroyRequested; 163 | 164 | // ------------------------------------------------- 165 | // Below are "private" implementation of the glue code. 166 | 167 | pthread_mutex_t mutex; 168 | pthread_cond_t cond; 169 | 170 | int msgread; 171 | int msgwrite; 172 | 173 | pthread_t thread; 174 | 175 | struct android_poll_source cmdPollSource; 176 | struct android_poll_source inputPollSource; 177 | 178 | int running; 179 | int stateSaved; 180 | int destroyed; 181 | int redrawNeeded; 182 | AInputQueue* pendingInputQueue; 183 | ANativeWindow* pendingWindow; 184 | ARect pendingContentRect; 185 | }; 186 | 187 | enum { 188 | /** 189 | * Looper data ID of commands coming from the app's main thread, which 190 | * is returned as an identifier from ALooper_pollOnce(). The data for this 191 | * identifier is a pointer to an android_poll_source structure. 192 | * These can be retrieved and processed with android_app_read_cmd() 193 | * and android_app_exec_cmd(). 194 | */ 195 | LOOPER_ID_MAIN = 1, 196 | 197 | /** 198 | * Looper data ID of events coming from the AInputQueue of the 199 | * application's window, which is returned as an identifier from 200 | * ALooper_pollOnce(). The data for this identifier is a pointer to an 201 | * android_poll_source structure. These can be read via the inputQueue 202 | * object of android_app. 203 | */ 204 | LOOPER_ID_INPUT = 2, 205 | 206 | /** 207 | * Start of user-defined ALooper identifiers. 208 | */ 209 | LOOPER_ID_USER = 3, 210 | }; 211 | 212 | enum { 213 | /** 214 | * Command from main thread: the AInputQueue has changed. Upon processing 215 | * this command, android_app->inputQueue will be updated to the new queue 216 | * (or NULL). 217 | */ 218 | APP_CMD_INPUT_CHANGED, 219 | 220 | /** 221 | * Command from main thread: a new ANativeWindow is ready for use. Upon 222 | * receiving this command, android_app->window will contain the new window 223 | * surface. 224 | */ 225 | APP_CMD_INIT_WINDOW, 226 | 227 | /** 228 | * Command from main thread: the existing ANativeWindow needs to be 229 | * terminated. Upon receiving this command, android_app->window still 230 | * contains the existing window; after calling android_app_exec_cmd 231 | * it will be set to NULL. 232 | */ 233 | APP_CMD_TERM_WINDOW, 234 | 235 | /** 236 | * Command from main thread: the current ANativeWindow has been resized. 237 | * Please redraw with its new size. 238 | */ 239 | APP_CMD_WINDOW_RESIZED, 240 | 241 | /** 242 | * Command from main thread: the system needs that the current ANativeWindow 243 | * be redrawn. You should redraw the window before handing this to 244 | * android_app_exec_cmd() in order to avoid transient drawing glitches. 245 | */ 246 | APP_CMD_WINDOW_REDRAW_NEEDED, 247 | 248 | /** 249 | * Command from main thread: the content area of the window has changed, 250 | * such as from the soft input window being shown or hidden. You can 251 | * find the new content rect in android_app::contentRect. 252 | */ 253 | APP_CMD_CONTENT_RECT_CHANGED, 254 | 255 | /** 256 | * Command from main thread: the app's activity window has gained 257 | * input focus. 258 | */ 259 | APP_CMD_GAINED_FOCUS, 260 | 261 | /** 262 | * Command from main thread: the app's activity window has lost 263 | * input focus. 264 | */ 265 | APP_CMD_LOST_FOCUS, 266 | 267 | /** 268 | * Command from main thread: the current device configuration has changed. 269 | */ 270 | APP_CMD_CONFIG_CHANGED, 271 | 272 | /** 273 | * Command from main thread: the system is running low on memory. 274 | * Try to reduce your memory use. 275 | */ 276 | APP_CMD_LOW_MEMORY, 277 | 278 | /** 279 | * Command from main thread: the app's activity has been started. 280 | */ 281 | APP_CMD_START, 282 | 283 | /** 284 | * Command from main thread: the app's activity has been resumed. 285 | */ 286 | APP_CMD_RESUME, 287 | 288 | /** 289 | * Command from main thread: the app should generate a new saved state 290 | * for itself, to restore from later if needed. If you have saved state, 291 | * allocate it with malloc and place it in android_app.savedState with 292 | * the size in android_app.savedStateSize. The will be freed for you 293 | * later. 294 | */ 295 | APP_CMD_SAVE_STATE, 296 | 297 | /** 298 | * Command from main thread: the app's activity has been paused. 299 | */ 300 | APP_CMD_PAUSE, 301 | 302 | /** 303 | * Command from main thread: the app's activity has been stopped. 304 | */ 305 | APP_CMD_STOP, 306 | 307 | /** 308 | * Command from main thread: the app's activity is being destroyed, 309 | * and waiting for the app thread to clean up and exit before proceeding. 310 | */ 311 | APP_CMD_DESTROY, 312 | }; 313 | 314 | /** 315 | * Call when ALooper_pollAll() returns LOOPER_ID_MAIN, reading the next 316 | * app command message. 317 | */ 318 | int8_t android_app_read_cmd(struct android_app* android_app); 319 | 320 | /** 321 | * Call with the command returned by android_app_read_cmd() to do the 322 | * initial pre-processing of the given command. You can perform your own 323 | * actions for the command after calling this function. 324 | */ 325 | void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd); 326 | 327 | /** 328 | * Call with the command returned by android_app_read_cmd() to do the 329 | * final post-processing of the given command. You must have done your own 330 | * actions for the command before calling this function. 331 | */ 332 | void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd); 333 | 334 | /** 335 | * Dummy function that used to be used to prevent the linker from stripping app 336 | * glue code. No longer necessary, since __attribute__((visibility("default"))) 337 | * does this for us. 338 | */ 339 | __attribute__(( 340 | deprecated("Calls to app_dummy are no longer necessary. See " 341 | "https://github.com/android-ndk/ndk/issues/381."))) void 342 | app_dummy(); 343 | 344 | /** 345 | * This is the function that application code must implement, representing 346 | * the main entry to the app. 347 | */ 348 | extern void android_main(struct android_app* app); 349 | 350 | #ifdef __cplusplus 351 | } 352 | #endif 353 | 354 | #endif /* _ANDROID_NATIVE_APP_GLUE_H */ 355 | -------------------------------------------------------------------------------- /platform/wasm/template.js: -------------------------------------------------------------------------------- 1 | //Portions of code from zNoctum and redline2466 2 | 3 | //Global memory for application. 4 | let memory = new WebAssembly.Memory({ initial: 4096 }); 5 | let HEAP8 = new Int8Array(memory.buffer); 6 | let HEAPU8 = new Uint8Array(memory.buffer); 7 | let HEAP16 = new Int16Array(memory.buffer); 8 | let HEAPU16 = new Uint16Array(memory.buffer); 9 | let HEAP32 = new Uint32Array(memory.buffer); 10 | let HEAPU32 = new Uint32Array(memory.buffer); 11 | let HEAPF32 = new Float32Array(memory.buffer); 12 | let HEAPF64 = new Float64Array(memory.buffer); 13 | 14 | let toUtf8Decoder = new TextDecoder("utf-8"); 15 | function toUTF8(ptr) { 16 | let len = 0 | 0; ptr |= 0; 17 | for (let i = ptr; HEAPU8[i] != 0; i++) len++; 18 | return toUtf8Decoder.decode(HEAPU8.subarray(ptr, ptr + len)); 19 | } 20 | 21 | let wasmExports; 22 | const DATA_ADDR = 16 | 0; // Where the unwind/rewind data structure will live. 23 | let rendering = false; 24 | let fullscreen = false; 25 | 26 | //Configure WebGL Stuff (allow to be part of global context) 27 | let canvas = document.getElementById('canvas'); 28 | canvas.width = window.innerWidth; 29 | canvas.height = window.innerHeight; 30 | 31 | let wgl = canvas.getContext('webgl'); 32 | if (!wgl) { 33 | //Janky - on Firefox 83, with NVIDIA GPU, you need to ask twice. 34 | wgl = canvas.getContext('webgl'); 35 | } 36 | let wglShader = null; //Standard flat color shader 37 | let wglABV = null; //Array buffer for vertices 38 | let wglABC = null; //Array buffer for colors. 39 | let wglUXFRM = null; //Uniform location for transform on solid colors 40 | 41 | 42 | //Utility stuff for WebGL sahder creation. 43 | function wgl_makeShader(vertText, fragText) { 44 | let vert = wgl.createShader(wgl.VERTEX_SHADER); 45 | wgl.shaderSource(vert, vertText); 46 | wgl.compileShader(vert); 47 | if (!wgl.getShaderParameter(vert, wgl.COMPILE_STATUS)) { 48 | alert(wgl.getShaderInfoLog(vert)); 49 | } 50 | 51 | let frag = wgl.createShader(wgl.FRAGMENT_SHADER); 52 | wgl.shaderSource(frag, fragText); 53 | wgl.compileShader(frag); 54 | if (!wgl.getShaderParameter(frag, wgl.COMPILE_STATUS)) { 55 | alert(wgl.getShaderInfoLog(frag)); 56 | } 57 | let ret = wgl.createProgram(); 58 | wgl.attachShader(ret, frag); 59 | wgl.attachShader(ret, vert); 60 | wgl.linkProgram(ret); 61 | wgl.bindAttribLocation(ret, 0, "a0"); 62 | wgl.bindAttribLocation(ret, 1, "a1"); 63 | return ret; 64 | } 65 | 66 | { 67 | //We load two shaders, one is a solid-color shader, for most rawdraw objects. 68 | wglShader = wgl_makeShader( 69 | "uniform vec4 xfrm; attribute vec3 a0; attribute vec4 a1; varying vec4 vc; void main() { gl_Position = vec4( a0.xy*xfrm.xy+xfrm.zw, a0.z, 0.5 ); vc = a1; }", 70 | "precision mediump float; varying vec4 vc; void main() { gl_FragColor = vec4(vc.xyzw); }"); 71 | 72 | wglUXFRM = wgl.getUniformLocation(wglShader, "xfrm"); 73 | 74 | //Compile the shaders. 75 | wgl.useProgram(wglShader); 76 | 77 | //Get some vertex/color buffers, to put geometry in. 78 | wglABV = wgl.createBuffer(); 79 | wglABC = wgl.createBuffer(); 80 | 81 | //We're using two buffers, so just enable them, now. 82 | wgl.enableVertexAttribArray(0); 83 | wgl.enableVertexAttribArray(1); 84 | 85 | //Enable alpha blending 86 | wgl.enable(wgl.BLEND); 87 | wgl.blendFunc(wgl.SRC_ALPHA, wgl.ONE_MINUS_SRC_ALPHA); 88 | } 89 | 90 | //Do webgl work that must happen every frame. 91 | function FrameStart() { 92 | //Fixup canvas sizes 93 | if (fullscreen) { 94 | wgl.viewportWidth = canvas.width = window.innerWidth; 95 | wgl.viewportHeight = canvas.height = window.innerHeight; 96 | } 97 | 98 | //Make sure viewport and input to shader is correct. 99 | //We do this so we can pass literal coordinates into the shader. 100 | wgl.viewport(0, 0, wgl.viewportWidth, wgl.viewportHeight); 101 | 102 | //Update geometry transform (Scale/shift) 103 | wgl.uniform4f(wglUXFRM, 104 | 1. / wgl.viewportWidth, -1. / wgl.viewportHeight, 105 | -0.5, 0.5); 106 | } 107 | 108 | function SystemStart(title, w, h) { 109 | wgl.viewportWidth = canvas.width = w; 110 | wgl.viewportHeight = canvas.height = h; 111 | } 112 | 113 | //Buffered geometry system. 114 | //This handles buffering a bunch of lines/segments, and using them all at once. 115 | globalv = null; 116 | 117 | function CNFGEmitBackendTrianglesJS(vertsF, colorsI, vertcount) { 118 | const ab = wgl.ARRAY_BUFFER; 119 | wgl.bindBuffer(ab, wglABV); 120 | wgl.bufferData(ab, vertsF, wgl.DYNAMIC_DRAW); 121 | wgl.vertexAttribPointer(0, 3, wgl.FLOAT, false, 0, 0); 122 | wgl.bindBuffer(ab, wglABC); 123 | wgl.bufferData(ab, colorsI, wgl.DYNAMIC_DRAW); 124 | wgl.vertexAttribPointer(1, 4, wgl.UNSIGNED_BYTE, true, 0, 0); 125 | wgl.drawArrays(wgl.TRIANGLES, 0, vertcount); 126 | globalv = vertsF; 127 | } 128 | 129 | //This defines the list of imports, the things that C will be importing from Javascript. 130 | //To use functions here, just call them. Surprisingly, signatures justwork. 131 | let imports = { 132 | env: { 133 | //Mapping our array buffer into the system. 134 | memory: memory, 135 | 136 | //Various draw-functions. 137 | CNFGEmitBackendTriangles: (vertsF, colorsI, vertcount) => { 138 | //Take a float* and uint32_t* of vertices, and flat-render them. 139 | CNFGEmitBackendTrianglesJS( 140 | HEAPF32.slice(vertsF >> 2, (vertsF >> 2) + vertcount * 3), 141 | HEAPU8.slice(colorsI, (colorsI) + vertcount * 4), 142 | vertcount); 143 | }, 144 | CNFGSetup: (title, w, h) => { 145 | SystemStart(title, w, h); 146 | fullscreen = false; 147 | }, 148 | CNFGSetupFullscreen: (title, w, h) => { 149 | SystemStart(title, w, h); 150 | canvas.style = "position:absolute; top:0; left:0;" 151 | fullscreen = true; 152 | }, 153 | CNFGClearFrameInternal: (color) => { 154 | wgl.clearColor((color & 0xff) / 255., ((color >> 8) & 0xff) / 255., 155 | ((color >> 16) & 0xff) / 255., ((color >> 24) & 0xff) / 255.); 156 | wgl.clear(wgl.COLOR_BUFFER_BIT | wgl.COLOR_DEPTH_BIT); 157 | }, 158 | CNFGGetDimensions: (pw, ph) => { 159 | HEAP16[pw >> 1] = canvas.width; 160 | HEAP16[ph >> 1] = canvas.height; 161 | }, 162 | OGGetAbsoluteTime: () => { return performance.now() / 1000.; }, 163 | 164 | Add1: (i) => { return i + 1; }, //Super simple function for speed testing. 165 | 166 | //Tricky - math functions just automatically link through. 167 | sin: Math.sin, 168 | cos: Math.cos, 169 | tan: Math.tan, 170 | sinf: Math.sin, 171 | cosf: Math.cos, 172 | tanf: Math.tan, 173 | ceilf: Math.ceil, 174 | 175 | //Quick-and-dirty debug. 176 | print: console.log, 177 | prints: (str) => { console.log(toUTF8(str)); }, 178 | } 179 | }; 180 | 181 | if (!RAWDRAW_USE_LOOP_FUNCTION) { 182 | imports.bynsyncify = { 183 | //Any javascript functions which may unwind the stack should be placed here. 184 | CNFGSwapBuffersInternal: () => { 185 | if (!rendering) { 186 | // We are called in order to start a sleep/unwind. 187 | // Fill in the data structure. The first value has the stack location, 188 | // which for simplicity we can start right after the data structure itself. 189 | HEAPU32[DATA_ADDR >> 2] = DATA_ADDR + 8; 190 | // The end of the stack will not be reached here anyhow. 191 | HEAPU32[DATA_ADDR + 4 >> 2] = 1024 | 0; 192 | wasmExports.asyncify_start_unwind(DATA_ADDR); 193 | rendering = true; 194 | // Resume after the proper delay. 195 | requestAnimationFrame(function () { 196 | FrameStart(); 197 | wasmExports.asyncify_start_rewind(DATA_ADDR); 198 | // The code is now ready to rewind; to start the process, enter the 199 | // first function that should be on the call stack. 200 | wasmExports.main(); 201 | }); 202 | } else { 203 | // We are called as part of a resume/rewind. Stop sleeping. 204 | wasmExports.asyncify_stop_rewind(); 205 | rendering = false; 206 | } 207 | } 208 | } 209 | } 210 | 211 | if (RAWDRAW_NEED_BLITTER) { 212 | let wglBlit = null; //Blitting shader for texture 213 | let wglTex = null; //Texture handle for blitting. 214 | let wglUXFRMBlit = null; //Uniform location for transform on blitter 215 | 216 | //We are not currently supporting the software renderer. 217 | //We load two shaders, the other is a texture shader, for blitting things. 218 | wglBlit = wgl_makeShader( 219 | "uniform vec4 xfrm; attribute vec3 a0; attribute vec4 a1; varying vec2 tc; void main() { gl_Position = vec4( a0.xy*xfrm.xy+xfrm.zw, a0.z, 0.5 ); tc = a1.xy; }", 220 | "precision mediump float; varying vec2 tc; uniform sampler2D tex; void main() { gl_FragColor = texture2D(tex,tc).wzyx;}"); 221 | 222 | wglUXFRMBlit = wgl.getUniformLocation(wglBlit, "xfrm"); 223 | 224 | imports.env.CNFGBlitImageInternal = (memptr, x, y, w, h) => { 225 | if (w <= 0 || h <= 0) return; 226 | 227 | wgl.useProgram(wglBlit); 228 | 229 | //Most of the time we don't use textures, so don't initiate at start. 230 | if (wglTex == null) wglTex = wgl.createTexture(); 231 | 232 | wgl.activeTexture(wgl.TEXTURE0); 233 | const t2d = wgl.TEXTURE_2D; 234 | wgl.bindTexture(t2d, wglTex); 235 | 236 | //Note that unlike the normal color operation, we don't have an extra offset. 237 | wgl.uniform4f(wglUXFRMBlit, 238 | 1. / wgl.viewportWidth, -1. / wgl.viewportHeight, 239 | -.5 + x / wgl.viewportWidth, .5 - y / wgl.viewportHeight); 240 | 241 | //These parameters are required. Not sure why the defaults don't work. 242 | wgl.texParameteri(t2d, wgl.TEXTURE_WRAP_T, wgl.CLAMP_TO_EDGE); 243 | wgl.texParameteri(t2d, wgl.TEXTURE_WRAP_S, wgl.CLAMP_TO_EDGE); 244 | wgl.texParameteri(t2d, wgl.TEXTURE_MIN_FILTER, wgl.NEAREST); 245 | 246 | wgl.texImage2D(t2d, 0, wgl.RGBA, w, h, 0, wgl.RGBA, 247 | wgl.UNSIGNED_BYTE, new Uint8Array(memory.buffer, memptr, w * h * 4)); 248 | 249 | CNFGEmitBackendTrianglesJS( 250 | new Float32Array([0, 0, 0, w, 0, 0, w, h, 0, 0, 0, 0, w, h, 0, 0, h, 0]), 251 | new Uint8Array([0, 0, 0, 0, 255, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 255, 0, 0]), 252 | 6); 253 | 254 | wgl.useProgram(wglShader); 255 | }; 256 | } 257 | 258 | { 259 | // Actually load the WASM blob. 260 | let blob = atob('${BLOB}'); 261 | let array = new Uint8Array(new ArrayBuffer(blob.length)); 262 | for (let i = 0; i < blob.length; i++) array[i] = blob.charCodeAt(i); 263 | 264 | WebAssembly.instantiate(array, imports).then( 265 | function (wa) { 266 | instance = wa.instance; 267 | wasmExports = instance.exports; 268 | 269 | window.addEventListener('resize', () => { 270 | canvas.width = window.innerWidth; 271 | canvas.height = window.innerHeight; 272 | instance.exports.OnResize(window.innerWidth, window.innerHeight); 273 | }); 274 | 275 | //Attach inputs. 276 | if (instance.exports.HandleMotion) { 277 | canvas.addEventListener('mousemove', e => { instance.exports.HandleMotion(e.offsetX, e.offsetY, e.buttons); }); 278 | canvas.addEventListener('touchmove', e => { instance.exports.HandleMotion(e.touches[0].clientX, e.touches[0].clientY, 1); }); 279 | } 280 | 281 | if (instance.exports.HandleButton) { 282 | canvas.addEventListener('mouseup', e => { instance.exports.HandleButton(e.offsetX, e.offsetY, e.button, 0); return false; }); 283 | canvas.addEventListener('mousedown', e => { instance.exports.HandleButton(e.offsetX, e.offsetY, e.button, 1); return false; }); 284 | canvas.addEventListener('touchstart', e => { instance.exports.HandleButton(e.touches[0].clientX, e.touches[0].clientY, 0, 1); }); 285 | canvas.addEventListener('touchend', e => { instance.exports.touchend(); }); 286 | canvas.addEventListener('touchcancel', e => { instance.exports.touchend(); }); 287 | } 288 | 289 | if (instance.exports.HandleKey) { 290 | document.addEventListener('keydown', e => { instance.exports.HandleKey(e.keyCode, 1); }); 291 | document.addEventListener('keyup', e => { instance.exports.HandleKey(e.keyCode, 0); }); 292 | } 293 | 294 | 295 | //Actually invoke main(). Note that, upon "CNFGSwapBuffers" this will 'exit' 296 | //But, will get re-entered from the swapbuffers animation callback. 297 | instance.exports.main(); 298 | 299 | if (RAWDRAW_USE_LOOP_FUNCTION) { 300 | function floop() { 301 | FrameStart(); 302 | requestAnimationFrame(floop); 303 | // The code is now ready to rewind; to start the process, enter the 304 | // first function that should be on the call stack. 305 | wasmExports.loop(); 306 | } 307 | floop(); 308 | } 309 | }); 310 | 311 | //Code here would continue executing, but this code is executed *before* main. 312 | } 313 | -------------------------------------------------------------------------------- /src/include/os_generic.h: -------------------------------------------------------------------------------- 1 | #ifndef _OS_GENERIC_H 2 | #define _OS_GENERIC_H 3 | /* 4 | "osgeneric" Generic, platform independent tool for threads and time. 5 | Geared around Windows and Linux. Designed for operation on MSVC, 6 | TCC, GCC and clang. Others may work. 7 | 8 | It offers the following operations: 9 | 10 | Delay functions: 11 | void OGSleep( int is ); 12 | void OGUSleep( int ius ); 13 | 14 | Getting current time (may be time from program start, boot, or epoc) 15 | double OGGetAbsoluteTime(); 16 | double OGGetFileTime( const char * file ); 17 | 18 | Thread functions 19 | og_thread_t OGCreateThread( void * (routine)( void * ), void * parameter ); 20 | void * OGJoinThread( og_thread_t ot ); 21 | void OGCancelThread( og_thread_t ot ); 22 | 23 | Mutex functions, used for protecting data structures. 24 | (recursive on platforms where available.) 25 | og_mutex_t OGCreateMutex(); 26 | void OGLockMutex( og_mutex_t om ); 27 | void OGUnlockMutex( og_mutex_t om ); 28 | void OGDeleteMutex( og_mutex_t om ); 29 | 30 | Always a semaphore (not recursive) 31 | og_sema_t OGCreateSema(); //Create a semaphore, comes locked initially. 32 | NOTE: For platform compatibility, max count is 32767 33 | void OGLockSema( og_sema_t os ); 34 | int OGGetSema( og_sema_t os ); //if <0 there was a failure. 35 | void OGUnlockSema( og_sema_t os ); 36 | void OGDeleteSema( og_sema_t os ); 37 | 38 | TLS (Thread-Local Storage) 39 | og_tls_t OGCreateTLS(); 40 | void OGDeleteTLS( og_tls_t tls ); 41 | void OGSetTLS( og_tls_t tls, void * data ); 42 | void * OGGetTLS( og_tls_t tls ); 43 | 44 | You can permute the operations of this file by the following means: 45 | OSG_NO_IMPLEMENTATION 46 | OSG_PREFIX 47 | OSG_NOSTATIC 48 | 49 | The default behavior is to do static inline. 50 | 51 | Copyright (c) 2011-2012,2013,2016,2018,2019,2020 <>< Charles Lohr 52 | 53 | This file may be licensed under the MIT/x11 license, NewBSD or CC0 licenses 54 | 55 | Permission is hereby granted, free of charge, to any person obtaining a 56 | copy of this software and associated documentation files (the "Software"), 57 | to deal in the Software without restriction, including without limitation 58 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 59 | and/or sell copies of the Software, and to permit persons to whom the 60 | Software is furnished to do so, subject to the following conditions: 61 | 62 | The above copyright notice and this permission notice shall be included in 63 | all copies or substantial portions of this file. 64 | 65 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 66 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 67 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 68 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 69 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 70 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 71 | IN THE SOFTWARE. 72 | 73 | Date Stamp: 2019-09-05 CNL: Allow for noninstantiation and added TLS. 74 | Date Stamp: 2018-03-25 CNL: Switched to header-only format. 75 | */ 76 | 77 | 78 | #if defined( OSG_NOSTATIC ) && OSG_NOSTATIC != 0 79 | #ifndef OSG_PREFIX 80 | #define OSG_PREFIX 81 | #endif 82 | #ifndef OSG_NO_IMPLEMENTATION 83 | #define OSG_NO_IMPLEMENTATION 84 | #endif 85 | #endif 86 | 87 | #ifndef OSG_PREFIX 88 | #ifdef __wasm__ 89 | #define OSG_PREFIX 90 | #else 91 | #define OSG_PREFIX static inline 92 | #endif 93 | #endif 94 | 95 | //In case you want to hook the closure of a thread, i.e. if your system has thread-local storage. 96 | #ifndef OSG_TERM_THREAD_CODE 97 | #define OSG_TERM_THREAD_CODE 98 | #endif 99 | 100 | typedef void* og_thread_t; 101 | typedef void* og_mutex_t; 102 | typedef void* og_sema_t; 103 | typedef void* og_tls_t; 104 | 105 | #ifdef __cplusplus 106 | extern "C" { 107 | #endif 108 | 109 | OSG_PREFIX void OGSleep( int is ); 110 | OSG_PREFIX void OGUSleep( int ius ); 111 | OSG_PREFIX double OGGetAbsoluteTime(); 112 | OSG_PREFIX double OGGetFileTime( const char * file ); 113 | OSG_PREFIX og_thread_t OGCreateThread( void * (routine)( void * ), void * parameter ); 114 | OSG_PREFIX void * OGJoinThread( og_thread_t ot ); 115 | OSG_PREFIX void OGCancelThread( og_thread_t ot ); 116 | OSG_PREFIX og_mutex_t OGCreateMutex(); 117 | OSG_PREFIX void OGLockMutex( og_mutex_t om ); 118 | OSG_PREFIX void OGUnlockMutex( og_mutex_t om ); 119 | OSG_PREFIX void OGDeleteMutex( og_mutex_t om ); 120 | OSG_PREFIX og_sema_t OGCreateSema(); 121 | OSG_PREFIX int OGGetSema( og_sema_t os ); 122 | OSG_PREFIX void OGLockSema( og_sema_t os ); 123 | OSG_PREFIX void OGUnlockSema( og_sema_t os ); 124 | OSG_PREFIX void OGDeleteSema( og_sema_t os ); 125 | OSG_PREFIX og_tls_t OGCreateTLS(); 126 | OSG_PREFIX void OGDeleteTLS( og_tls_t key ); 127 | OSG_PREFIX void * OGGetTLS( og_tls_t key ); 128 | OSG_PREFIX void OGSetTLS( og_tls_t key, void * data ); 129 | 130 | #ifdef __cplusplus 131 | }; 132 | #endif 133 | 134 | #ifndef OSG_NO_IMPLEMENTATION 135 | 136 | #if defined( WIN32 ) || defined (WINDOWS) || defined( _WIN32) 137 | #define USE_WINDOWS 138 | #endif 139 | 140 | 141 | #ifdef __cplusplus 142 | extern "C" { 143 | #endif 144 | 145 | 146 | #ifdef USE_WINDOWS 147 | 148 | #include 149 | #include 150 | 151 | OSG_PREFIX void OGSleep( int is ) 152 | { 153 | Sleep( is*1000 ); 154 | } 155 | 156 | OSG_PREFIX void OGUSleep( int ius ) 157 | { 158 | Sleep( ius/1000 ); 159 | } 160 | 161 | OSG_PREFIX double OGGetAbsoluteTime() 162 | { 163 | static LARGE_INTEGER lpf; 164 | LARGE_INTEGER li; 165 | 166 | if( !lpf.QuadPart ) 167 | { 168 | QueryPerformanceFrequency( &lpf ); 169 | } 170 | 171 | QueryPerformanceCounter( &li ); 172 | return (double)li.QuadPart / (double)lpf.QuadPart; 173 | } 174 | 175 | 176 | OSG_PREFIX double OGGetFileTime( const char * file ) 177 | { 178 | FILETIME ft; 179 | 180 | HANDLE h = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 181 | 182 | if( h==INVALID_HANDLE_VALUE ) 183 | return -1; 184 | 185 | GetFileTime( h, 0, 0, &ft ); 186 | 187 | CloseHandle( h ); 188 | 189 | return ft.dwHighDateTime + ft.dwLowDateTime; 190 | } 191 | 192 | 193 | OSG_PREFIX og_thread_t OGCreateThread( void * (routine)( void * ), void * parameter ) 194 | { 195 | return (og_thread_t)CreateThread( 0, 0, (LPTHREAD_START_ROUTINE)routine, parameter, 0, 0 ); 196 | } 197 | 198 | OSG_PREFIX void * OGJoinThread( og_thread_t ot ) 199 | { 200 | WaitForSingleObject( ot, INFINITE ); 201 | OSG_TERM_THREAD_CODE 202 | CloseHandle( ot ); 203 | return 0; 204 | } 205 | 206 | OSG_PREFIX void OGCancelThread( og_thread_t ot ) 207 | { 208 | OSG_TERM_THREAD_CODE 209 | TerminateThread( ot, 0); 210 | CloseHandle( ot ); 211 | } 212 | 213 | OSG_PREFIX og_mutex_t OGCreateMutex() 214 | { 215 | return CreateMutex( 0, 0, 0 ); 216 | } 217 | 218 | OSG_PREFIX void OGLockMutex( og_mutex_t om ) 219 | { 220 | WaitForSingleObject(om, INFINITE); 221 | } 222 | 223 | OSG_PREFIX void OGUnlockMutex( og_mutex_t om ) 224 | { 225 | ReleaseMutex(om); 226 | } 227 | 228 | OSG_PREFIX void OGDeleteMutex( og_mutex_t om ) 229 | { 230 | CloseHandle( om ); 231 | } 232 | 233 | 234 | 235 | OSG_PREFIX og_sema_t OGCreateSema() 236 | { 237 | HANDLE sem = CreateSemaphore( 0, 0, 32767, 0 ); 238 | return (og_sema_t)sem; 239 | } 240 | 241 | OSG_PREFIX int OGGetSema( og_sema_t os ) 242 | { 243 | typedef LONG NTSTATUS; 244 | HANDLE sem = (HANDLE)os; 245 | typedef NTSTATUS (NTAPI *_NtQuerySemaphore)( 246 | HANDLE SemaphoreHandle, 247 | DWORD SemaphoreInformationClass, /* Would be SEMAPHORE_INFORMATION_CLASS */ 248 | PVOID SemaphoreInformation, /* but this is to much to dump here */ 249 | ULONG SemaphoreInformationLength, 250 | PULONG ReturnLength OPTIONAL 251 | ); 252 | 253 | typedef struct _SEMAPHORE_BASIC_INFORMATION 254 | { 255 | ULONG CurrentCount; 256 | ULONG MaximumCount; 257 | } SEMAPHORE_BASIC_INFORMATION; 258 | 259 | 260 | static _NtQuerySemaphore NtQuerySemaphore; 261 | SEMAPHORE_BASIC_INFORMATION BasicInfo; 262 | NTSTATUS Status; 263 | 264 | if( !NtQuerySemaphore ) 265 | { 266 | NtQuerySemaphore = (_NtQuerySemaphore)GetProcAddress (GetModuleHandle ("ntdll.dll"), "NtQuerySemaphore"); 267 | if( !NtQuerySemaphore ) 268 | { 269 | return -1; 270 | } 271 | } 272 | 273 | 274 | Status = NtQuerySemaphore (sem, 0 /*SemaphoreBasicInformation*/, 275 | &BasicInfo, sizeof (SEMAPHORE_BASIC_INFORMATION), NULL); 276 | 277 | if (Status == ERROR_SUCCESS) 278 | { 279 | return BasicInfo.CurrentCount; 280 | } 281 | 282 | return -2; 283 | } 284 | 285 | OSG_PREFIX void OGLockSema( og_sema_t os ) 286 | { 287 | WaitForSingleObject( (HANDLE)os, INFINITE ); 288 | } 289 | 290 | OSG_PREFIX void OGUnlockSema( og_sema_t os ) 291 | { 292 | ReleaseSemaphore( (HANDLE)os, 1, 0 ); 293 | } 294 | 295 | OSG_PREFIX void OGDeleteSema( og_sema_t os ) 296 | { 297 | CloseHandle( os ); 298 | } 299 | 300 | OSG_PREFIX og_tls_t OGCreateTLS() 301 | { 302 | return (og_tls_t)(intptr_t)TlsAlloc(); 303 | } 304 | 305 | OSG_PREFIX void OGDeleteTLS( og_tls_t key ) 306 | { 307 | TlsFree( (DWORD)(intptr_t)key ); 308 | } 309 | 310 | OSG_PREFIX void * OGGetTLS( og_tls_t key ) 311 | { 312 | return TlsGetValue( (DWORD)(intptr_t)key ); 313 | } 314 | 315 | OSG_PREFIX void OGSetTLS( og_tls_t key, void * data ) 316 | { 317 | TlsSetValue( (DWORD)(intptr_t)key, data ); 318 | } 319 | 320 | #elif defined( __wasm__ ) 321 | 322 | //We don't actually have any function defintions here. 323 | //The outside system will handle it. 324 | 325 | #else 326 | 327 | #ifndef _GNU_SOURCE 328 | #define _GNU_SOURCE 329 | #endif 330 | 331 | #include 332 | #include 333 | #include 334 | #include 335 | #include 336 | #include 337 | 338 | OSG_PREFIX void OGSleep( int is ) 339 | { 340 | sleep( is ); 341 | } 342 | 343 | OSG_PREFIX void OGUSleep( int ius ) 344 | { 345 | usleep( ius ); 346 | } 347 | 348 | OSG_PREFIX double OGGetAbsoluteTime() 349 | { 350 | struct timeval tv; 351 | gettimeofday( &tv, 0 ); 352 | return ((double)tv.tv_usec)/1000000. + (tv.tv_sec); 353 | } 354 | 355 | OSG_PREFIX double OGGetFileTime( const char * file ) 356 | { 357 | struct stat buff; 358 | 359 | int r = stat( file, &buff ); 360 | 361 | if( r < 0 ) 362 | { 363 | return -1; 364 | } 365 | 366 | return buff.st_mtime; 367 | } 368 | 369 | 370 | 371 | OSG_PREFIX og_thread_t OGCreateThread( void * (routine)( void * ), void * parameter ) 372 | { 373 | pthread_t * ret = (pthread_t *)malloc( sizeof( pthread_t ) ); 374 | if( !ret ) return 0; 375 | int r = pthread_create( ret, 0, routine, parameter ); 376 | if( r ) 377 | { 378 | free( ret ); 379 | return 0; 380 | } 381 | return (og_thread_t)ret; 382 | } 383 | 384 | OSG_PREFIX void * OGJoinThread( og_thread_t ot ) 385 | { 386 | void * retval; 387 | if( !ot ) 388 | { 389 | return 0; 390 | } 391 | pthread_join( *(pthread_t*)ot, &retval ); 392 | OSG_TERM_THREAD_CODE 393 | free( ot ); 394 | return retval; 395 | } 396 | 397 | OSG_PREFIX void OGCancelThread( og_thread_t ot ) 398 | { 399 | if( !ot ) 400 | { 401 | return; 402 | } 403 | #ifdef ANDROID 404 | pthread_kill( *(pthread_t*)ot, SIGTERM ); 405 | #else 406 | pthread_cancel( *(pthread_t*)ot ); 407 | #endif 408 | OSG_TERM_THREAD_CODE 409 | free( ot ); 410 | } 411 | 412 | OSG_PREFIX og_mutex_t OGCreateMutex() 413 | { 414 | pthread_mutexattr_t mta; 415 | og_mutex_t r = malloc( sizeof( pthread_mutex_t ) ); 416 | if( !r ) return 0; 417 | 418 | pthread_mutexattr_init(&mta); 419 | pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_RECURSIVE); 420 | 421 | pthread_mutex_init( (pthread_mutex_t *)r, &mta ); 422 | 423 | return r; 424 | } 425 | 426 | OSG_PREFIX void OGLockMutex( og_mutex_t om ) 427 | { 428 | if( !om ) 429 | { 430 | return; 431 | } 432 | pthread_mutex_lock( (pthread_mutex_t*)om ); 433 | } 434 | 435 | OSG_PREFIX void OGUnlockMutex( og_mutex_t om ) 436 | { 437 | if( !om ) 438 | { 439 | return; 440 | } 441 | pthread_mutex_unlock( (pthread_mutex_t*)om ); 442 | } 443 | 444 | OSG_PREFIX void OGDeleteMutex( og_mutex_t om ) 445 | { 446 | if( !om ) 447 | { 448 | return; 449 | } 450 | 451 | pthread_mutex_destroy( (pthread_mutex_t*)om ); 452 | free( om ); 453 | } 454 | 455 | 456 | 457 | 458 | OSG_PREFIX og_sema_t OGCreateSema() 459 | { 460 | sem_t * sem = (sem_t *)malloc( sizeof( sem_t ) ); 461 | if( !sem ) return 0; 462 | sem_init( sem, 0, 0 ); 463 | return (og_sema_t)sem; 464 | } 465 | 466 | OSG_PREFIX int OGGetSema( og_sema_t os ) 467 | { 468 | int valp; 469 | sem_getvalue( (sem_t*)os, &valp ); 470 | return valp; 471 | } 472 | 473 | 474 | OSG_PREFIX void OGLockSema( og_sema_t os ) 475 | { 476 | sem_wait( (sem_t*)os ); 477 | } 478 | 479 | OSG_PREFIX void OGUnlockSema( og_sema_t os ) 480 | { 481 | sem_post( (sem_t*)os ); 482 | } 483 | 484 | OSG_PREFIX void OGDeleteSema( og_sema_t os ) 485 | { 486 | sem_destroy( (sem_t*)os ); 487 | free(os); 488 | } 489 | 490 | OSG_PREFIX og_tls_t OGCreateTLS() 491 | { 492 | pthread_key_t ret = 0; 493 | pthread_key_create(&ret, 0); 494 | return (og_tls_t)(intptr_t)ret; 495 | } 496 | 497 | OSG_PREFIX void OGDeleteTLS( og_tls_t key ) 498 | { 499 | pthread_key_delete( (pthread_key_t)(intptr_t)key ); 500 | } 501 | 502 | OSG_PREFIX void * OGGetTLS( og_tls_t key ) 503 | { 504 | return pthread_getspecific( (pthread_key_t)(intptr_t)key ); 505 | } 506 | 507 | OSG_PREFIX void OGSetTLS( og_tls_t key, void * data ) 508 | { 509 | pthread_setspecific( (pthread_key_t)(intptr_t)key, data ); 510 | } 511 | 512 | #endif 513 | 514 | #ifdef __cplusplus 515 | }; 516 | #endif 517 | 518 | #endif //OSG_NO_IMPLEMENTATION 519 | 520 | #endif //_OS_GENERIC_H 521 | -------------------------------------------------------------------------------- /platform/android/android_native_app_glue.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "android_native_app_glue.h" 27 | #include 28 | 29 | #define LOGI(...) ((void)printf(__VA_ARGS__)) 30 | #define LOGE(...) ((void)printf(__VA_ARGS__)) 31 | 32 | /* For debug builds, always enable the debug traces in this library */ 33 | 34 | #ifndef NDEBUG 35 | # define LOGV(...) ((void)printf(__VA_ARGS__)) 36 | #else 37 | # define LOGV(...) ((void)0) 38 | #endif 39 | 40 | static int pfd[2]; 41 | pthread_t debug_capture_thread; 42 | static void * debug_capture_thread_fn( void * v ) 43 | { 44 | //struct android_app * app = (struct android_app*)v; 45 | ssize_t readSize; 46 | char buf[2048]; 47 | 48 | while((readSize = read(pfd[0], buf, sizeof buf - 1)) > 0) { 49 | if(buf[readSize - 1] == '\n') { 50 | --readSize; 51 | } 52 | buf[readSize] = 0; // add null-terminator 53 | __android_log_write(ANDROID_LOG_DEBUG, APPNAME, buf); // Set any log level you want 54 | #ifdef RDALOGFNCB 55 | extern void RDALOGFNCB( int size, char * buf ); 56 | RDALOGFNCB( readSize, buf ); 57 | #endif 58 | //if( debug_capture_hook_function ) debug_capture_hook_function( readSize, buf ); 59 | } 60 | return 0; 61 | } 62 | 63 | static void free_saved_state(struct android_app* android_app) { 64 | pthread_mutex_lock(&android_app->mutex); 65 | if (android_app->savedState != NULL) { 66 | free(android_app->savedState); 67 | android_app->savedState = NULL; 68 | android_app->savedStateSize = 0; 69 | } 70 | pthread_mutex_unlock(&android_app->mutex); 71 | } 72 | 73 | int8_t android_app_read_cmd(struct android_app* android_app) { 74 | int8_t cmd; 75 | if (read(android_app->msgread, &cmd, sizeof(cmd)) == sizeof(cmd)) { 76 | switch (cmd) { 77 | case APP_CMD_SAVE_STATE: 78 | free_saved_state(android_app); 79 | break; 80 | } 81 | return cmd; 82 | } else { 83 | LOGE("No data on command pipe!"); 84 | } 85 | return -1; 86 | } 87 | 88 | static void print_cur_config(struct android_app* android_app) { 89 | //For additional debugging this can be enabled, but for now - no need for the extra space. 90 | /* 91 | char lang[2], country[2]; 92 | AConfiguration_getLanguage(android_app->config, lang); 93 | AConfiguration_getCountry(android_app->config, country); 94 | 95 | LOGV("Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d " 96 | "keys=%d nav=%d keysHid=%d navHid=%d sdk=%d size=%d long=%d " 97 | "modetype=%d modenight=%d", 98 | AConfiguration_getMcc(android_app->config), 99 | AConfiguration_getMnc(android_app->config), 100 | lang[0], lang[1], country[0], country[1], 101 | AConfiguration_getOrientation(android_app->config), 102 | AConfiguration_getTouchscreen(android_app->config), 103 | AConfiguration_getDensity(android_app->config), 104 | AConfiguration_getKeyboard(android_app->config), 105 | AConfiguration_getNavigation(android_app->config), 106 | AConfiguration_getKeysHidden(android_app->config), 107 | AConfiguration_getNavHidden(android_app->config), 108 | AConfiguration_getSdkVersion(android_app->config), 109 | AConfiguration_getScreenSize(android_app->config), 110 | AConfiguration_getScreenLong(android_app->config), 111 | AConfiguration_getUiModeType(android_app->config), 112 | AConfiguration_getUiModeNight(android_app->config)); 113 | */ 114 | } 115 | 116 | void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd) { 117 | switch (cmd) { 118 | case APP_CMD_INPUT_CHANGED: 119 | LOGV("APP_CMD_INPUT_CHANGED\n"); 120 | pthread_mutex_lock(&android_app->mutex); 121 | if (android_app->inputQueue != NULL) { 122 | AInputQueue_detachLooper(android_app->inputQueue); 123 | } 124 | android_app->inputQueue = android_app->pendingInputQueue; 125 | if (android_app->inputQueue != NULL) { 126 | LOGV("Attaching input queue to looper"); 127 | AInputQueue_attachLooper(android_app->inputQueue, 128 | android_app->looper, LOOPER_ID_INPUT, NULL, 129 | &android_app->inputPollSource); 130 | } 131 | pthread_cond_broadcast(&android_app->cond); 132 | pthread_mutex_unlock(&android_app->mutex); 133 | break; 134 | 135 | case APP_CMD_INIT_WINDOW: 136 | LOGV("APP_CMD_INIT_WINDOW\n"); 137 | pthread_mutex_lock(&android_app->mutex); 138 | android_app->window = android_app->pendingWindow; 139 | pthread_cond_broadcast(&android_app->cond); 140 | pthread_mutex_unlock(&android_app->mutex); 141 | break; 142 | 143 | case APP_CMD_TERM_WINDOW: 144 | LOGV("APP_CMD_TERM_WINDOW\n"); 145 | pthread_cond_broadcast(&android_app->cond); 146 | break; 147 | 148 | case APP_CMD_RESUME: 149 | case APP_CMD_START: 150 | case APP_CMD_PAUSE: 151 | case APP_CMD_STOP: 152 | LOGV("activityState=%d\n", cmd); 153 | pthread_mutex_lock(&android_app->mutex); 154 | android_app->activityState = cmd; 155 | pthread_cond_broadcast(&android_app->cond); 156 | pthread_mutex_unlock(&android_app->mutex); 157 | break; 158 | 159 | case APP_CMD_CONFIG_CHANGED: 160 | LOGV("APP_CMD_CONFIG_CHANGED\n"); 161 | AConfiguration_fromAssetManager(android_app->config, 162 | android_app->activity->assetManager); 163 | print_cur_config(android_app); 164 | break; 165 | 166 | case APP_CMD_DESTROY: 167 | LOGV("APP_CMD_DESTROY\n"); 168 | android_app->destroyRequested = 1; 169 | break; 170 | } 171 | } 172 | 173 | void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd) { 174 | switch (cmd) { 175 | case APP_CMD_TERM_WINDOW: 176 | LOGV("APP_CMD_TERM_WINDOW\n"); 177 | pthread_mutex_lock(&android_app->mutex); 178 | android_app->window = NULL; 179 | pthread_cond_broadcast(&android_app->cond); 180 | pthread_mutex_unlock(&android_app->mutex); 181 | break; 182 | 183 | case APP_CMD_SAVE_STATE: 184 | LOGV("APP_CMD_SAVE_STATE\n"); 185 | pthread_mutex_lock(&android_app->mutex); 186 | android_app->stateSaved = 1; 187 | pthread_cond_broadcast(&android_app->cond); 188 | pthread_mutex_unlock(&android_app->mutex); 189 | break; 190 | 191 | case APP_CMD_RESUME: 192 | free_saved_state(android_app); 193 | break; 194 | } 195 | } 196 | 197 | void app_dummy() { 198 | 199 | } 200 | 201 | static void android_app_destroy(struct android_app* android_app) { 202 | LOGV("android_app_destroy!"); 203 | free_saved_state(android_app); 204 | pthread_mutex_lock(&android_app->mutex); 205 | if (android_app->inputQueue != NULL) { 206 | AInputQueue_detachLooper(android_app->inputQueue); 207 | } 208 | AConfiguration_delete(android_app->config); 209 | android_app->destroyed = 1; 210 | pthread_cond_broadcast(&android_app->cond); 211 | pthread_mutex_unlock(&android_app->mutex); 212 | // Can't touch android_app object after this. 213 | } 214 | 215 | static void process_input(struct android_app* app, struct android_poll_source* source) { 216 | AInputEvent* event = NULL; 217 | while (AInputQueue_getEvent(app->inputQueue, &event) >= 0) { 218 | //LOGV("New input event: type=%d\n", AInputEvent_getType(event)); 219 | if (AInputQueue_preDispatchEvent(app->inputQueue, event)) { 220 | continue; 221 | } 222 | int32_t handled = 0; 223 | if (app->onInputEvent != NULL) handled = app->onInputEvent(app, event); 224 | AInputQueue_finishEvent(app->inputQueue, event, handled); 225 | } 226 | } 227 | 228 | static void process_cmd(struct android_app* app, struct android_poll_source* source) { 229 | int8_t cmd = android_app_read_cmd(app); 230 | android_app_pre_exec_cmd(app, cmd); 231 | if (app->onAppCmd != NULL) app->onAppCmd(app, cmd); 232 | android_app_post_exec_cmd(app, cmd); 233 | } 234 | 235 | static void* android_app_entry(void* param) { 236 | struct android_app* android_app = (struct android_app*)param; 237 | 238 | android_app->config = AConfiguration_new(); 239 | AConfiguration_fromAssetManager(android_app->config, android_app->activity->assetManager); 240 | 241 | print_cur_config(android_app); 242 | 243 | android_app->cmdPollSource.id = LOOPER_ID_MAIN; 244 | android_app->cmdPollSource.app = android_app; 245 | android_app->cmdPollSource.process = process_cmd; 246 | android_app->inputPollSource.id = LOOPER_ID_INPUT; 247 | android_app->inputPollSource.app = android_app; 248 | android_app->inputPollSource.process = process_input; 249 | 250 | ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); 251 | ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL, 252 | &android_app->cmdPollSource); 253 | android_app->looper = looper; 254 | 255 | pthread_mutex_lock(&android_app->mutex); 256 | android_app->running = 1; 257 | pthread_cond_broadcast(&android_app->cond); 258 | pthread_mutex_unlock(&android_app->mutex); 259 | 260 | android_main(android_app); 261 | 262 | android_app_destroy(android_app); 263 | return NULL; 264 | } 265 | 266 | // -------------------------------------------------------------------- 267 | // Native activity interaction (called from main thread) 268 | // -------------------------------------------------------------------- 269 | 270 | static struct android_app* android_app_create(ANativeActivity* activity, 271 | void* savedState, size_t savedStateSize) { 272 | struct android_app* android_app = (struct android_app*)malloc(sizeof(struct android_app)); 273 | memset(android_app, 0, sizeof(struct android_app)); 274 | android_app->activity = activity; 275 | 276 | pthread_mutex_init(&android_app->mutex, NULL); 277 | pthread_cond_init(&android_app->cond, NULL); 278 | 279 | 280 | pthread_attr_t attr; 281 | pthread_attr_init(&attr); 282 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 283 | 284 | //Capture input 285 | setvbuf(stdout, 0, _IOLBF, 0); // make stdout line-buffered 286 | setvbuf(stderr, 0, _IONBF, 0); // make stderr unbuffered 287 | pipe(pfd); 288 | dup2(pfd[1], 1); 289 | dup2(pfd[1], 2); 290 | pthread_create(&debug_capture_thread, &attr, debug_capture_thread_fn, android_app); 291 | 292 | 293 | if (savedState != NULL) { 294 | android_app->savedState = malloc(savedStateSize); 295 | android_app->savedStateSize = savedStateSize; 296 | memcpy(android_app->savedState, savedState, savedStateSize); 297 | } 298 | 299 | int msgpipe[2]; 300 | if (pipe(msgpipe)) { 301 | LOGE("could not create pipe: %s", strerror(errno)); 302 | return NULL; 303 | } 304 | android_app->msgread = msgpipe[0]; 305 | android_app->msgwrite = msgpipe[1]; 306 | 307 | pthread_attr_init(&attr); 308 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 309 | pthread_create(&android_app->thread, &attr, android_app_entry, android_app); 310 | 311 | // Wait for thread to start. 312 | pthread_mutex_lock(&android_app->mutex); 313 | while (!android_app->running) { 314 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 315 | } 316 | pthread_mutex_unlock(&android_app->mutex); 317 | 318 | return android_app; 319 | } 320 | 321 | static void android_app_write_cmd(struct android_app* android_app, int8_t cmd) { 322 | if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) { 323 | LOGE("Failure writing android_app cmd: %s\n", strerror(errno)); 324 | } 325 | } 326 | 327 | static void android_app_set_input(struct android_app* android_app, AInputQueue* inputQueue) { 328 | pthread_mutex_lock(&android_app->mutex); 329 | android_app->pendingInputQueue = inputQueue; 330 | android_app_write_cmd(android_app, APP_CMD_INPUT_CHANGED); 331 | while (android_app->inputQueue != android_app->pendingInputQueue) { 332 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 333 | } 334 | pthread_mutex_unlock(&android_app->mutex); 335 | } 336 | 337 | static void android_app_set_window(struct android_app* android_app, ANativeWindow* window) { 338 | pthread_mutex_lock(&android_app->mutex); 339 | if (android_app->pendingWindow != NULL) { 340 | android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW); 341 | } 342 | android_app->pendingWindow = window; 343 | if (window != NULL) { 344 | android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW); 345 | } 346 | while (android_app->window != android_app->pendingWindow) { 347 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 348 | } 349 | pthread_mutex_unlock(&android_app->mutex); 350 | } 351 | 352 | static void android_app_set_activity_state(struct android_app* android_app, int8_t cmd) { 353 | pthread_mutex_lock(&android_app->mutex); 354 | android_app_write_cmd(android_app, cmd); 355 | while (android_app->activityState != cmd) { 356 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 357 | } 358 | pthread_mutex_unlock(&android_app->mutex); 359 | } 360 | 361 | static void android_app_free(struct android_app* android_app) { 362 | pthread_mutex_lock(&android_app->mutex); 363 | android_app_write_cmd(android_app, APP_CMD_DESTROY); 364 | while (!android_app->destroyed) { 365 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 366 | } 367 | pthread_mutex_unlock(&android_app->mutex); 368 | 369 | close(android_app->msgread); 370 | close(android_app->msgwrite); 371 | pthread_cond_destroy(&android_app->cond); 372 | pthread_mutex_destroy(&android_app->mutex); 373 | free(android_app); 374 | } 375 | 376 | static void onDestroy(ANativeActivity* activity) { 377 | LOGV("Destroy: %p\n", activity); 378 | android_app_free((struct android_app*)activity->instance); 379 | } 380 | 381 | static void onStart(ANativeActivity* activity) { 382 | LOGV("Start: %p\n", activity); 383 | android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_START); 384 | } 385 | 386 | static void onResume(ANativeActivity* activity) { 387 | LOGV("Resume: %p\n", activity); 388 | android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_RESUME); 389 | } 390 | 391 | static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) { 392 | struct android_app* android_app = (struct android_app*)activity->instance; 393 | void* savedState = NULL; 394 | 395 | LOGV("SaveInstanceState: %p\n", activity); 396 | pthread_mutex_lock(&android_app->mutex); 397 | android_app->stateSaved = 0; 398 | android_app_write_cmd(android_app, APP_CMD_SAVE_STATE); 399 | while (!android_app->stateSaved) { 400 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 401 | } 402 | 403 | if (android_app->savedState != NULL) { 404 | savedState = android_app->savedState; 405 | *outLen = android_app->savedStateSize; 406 | android_app->savedState = NULL; 407 | android_app->savedStateSize = 0; 408 | } 409 | 410 | pthread_mutex_unlock(&android_app->mutex); 411 | 412 | return savedState; 413 | } 414 | 415 | static void onPause(ANativeActivity* activity) { 416 | LOGV("Pause: %p\n", activity); 417 | android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_PAUSE); 418 | } 419 | 420 | static void onStop(ANativeActivity* activity) { 421 | LOGV("Stop: %p\n", activity); 422 | android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_STOP); 423 | } 424 | 425 | static void onConfigurationChanged(ANativeActivity* activity) { 426 | struct android_app* android_app = (struct android_app*)activity->instance; 427 | LOGV("ConfigurationChanged: %p\n", activity); 428 | android_app_write_cmd(android_app, APP_CMD_CONFIG_CHANGED); 429 | } 430 | 431 | static void onLowMemory(ANativeActivity* activity) { 432 | struct android_app* android_app = (struct android_app*)activity->instance; 433 | LOGV("LowMemory: %p\n", activity); 434 | android_app_write_cmd(android_app, APP_CMD_LOW_MEMORY); 435 | } 436 | 437 | static void onWindowFocusChanged(ANativeActivity* activity, int focused) { 438 | LOGV("WindowFocusChanged: %p -- %d\n", activity, focused); 439 | android_app_write_cmd((struct android_app*)activity->instance, 440 | focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS); 441 | } 442 | 443 | static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) { 444 | LOGV("NativeWindowCreated: %p -- %p\n", activity, window); 445 | android_app_set_window((struct android_app*)activity->instance, window); 446 | } 447 | 448 | static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) { 449 | LOGV("NativeWindowDestroyed: %p -- %p\n", activity, window); 450 | android_app_set_window((struct android_app*)activity->instance, NULL); 451 | } 452 | 453 | static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) { 454 | LOGV("InputQueueCreated: %p -- %p\n", activity, queue); 455 | android_app_set_input((struct android_app*)activity->instance, queue); 456 | } 457 | 458 | static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) { 459 | LOGV("InputQueueDestroyed: %p -- %p\n", activity, queue); 460 | android_app_set_input((struct android_app*)activity->instance, NULL); 461 | } 462 | 463 | JNIEXPORT 464 | void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState, 465 | size_t savedStateSize) { 466 | LOGV("Creating: %p\n", activity); 467 | activity->callbacks->onDestroy = onDestroy; 468 | activity->callbacks->onStart = onStart; 469 | activity->callbacks->onResume = onResume; 470 | activity->callbacks->onSaveInstanceState = onSaveInstanceState; 471 | activity->callbacks->onPause = onPause; 472 | activity->callbacks->onStop = onStop; 473 | activity->callbacks->onConfigurationChanged = onConfigurationChanged; 474 | activity->callbacks->onLowMemory = onLowMemory; 475 | activity->callbacks->onWindowFocusChanged = onWindowFocusChanged; 476 | activity->callbacks->onNativeWindowCreated = onNativeWindowCreated; 477 | activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed; 478 | activity->callbacks->onInputQueueCreated = onInputQueueCreated; 479 | activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed; 480 | 481 | activity->instance = android_app_create(activity, savedState, savedStateSize); 482 | } 483 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | fisiks 7 | 11 | 12 | 13 | 14 | 17 | GitHub 18 | 19 | -------------------------------------------------------------------------------- /nobuild.h: -------------------------------------------------------------------------------- 1 | #ifndef NOBUILD_H_ 2 | #define NOBUILD_H_ 3 | 4 | #ifndef _WIN32 5 | # define _POSIX_C_SOURCE 200809L 6 | # include 7 | # include 8 | # include 9 | # include 10 | # include 11 | # include 12 | # define PATH_SEP "/" 13 | typedef pid_t Pid; 14 | typedef int Fd; 15 | #else 16 | # define WIN32_MEAN_AND_LEAN 17 | # include "windows.h" 18 | # include 19 | # define PATH_SEP "\\" 20 | typedef HANDLE Pid; 21 | typedef HANDLE Fd; 22 | // minirent.h HEADER BEGIN //////////////////////////////////////// 23 | // Copyright 2021 Alexey Kutepov 24 | // 25 | // Permission is hereby granted, free of charge, to any person obtaining 26 | // a copy of this software and associated documentation files (the 27 | // "Software"), to deal in the Software without restriction, including 28 | // without limitation the rights to use, copy, modify, merge, publish, 29 | // distribute, sublicense, and/or sell copies of the Software, and to 30 | // permit persons to whom the Software is furnished to do so, subject to 31 | // the following conditions: 32 | // 33 | // The above copyright notice and this permission notice shall be 34 | // included in all copies or substantial portions of the Software. 35 | // 36 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 37 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 38 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 39 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 40 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 41 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 42 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 43 | // 44 | // ============================================================ 45 | // 46 | // minirent — 0.0.1 — A subset of dirent interface for Windows. 47 | // 48 | // https://github.com/tsoding/minirent 49 | // 50 | // ============================================================ 51 | // 52 | // ChangeLog (https://semver.org/ is implied) 53 | // 54 | // 0.0.1 First Official Release 55 | 56 | #ifndef MINIRENT_H_ 57 | #define MINIRENT_H_ 58 | 59 | #define WIN32_LEAN_AND_MEAN 60 | #include "windows.h" 61 | 62 | struct dirent { 63 | char d_name[MAX_PATH+1]; 64 | }; 65 | 66 | typedef struct DIR DIR; 67 | 68 | DIR *opendir(const char *dirpath); 69 | struct dirent *readdir(DIR *dirp); 70 | int closedir(DIR *dirp); 71 | 72 | #endif // MINIRENT_H_ 73 | // minirent.h HEADER END //////////////////////////////////////// 74 | 75 | // TODO(#28): use GetLastErrorAsString everywhere on Windows error reporting 76 | LPSTR GetLastErrorAsString(void); 77 | 78 | #endif // _WIN32 79 | 80 | #include 81 | #include 82 | #include 83 | #include 84 | #include 85 | #include 86 | 87 | #define FOREACH_ARRAY(type, elem, array, body) \ 88 | for (size_t elem_##index = 0; \ 89 | elem_##index < array.count; \ 90 | ++elem_##index) \ 91 | { \ 92 | type *elem = &array.elems[elem_##index]; \ 93 | body; \ 94 | } 95 | 96 | typedef const char * Cstr; 97 | 98 | int cstr_ends_with(Cstr cstr, Cstr postfix); 99 | #define ENDS_WITH(cstr, postfix) cstr_ends_with(cstr, postfix) 100 | 101 | Cstr cstr_no_ext(Cstr path); 102 | #define NOEXT(path) cstr_no_ext(path) 103 | 104 | typedef struct { 105 | Cstr *elems; 106 | size_t count; 107 | } Cstr_Array; 108 | 109 | Cstr_Array cstr_array_make(Cstr first, ...); 110 | Cstr_Array cstr_array_append(Cstr_Array cstrs, Cstr cstr); 111 | Cstr cstr_array_join(Cstr sep, Cstr_Array cstrs); 112 | 113 | #define JOIN(sep, ...) cstr_array_join(sep, cstr_array_make(__VA_ARGS__, NULL)) 114 | #define CONCAT(...) JOIN("", __VA_ARGS__) 115 | #define PATH(...) JOIN(PATH_SEP, __VA_ARGS__) 116 | 117 | typedef struct { 118 | Fd read; 119 | Fd write; 120 | } Pipe; 121 | 122 | Pipe pipe_make(void); 123 | 124 | typedef struct { 125 | Cstr_Array line; 126 | } Cmd; 127 | 128 | Fd fd_open_for_read(Cstr path); 129 | Fd fd_open_for_write(Cstr path); 130 | void fd_close(Fd fd); 131 | void pid_wait(Pid pid); 132 | Cstr cmd_show(Cmd cmd); 133 | Pid cmd_run_async(Cmd cmd, Fd *fdin, Fd *fdout); 134 | void cmd_run_sync(Cmd cmd); 135 | 136 | typedef struct { 137 | Cmd *elems; 138 | size_t count; 139 | } Cmd_Array; 140 | 141 | // TODO(#1): no way to disable echo in nobuild scripts 142 | // TODO(#2): no way to ignore fails 143 | #define CMD(...) \ 144 | do { \ 145 | Cmd cmd = { \ 146 | .line = cstr_array_make(__VA_ARGS__, NULL) \ 147 | }; \ 148 | INFO("CMD: %s", cmd_show(cmd)); \ 149 | cmd_run_sync(cmd); \ 150 | } while (0) 151 | 152 | typedef enum { 153 | CHAIN_TOKEN_END = 0, 154 | CHAIN_TOKEN_IN, 155 | CHAIN_TOKEN_OUT, 156 | CHAIN_TOKEN_CMD 157 | } Chain_Token_Type; 158 | 159 | // A single token for the CHAIN(...) DSL syntax 160 | typedef struct { 161 | Chain_Token_Type type; 162 | Cstr_Array args; 163 | } Chain_Token; 164 | 165 | // TODO(#17): IN and OUT are already taken by WinAPI 166 | #define IN(path) \ 167 | (Chain_Token) { \ 168 | .type = CHAIN_TOKEN_IN, \ 169 | .args = cstr_array_make(path, NULL) \ 170 | } 171 | 172 | #define OUT(path) \ 173 | (Chain_Token) { \ 174 | .type = CHAIN_TOKEN_OUT, \ 175 | .args = cstr_array_make(path, NULL) \ 176 | } 177 | 178 | #define CHAIN_CMD(...) \ 179 | (Chain_Token) { \ 180 | .type = CHAIN_TOKEN_CMD, \ 181 | .args = cstr_array_make(__VA_ARGS__, NULL) \ 182 | } 183 | 184 | // TODO(#20): pipes do not allow redirecting stderr 185 | typedef struct { 186 | Cstr input_filepath; 187 | Cmd_Array cmds; 188 | Cstr output_filepath; 189 | } Chain; 190 | 191 | Chain chain_build_from_tokens(Chain_Token first, ...); 192 | void chain_run_sync(Chain chain); 193 | void chain_echo(Chain chain); 194 | 195 | // TODO(#15): PIPE does not report where exactly a syntactic error has happened 196 | #define CHAIN(...) \ 197 | do { \ 198 | Chain chain = chain_build_from_tokens(__VA_ARGS__, (Chain_Token) {0}); \ 199 | chain_echo(chain); \ 200 | chain_run_sync(chain); \ 201 | } while(0) 202 | 203 | #ifndef REBUILD_URSELF 204 | # if _WIN32 205 | # if defined(__GNUC__) 206 | # define REBUILD_URSELF(binary_path, source_path) CMD("gcc", "-o", binary_path, source_path) 207 | # elif defined(__clang__) 208 | # define REBUILD_URSELF(binary_path, source_path) CMD("clang", "-o", binary_path, source_path) 209 | # elif defined(_MSC_VER) 210 | # define REBUILD_URSELF(binary_path, source_path) CMD("cl.exe", source_path) 211 | # endif 212 | # else 213 | # define REBUILD_URSELF(binary_path, source_path) CMD("cc", "-o", binary_path, source_path) 214 | # endif 215 | #endif 216 | 217 | // Go Rebuild Urself™ Technology 218 | // 219 | // How to use it: 220 | // int main(int argc, char** argv) { 221 | // GO_REBUILD_URSELF(argc, argv); 222 | // // actual work 223 | // return 0; 224 | // } 225 | // 226 | // After your added this macro every time you run ./nobuild it will detect 227 | // that you modified its original source code and will try to rebuild itself 228 | // before doing any actual work. So you only need to bootstrap your build system 229 | // once. 230 | // 231 | // The modification is detected by comparing the last modified times of the executable 232 | // and its source code. The same way the make utility usually does it. 233 | // 234 | // The rebuilding is done by using the REBUILD_URSELF macro which you can redefine 235 | // if you need a special way of bootstraping your build system. (which I personally 236 | // do not recommend since the whole idea of nobuild is to keep the process of bootstrapping 237 | // as simple as possible and doing all of the actual work inside of the nobuild) 238 | // 239 | #define GO_REBUILD_URSELF(argc, argv) \ 240 | do { \ 241 | const char *source_path = __FILE__; \ 242 | assert(argc >= 1); \ 243 | const char *binary_path = argv[0]; \ 244 | \ 245 | if (is_path1_modified_after_path2(source_path, binary_path)) { \ 246 | RENAME(binary_path, CONCAT(binary_path, ".old")); \ 247 | REBUILD_URSELF(binary_path, source_path); \ 248 | Cmd cmd = { \ 249 | .line = { \ 250 | .elems = (Cstr*) argv, \ 251 | .count = argc, \ 252 | }, \ 253 | }; \ 254 | INFO("CMD: %s", cmd_show(cmd)); \ 255 | cmd_run_sync(cmd); \ 256 | exit(0); \ 257 | } \ 258 | } while(0) 259 | // The implementation idea is stolen from https://github.com/zhiayang/nabs 260 | 261 | void rebuild_urself(const char *binary_path, const char *source_path); 262 | 263 | int path_is_dir(Cstr path); 264 | #define IS_DIR(path) path_is_dir(path) 265 | 266 | int path_exists(Cstr path); 267 | #define PATH_EXISTS(path) path_exists(path) 268 | 269 | void path_mkdirs(Cstr_Array path); 270 | #define MKDIRS(...) \ 271 | do { \ 272 | Cstr_Array path = cstr_array_make(__VA_ARGS__, NULL); \ 273 | INFO("MKDIRS: %s", cstr_array_join(PATH_SEP, path)); \ 274 | path_mkdirs(path); \ 275 | } while (0) 276 | 277 | void path_rename(Cstr old_path, Cstr new_path); 278 | #define RENAME(old_path, new_path) \ 279 | do { \ 280 | INFO("RENAME: %s -> %s", old_path, new_path); \ 281 | path_rename(old_path, new_path); \ 282 | } while (0) 283 | 284 | void path_rm(Cstr path); 285 | #define RM(path) \ 286 | do { \ 287 | INFO("RM: %s", path); \ 288 | path_rm(path); \ 289 | } while(0) 290 | 291 | #define FOREACH_FILE_IN_DIR(file, dirpath, body) \ 292 | do { \ 293 | struct dirent *dp = NULL; \ 294 | DIR *dir = opendir(dirpath); \ 295 | if (dir == NULL) { \ 296 | PANIC("could not open directory %s: %s", \ 297 | dirpath, strerror(errno)); \ 298 | } \ 299 | errno = 0; \ 300 | while ((dp = readdir(dir))) { \ 301 | const char *file = dp->d_name; \ 302 | body; \ 303 | } \ 304 | \ 305 | if (errno > 0) { \ 306 | PANIC("could not read directory %s: %s", \ 307 | dirpath, strerror(errno)); \ 308 | } \ 309 | \ 310 | closedir(dir); \ 311 | } while(0) 312 | 313 | #if defined(__GNUC__) || defined(__clang__) 314 | // https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Function-Attributes.html 315 | #define NOBUILD_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) __attribute__ ((format (printf, STRING_INDEX, FIRST_TO_CHECK))) 316 | #else 317 | #define NOBUILD_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) 318 | #endif 319 | 320 | void VLOG(FILE *stream, Cstr tag, Cstr fmt, va_list args); 321 | void INFO(Cstr fmt, ...) NOBUILD_PRINTF_FORMAT(1, 2); 322 | void WARN(Cstr fmt, ...) NOBUILD_PRINTF_FORMAT(1, 2); 323 | void ERRO(Cstr fmt, ...) NOBUILD_PRINTF_FORMAT(1, 2); 324 | void PANIC(Cstr fmt, ...) NOBUILD_PRINTF_FORMAT(1, 2); 325 | 326 | char *shift_args(int *argc, char ***argv); 327 | 328 | #endif // NOBUILD_H_ 329 | 330 | //////////////////////////////////////////////////////////////////////////////// 331 | 332 | #ifdef NOBUILD_IMPLEMENTATION 333 | 334 | #ifdef _WIN32 335 | LPSTR GetLastErrorAsString(void) 336 | { 337 | // https://stackoverflow.com/questions/1387064/how-to-get-the-error-message-from-the-error-code-returned-by-getlasterror 338 | 339 | DWORD errorMessageId = GetLastError(); 340 | assert(errorMessageId != 0); 341 | 342 | LPSTR messageBuffer = NULL; 343 | 344 | DWORD size = 345 | FormatMessage( 346 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, // DWORD dwFlags, 347 | NULL, // LPCVOID lpSource, 348 | errorMessageId, // DWORD dwMessageId, 349 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // DWORD dwLanguageId, 350 | (LPSTR) &messageBuffer, // LPTSTR lpBuffer, 351 | 0, // DWORD nSize, 352 | NULL // va_list *Arguments 353 | ); 354 | 355 | return messageBuffer; 356 | } 357 | 358 | // minirent.h IMPLEMENTATION BEGIN //////////////////////////////////////// 359 | struct DIR { 360 | HANDLE hFind; 361 | WIN32_FIND_DATA data; 362 | struct dirent *dirent; 363 | }; 364 | 365 | DIR *opendir(const char *dirpath) 366 | { 367 | assert(dirpath); 368 | 369 | char buffer[MAX_PATH]; 370 | snprintf(buffer, MAX_PATH, "%s\\*", dirpath); 371 | 372 | DIR *dir = (DIR*)calloc(1, sizeof(DIR)); 373 | 374 | dir->hFind = FindFirstFile(buffer, &dir->data); 375 | if (dir->hFind == INVALID_HANDLE_VALUE) { 376 | errno = ENOSYS; 377 | goto fail; 378 | } 379 | 380 | return dir; 381 | 382 | fail: 383 | if (dir) { 384 | free(dir); 385 | } 386 | 387 | return NULL; 388 | } 389 | 390 | struct dirent *readdir(DIR *dirp) 391 | { 392 | assert(dirp); 393 | 394 | if (dirp->dirent == NULL) { 395 | dirp->dirent = (struct dirent*)calloc(1, sizeof(struct dirent)); 396 | } else { 397 | if(!FindNextFile(dirp->hFind, &dirp->data)) { 398 | if (GetLastError() != ERROR_NO_MORE_FILES) { 399 | errno = ENOSYS; 400 | } 401 | 402 | return NULL; 403 | } 404 | } 405 | 406 | memset(dirp->dirent->d_name, 0, sizeof(dirp->dirent->d_name)); 407 | 408 | strncpy( 409 | dirp->dirent->d_name, 410 | dirp->data.cFileName, 411 | sizeof(dirp->dirent->d_name) - 1); 412 | 413 | return dirp->dirent; 414 | } 415 | 416 | int closedir(DIR *dirp) 417 | { 418 | assert(dirp); 419 | 420 | if(!FindClose(dirp->hFind)) { 421 | errno = ENOSYS; 422 | return -1; 423 | } 424 | 425 | if (dirp->dirent) { 426 | free(dirp->dirent); 427 | } 428 | free(dirp); 429 | 430 | return 0; 431 | } 432 | // minirent.h IMPLEMENTATION END //////////////////////////////////////// 433 | #endif // _WIN32 434 | 435 | Cstr_Array cstr_array_append(Cstr_Array cstrs, Cstr cstr) 436 | { 437 | Cstr_Array result = { 438 | .count = cstrs.count + 1 439 | }; 440 | result.elems = malloc(sizeof(result.elems[0]) * result.count); 441 | memcpy(result.elems, cstrs.elems, cstrs.count * sizeof(result.elems[0])); 442 | result.elems[cstrs.count] = cstr; 443 | return result; 444 | } 445 | 446 | int cstr_ends_with(Cstr cstr, Cstr postfix) 447 | { 448 | const size_t cstr_len = strlen(cstr); 449 | const size_t postfix_len = strlen(postfix); 450 | return postfix_len <= cstr_len 451 | && strcmp(cstr + cstr_len - postfix_len, postfix) == 0; 452 | } 453 | 454 | Cstr cstr_no_ext(Cstr path) 455 | { 456 | size_t n = strlen(path); 457 | while (n > 0 && path[n - 1] != '.') { 458 | n -= 1; 459 | } 460 | 461 | if (n > 0) { 462 | char *result = malloc(n); 463 | memcpy(result, path, n); 464 | result[n - 1] = '\0'; 465 | 466 | return result; 467 | } else { 468 | return path; 469 | } 470 | } 471 | 472 | Cstr_Array cstr_array_make(Cstr first, ...) 473 | { 474 | Cstr_Array result = {0}; 475 | 476 | if (first == NULL) { 477 | return result; 478 | } 479 | 480 | result.count += 1; 481 | 482 | va_list args; 483 | va_start(args, first); 484 | for (Cstr next = va_arg(args, Cstr); 485 | next != NULL; 486 | next = va_arg(args, Cstr)) { 487 | result.count += 1; 488 | } 489 | va_end(args); 490 | 491 | result.elems = malloc(sizeof(result.elems[0]) * result.count); 492 | if (result.elems == NULL) { 493 | PANIC("could not allocate memory: %s", strerror(errno)); 494 | } 495 | result.count = 0; 496 | 497 | result.elems[result.count++] = first; 498 | 499 | va_start(args, first); 500 | for (Cstr next = va_arg(args, Cstr); 501 | next != NULL; 502 | next = va_arg(args, Cstr)) { 503 | result.elems[result.count++] = next; 504 | } 505 | va_end(args); 506 | 507 | return result; 508 | } 509 | 510 | Cstr cstr_array_join(Cstr sep, Cstr_Array cstrs) 511 | { 512 | if (cstrs.count == 0) { 513 | return ""; 514 | } 515 | 516 | const size_t sep_len = strlen(sep); 517 | size_t len = 0; 518 | for (size_t i = 0; i < cstrs.count; ++i) { 519 | len += strlen(cstrs.elems[i]); 520 | } 521 | 522 | const size_t result_len = (cstrs.count - 1) * sep_len + len + 1; 523 | char *result = malloc(sizeof(char) * result_len); 524 | if (result == NULL) { 525 | PANIC("could not allocate memory: %s", strerror(errno)); 526 | } 527 | 528 | len = 0; 529 | for (size_t i = 0; i < cstrs.count; ++i) { 530 | if (i > 0) { 531 | memcpy(result + len, sep, sep_len); 532 | len += sep_len; 533 | } 534 | 535 | size_t elem_len = strlen(cstrs.elems[i]); 536 | memcpy(result + len, cstrs.elems[i], elem_len); 537 | len += elem_len; 538 | } 539 | result[len] = '\0'; 540 | 541 | return result; 542 | } 543 | 544 | Pipe pipe_make(void) 545 | { 546 | Pipe pip = {0}; 547 | 548 | #ifdef _WIN32 549 | // https://docs.microsoft.com/en-us/windows/win32/ProcThread/creating-a-child-process-with-redirected-input-and-output 550 | 551 | SECURITY_ATTRIBUTES saAttr = {0}; 552 | saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 553 | saAttr.bInheritHandle = TRUE; 554 | 555 | if (!CreatePipe(&pip.read, &pip.write, &saAttr, 0)) { 556 | PANIC("Could not create pipe: %s", GetLastErrorAsString()); 557 | } 558 | #else 559 | Fd pipefd[2]; 560 | if (pipe(pipefd) < 0) { 561 | PANIC("Could not create pipe: %s", strerror(errno)); 562 | } 563 | 564 | pip.read = pipefd[0]; 565 | pip.write = pipefd[1]; 566 | #endif // _WIN32 567 | 568 | return pip; 569 | } 570 | 571 | Fd fd_open_for_read(Cstr path) 572 | { 573 | #ifndef _WIN32 574 | Fd result = open(path, O_RDONLY); 575 | if (result < 0) { 576 | PANIC("Could not open file %s: %s", path, strerror(errno)); 577 | } 578 | return result; 579 | #else 580 | // https://docs.microsoft.com/en-us/windows/win32/fileio/opening-a-file-for-reading-or-writing 581 | SECURITY_ATTRIBUTES saAttr = {0}; 582 | saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 583 | saAttr.bInheritHandle = TRUE; 584 | 585 | Fd result = CreateFile( 586 | path, 587 | GENERIC_READ, 588 | 0, 589 | &saAttr, 590 | OPEN_EXISTING, 591 | FILE_ATTRIBUTE_READONLY, 592 | NULL); 593 | 594 | if (result == INVALID_HANDLE_VALUE) { 595 | PANIC("Could not open file %s", path); 596 | } 597 | 598 | return result; 599 | #endif // _WIN32 600 | } 601 | 602 | Fd fd_open_for_write(Cstr path) 603 | { 604 | #ifndef _WIN32 605 | Fd result = open(path, 606 | O_WRONLY | O_CREAT | O_TRUNC, 607 | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 608 | if (result < 0) { 609 | PANIC("could not open file %s: %s", path, strerror(errno)); 610 | } 611 | return result; 612 | #else 613 | SECURITY_ATTRIBUTES saAttr = {0}; 614 | saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 615 | saAttr.bInheritHandle = TRUE; 616 | 617 | Fd result = CreateFile( 618 | path, // name of the write 619 | GENERIC_WRITE, // open for writing 620 | 0, // do not share 621 | &saAttr, // default security 622 | CREATE_NEW, // create new file only 623 | FILE_ATTRIBUTE_NORMAL, // normal file 624 | NULL // no attr. template 625 | ); 626 | 627 | if (result == INVALID_HANDLE_VALUE) { 628 | PANIC("Could not open file %s: %s", path, GetLastErrorAsString()); 629 | } 630 | 631 | return result; 632 | #endif // _WIN32 633 | } 634 | 635 | void fd_close(Fd fd) 636 | { 637 | #ifdef _WIN32 638 | CloseHandle(fd); 639 | #else 640 | close(fd); 641 | #endif // _WIN32 642 | } 643 | 644 | void pid_wait(Pid pid) 645 | { 646 | #ifdef _WIN32 647 | DWORD result = WaitForSingleObject( 648 | pid, // HANDLE hHandle, 649 | INFINITE // DWORD dwMilliseconds 650 | ); 651 | 652 | if (result == WAIT_FAILED) { 653 | PANIC("could not wait on child process: %s", GetLastErrorAsString()); 654 | } 655 | 656 | DWORD exit_status; 657 | if (GetExitCodeProcess(pid, &exit_status) == 0) { 658 | PANIC("could not get process exit code: %lu", GetLastError()); 659 | } 660 | 661 | if (exit_status != 0) { 662 | PANIC("command exited with exit code %lu", exit_status); 663 | } 664 | 665 | CloseHandle(pid); 666 | #else 667 | for (;;) { 668 | int wstatus = 0; 669 | if (waitpid(pid, &wstatus, 0) < 0) { 670 | PANIC("could not wait on command (pid %d): %s", pid, strerror(errno)); 671 | } 672 | 673 | if (WIFEXITED(wstatus)) { 674 | int exit_status = WEXITSTATUS(wstatus); 675 | if (exit_status != 0) { 676 | PANIC("command exited with exit code %d", exit_status); 677 | } 678 | 679 | break; 680 | } 681 | 682 | if (WIFSIGNALED(wstatus)) { 683 | PANIC("command process was terminated by %s", strsignal(WTERMSIG(wstatus))); 684 | } 685 | } 686 | 687 | #endif // _WIN32 688 | } 689 | 690 | Cstr cmd_show(Cmd cmd) 691 | { 692 | // TODO(#31): cmd_show does not render the command line properly 693 | // - No string literals when arguments contains space 694 | // - No escaping of special characters 695 | // - Etc. 696 | return cstr_array_join(" ", cmd.line); 697 | } 698 | 699 | Pid cmd_run_async(Cmd cmd, Fd *fdin, Fd *fdout) 700 | { 701 | #ifdef _WIN32 702 | // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output 703 | 704 | STARTUPINFO siStartInfo; 705 | ZeroMemory(&siStartInfo, sizeof(siStartInfo)); 706 | siStartInfo.cb = sizeof(STARTUPINFO); 707 | // NOTE: theoretically setting NULL to std handles should not be a problem 708 | // https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior 709 | siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); 710 | // TODO(#32): check for errors in GetStdHandle 711 | siStartInfo.hStdOutput = fdout ? *fdout : GetStdHandle(STD_OUTPUT_HANDLE); 712 | siStartInfo.hStdInput = fdin ? *fdin : GetStdHandle(STD_INPUT_HANDLE); 713 | siStartInfo.dwFlags |= STARTF_USESTDHANDLES; 714 | 715 | PROCESS_INFORMATION piProcInfo; 716 | ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); 717 | 718 | BOOL bSuccess = 719 | CreateProcess( 720 | NULL, 721 | // TODO(#33): cmd_run_async on Windows does not render command line properly 722 | // It may require wrapping some arguments with double-quotes if they contains spaces, etc. 723 | cstr_array_join(" ", cmd.line), 724 | NULL, 725 | NULL, 726 | TRUE, 727 | 0, 728 | NULL, 729 | NULL, 730 | &siStartInfo, 731 | &piProcInfo 732 | ); 733 | 734 | if (!bSuccess) { 735 | PANIC("Could not create child process %s: %s\n", 736 | cmd_show(cmd), GetLastErrorAsString()); 737 | } 738 | 739 | CloseHandle(piProcInfo.hThread); 740 | 741 | return piProcInfo.hProcess; 742 | #else 743 | pid_t cpid = fork(); 744 | if (cpid < 0) { 745 | PANIC("Could not fork child process: %s: %s", 746 | cmd_show(cmd), strerror(errno)); 747 | } 748 | 749 | if (cpid == 0) { 750 | Cstr_Array args = cstr_array_append(cmd.line, NULL); 751 | 752 | if (fdin) { 753 | if (dup2(*fdin, STDIN_FILENO) < 0) { 754 | PANIC("Could not setup stdin for child process: %s", strerror(errno)); 755 | } 756 | } 757 | 758 | if (fdout) { 759 | if (dup2(*fdout, STDOUT_FILENO) < 0) { 760 | PANIC("Could not setup stdout for child process: %s", strerror(errno)); 761 | } 762 | } 763 | 764 | if (execvp(args.elems[0], (char * const*) args.elems) < 0) { 765 | PANIC("Could not exec child process: %s: %s", 766 | cmd_show(cmd), strerror(errno)); 767 | } 768 | } 769 | 770 | return cpid; 771 | #endif // _WIN32 772 | } 773 | 774 | void cmd_run_sync(Cmd cmd) 775 | { 776 | pid_wait(cmd_run_async(cmd, NULL, NULL)); 777 | } 778 | 779 | static void chain_set_input_output_files_or_count_cmds(Chain *chain, Chain_Token token) 780 | { 781 | switch (token.type) { 782 | case CHAIN_TOKEN_CMD: { 783 | chain->cmds.count += 1; 784 | } 785 | break; 786 | 787 | case CHAIN_TOKEN_IN: { 788 | if (chain->input_filepath) { 789 | PANIC("Input file path was already set"); 790 | } 791 | 792 | chain->input_filepath = token.args.elems[0]; 793 | } 794 | break; 795 | 796 | case CHAIN_TOKEN_OUT: { 797 | if (chain->output_filepath) { 798 | PANIC("Output file path was already set"); 799 | } 800 | 801 | chain->output_filepath = token.args.elems[0]; 802 | } 803 | break; 804 | 805 | case CHAIN_TOKEN_END: 806 | default: { 807 | assert(0 && "unreachable"); 808 | exit(1); 809 | } 810 | } 811 | } 812 | 813 | static void chain_push_cmd(Chain *chain, Chain_Token token) 814 | { 815 | if (token.type == CHAIN_TOKEN_CMD) { 816 | chain->cmds.elems[chain->cmds.count++] = (Cmd) { 817 | .line = token.args 818 | }; 819 | } 820 | } 821 | 822 | Chain chain_build_from_tokens(Chain_Token first, ...) 823 | { 824 | Chain result = {0}; 825 | 826 | chain_set_input_output_files_or_count_cmds(&result, first); 827 | va_list args; 828 | va_start(args, first); 829 | Chain_Token next = va_arg(args, Chain_Token); 830 | while (next.type != CHAIN_TOKEN_END) { 831 | chain_set_input_output_files_or_count_cmds(&result, next); 832 | next = va_arg(args, Chain_Token); 833 | } 834 | va_end(args); 835 | 836 | result.cmds.elems = malloc(sizeof(result.cmds.elems[0]) * result.cmds.count); 837 | if (result.cmds.elems == NULL) { 838 | PANIC("could not allocate memory: %s", strerror(errno)); 839 | } 840 | result.cmds.count = 0; 841 | 842 | chain_push_cmd(&result, first); 843 | 844 | va_start(args, first); 845 | next = va_arg(args, Chain_Token); 846 | while (next.type != CHAIN_TOKEN_END) { 847 | chain_push_cmd(&result, next); 848 | next = va_arg(args, Chain_Token); 849 | } 850 | va_end(args); 851 | 852 | return result; 853 | } 854 | 855 | void chain_run_sync(Chain chain) 856 | { 857 | if (chain.cmds.count == 0) { 858 | return; 859 | } 860 | 861 | Pid *cpids = malloc(sizeof(Pid) * chain.cmds.count); 862 | 863 | Pipe pip = {0}; 864 | Fd fdin = 0; 865 | Fd *fdprev = NULL; 866 | 867 | if (chain.input_filepath) { 868 | fdin = fd_open_for_read(chain.input_filepath); 869 | if (fdin < 0) { 870 | PANIC("could not open file %s: %s", chain.input_filepath, strerror(errno)); 871 | } 872 | fdprev = &fdin; 873 | } 874 | 875 | for (size_t i = 0; i < chain.cmds.count - 1; ++i) { 876 | pip = pipe_make(); 877 | 878 | cpids[i] = cmd_run_async( 879 | chain.cmds.elems[i], 880 | fdprev, 881 | &pip.write); 882 | 883 | if (fdprev) fd_close(*fdprev); 884 | fd_close(pip.write); 885 | fdprev = &fdin; 886 | fdin = pip.read; 887 | } 888 | 889 | { 890 | Fd fdout = 0; 891 | Fd *fdnext = NULL; 892 | 893 | if (chain.output_filepath) { 894 | fdout = fd_open_for_write(chain.output_filepath); 895 | if (fdout < 0) { 896 | PANIC("could not open file %s: %s", 897 | chain.output_filepath, 898 | strerror(errno)); 899 | } 900 | fdnext = &fdout; 901 | } 902 | 903 | const size_t last = chain.cmds.count - 1; 904 | cpids[last] = 905 | cmd_run_async( 906 | chain.cmds.elems[last], 907 | fdprev, 908 | fdnext); 909 | 910 | if (fdprev) fd_close(*fdprev); 911 | if (fdnext) fd_close(*fdnext); 912 | } 913 | 914 | for (size_t i = 0; i < chain.cmds.count; ++i) { 915 | pid_wait(cpids[i]); 916 | } 917 | } 918 | 919 | void chain_echo(Chain chain) 920 | { 921 | printf("[INFO] CHAIN:"); 922 | if (chain.input_filepath) { 923 | printf(" %s", chain.input_filepath); 924 | } 925 | 926 | FOREACH_ARRAY(Cmd, cmd, chain.cmds, { 927 | printf(" |> %s", cmd_show(*cmd)); 928 | }); 929 | 930 | if (chain.output_filepath) { 931 | printf(" |> %s", chain.output_filepath); 932 | } 933 | 934 | printf("\n"); 935 | } 936 | 937 | int path_exists(Cstr path) 938 | { 939 | #ifdef _WIN32 940 | DWORD dwAttrib = GetFileAttributes(path); 941 | return (dwAttrib != INVALID_FILE_ATTRIBUTES); 942 | #else 943 | struct stat statbuf = {0}; 944 | if (stat(path, &statbuf) < 0) { 945 | if (errno == ENOENT) { 946 | errno = 0; 947 | return 0; 948 | } 949 | 950 | PANIC("could not retrieve information about file %s: %s", 951 | path, strerror(errno)); 952 | } 953 | 954 | return 1; 955 | #endif 956 | } 957 | 958 | int path_is_dir(Cstr path) 959 | { 960 | #ifdef _WIN32 961 | DWORD dwAttrib = GetFileAttributes(path); 962 | 963 | return (dwAttrib != INVALID_FILE_ATTRIBUTES && 964 | (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); 965 | #else 966 | struct stat statbuf = {0}; 967 | if (stat(path, &statbuf) < 0) { 968 | if (errno == ENOENT) { 969 | errno = 0; 970 | return 0; 971 | } 972 | 973 | PANIC("could not retrieve information about file %s: %s", 974 | path, strerror(errno)); 975 | } 976 | 977 | return S_ISDIR(statbuf.st_mode); 978 | #endif // _WIN32 979 | } 980 | 981 | void path_rename(const char *old_path, const char *new_path) 982 | { 983 | #ifdef _WIN32 984 | if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) { 985 | PANIC("could not rename %s to %s: %s", old_path, new_path, 986 | GetLastErrorAsString()); 987 | } 988 | #else 989 | if (rename(old_path, new_path) < 0) { 990 | PANIC("could not rename %s to %s: %s", old_path, new_path, 991 | strerror(errno)); 992 | } 993 | #endif // _WIN32 994 | } 995 | 996 | void path_mkdirs(Cstr_Array path) 997 | { 998 | if (path.count == 0) { 999 | return; 1000 | } 1001 | 1002 | size_t len = 0; 1003 | for (size_t i = 0; i < path.count; ++i) { 1004 | len += strlen(path.elems[i]); 1005 | } 1006 | 1007 | size_t seps_count = path.count - 1; 1008 | const size_t sep_len = strlen(PATH_SEP); 1009 | 1010 | char *result = malloc(len + seps_count * sep_len + 1); 1011 | 1012 | len = 0; 1013 | for (size_t i = 0; i < path.count; ++i) { 1014 | size_t n = strlen(path.elems[i]); 1015 | memcpy(result + len, path.elems[i], n); 1016 | len += n; 1017 | 1018 | if (seps_count > 0) { 1019 | memcpy(result + len, PATH_SEP, sep_len); 1020 | len += sep_len; 1021 | seps_count -= 1; 1022 | } 1023 | 1024 | result[len] = '\0'; 1025 | 1026 | if (mkdir(result, 0755) < 0) { 1027 | if (errno == EEXIST) { 1028 | errno = 0; 1029 | WARN("directory %s already exists", result); 1030 | } else { 1031 | PANIC("could not create directory %s: %s", result, strerror(errno)); 1032 | } 1033 | } 1034 | } 1035 | } 1036 | 1037 | void path_rm(Cstr path) 1038 | { 1039 | if (IS_DIR(path)) { 1040 | FOREACH_FILE_IN_DIR(file, path, { 1041 | if (strcmp(file, ".") != 0 && strcmp(file, "..") != 0) 1042 | { 1043 | path_rm(PATH(path, file)); 1044 | } 1045 | }); 1046 | 1047 | if (rmdir(path) < 0) { 1048 | if (errno == ENOENT) { 1049 | errno = 0; 1050 | WARN("directory %s does not exist", path); 1051 | } else { 1052 | PANIC("could not remove directory %s: %s", path, strerror(errno)); 1053 | } 1054 | } 1055 | } else { 1056 | if (unlink(path) < 0) { 1057 | if (errno == ENOENT) { 1058 | errno = 0; 1059 | WARN("file %s does not exist", path); 1060 | } else { 1061 | PANIC("could not remove file %s: %s", path, strerror(errno)); 1062 | } 1063 | } 1064 | } 1065 | } 1066 | 1067 | int is_path1_modified_after_path2(const char *path1, const char *path2) 1068 | { 1069 | #ifdef _WIN32 1070 | FILETIME path1_time, path2_time; 1071 | 1072 | Fd path1_fd = fd_open_for_read(path1); 1073 | if (!GetFileTime(path1_fd, NULL, NULL, &path1_time)) { 1074 | PANIC("could not get time of %s: %s", path1, GetLastErrorAsString()); 1075 | } 1076 | fd_close(path1_fd); 1077 | 1078 | Fd path2_fd = fd_open_for_read(path2); 1079 | if (!GetFileTime(path2_fd, NULL, NULL, &path2_time)) { 1080 | PANIC("could not get time of %s: %s", path2, GetLastErrorAsString()); 1081 | } 1082 | fd_close(path2_fd); 1083 | 1084 | return CompareFileTime(&path1_time, &path2_time) == 1; 1085 | #else 1086 | struct stat statbuf = {0}; 1087 | 1088 | if (stat(path1, &statbuf) < 0) { 1089 | PANIC("could not stat %s: %s\n", path1, strerror(errno)); 1090 | } 1091 | int path1_time = statbuf.st_mtime; 1092 | 1093 | if (stat(path2, &statbuf) < 0) { 1094 | PANIC("could not stat %s: %s\n", path2, strerror(errno)); 1095 | } 1096 | int path2_time = statbuf.st_mtime; 1097 | 1098 | return path1_time > path2_time; 1099 | #endif 1100 | } 1101 | 1102 | void VLOG(FILE *stream, Cstr tag, Cstr fmt, va_list args) 1103 | { 1104 | fprintf(stream, "[%s] ", tag); 1105 | vfprintf(stream, fmt, args); 1106 | fprintf(stream, "\n"); 1107 | } 1108 | 1109 | void INFO(Cstr fmt, ...) 1110 | { 1111 | va_list args; 1112 | va_start(args, fmt); 1113 | VLOG(stderr, "INFO", fmt, args); 1114 | va_end(args); 1115 | } 1116 | 1117 | void WARN(Cstr fmt, ...) 1118 | { 1119 | va_list args; 1120 | va_start(args, fmt); 1121 | VLOG(stderr, "WARN", fmt, args); 1122 | va_end(args); 1123 | } 1124 | 1125 | void ERRO(Cstr fmt, ...) 1126 | { 1127 | va_list args; 1128 | va_start(args, fmt); 1129 | VLOG(stderr, "ERRO", fmt, args); 1130 | va_end(args); 1131 | } 1132 | 1133 | void PANIC(Cstr fmt, ...) 1134 | { 1135 | va_list args; 1136 | va_start(args, fmt); 1137 | VLOG(stderr, "ERRO", fmt, args); 1138 | va_end(args); 1139 | exit(1); 1140 | } 1141 | 1142 | char *shift_args(int *argc, char ***argv) 1143 | { 1144 | assert(*argc > 0); 1145 | char *result = **argv; 1146 | *argc -= 1; 1147 | *argv += 1; 1148 | return result; 1149 | } 1150 | 1151 | #endif // NOBUILD_IMPLEMENTATION --------------------------------------------------------------------------------