├── 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
--------------------------------------------------------------------------------