2 ** Copyright (c) 2024 Serge Zaitsev
4 ** Permission is hereby granted, free of charge, to any person obtaining a copy
5 ** of this software and associated documentation files (the "Software"), to
6 ** deal in the Software without restriction, including without limitation the
7 ** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8 ** sell copies of the Software, and to permit persons to whom the Software is
9 ** furnished to do so, subject to the following conditions:
11 ** The above copyright notice and this permission notice shall be included in
12 ** all copies or substantial portions of the Software.
14 ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 ** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26 #if defined(__APPLE__)
27 #include <CoreGraphics/CoreGraphics.h>
28 #include <objc/NSObjCRuntime.h>
29 #include <objc/objc-runtime.h>
33 #define _DEFAULT_SOURCE 1
34 #include <X11/XKBlib.h>
36 #include <X11/keysym.h>
49 int keys
[256]; /* keys are mostly ASCII, but arrows are 17..20 */
50 int mod
; /* mod is 4 bits mask, ctrl=1, shift=2, alt=4, meta=8 */
54 #if defined(__APPLE__)
67 #define FENSTER_API extern
69 FENSTER_API
int fenster_open(struct fenster
*f
);
70 FENSTER_API
int fenster_loop(struct fenster
*f
);
71 FENSTER_API
void fenster_close(struct fenster
*f
);
72 FENSTER_API
void fenster_sleep(int64_t ms
);
73 FENSTER_API
int64_t fenster_time(void);
74 #define fenster_pixel(f, x, y) ((f)->buf[((y) * (f)->width) + (x)])
76 #ifndef FENSTER_HEADER
77 #if defined(__APPLE__)
78 #define msg(r, o, s) ((r(*)(id, SEL))objc_msgSend)(o, sel_getUid(s))
79 #define msg1(r, o, s, A, a) \
80 ((r(*)(id, SEL, A))objc_msgSend)(o, sel_getUid(s), a)
81 #define msg2(r, o, s, A, a, B, b) \
82 ((r(*)(id, SEL, A, B))objc_msgSend)(o, sel_getUid(s), a, b)
83 #define msg3(r, o, s, A, a, B, b, C, c) \
84 ((r(*)(id, SEL, A, B, C))objc_msgSend)(o, sel_getUid(s), a, b, c)
85 #define msg4(r, o, s, A, a, B, b, C, c, D, d) \
86 ((r(*)(id, SEL, A, B, C, D))objc_msgSend)(o, sel_getUid(s), a, b, c, d)
88 #define cls(x) ((id)objc_getClass(x))
90 extern id
const NSDefaultRunLoopMode
;
91 extern id
const NSApp
;
93 static void fenster_draw_rect(id v
, SEL s
, CGRect r
) {
95 struct fenster
*f
= (struct fenster
*)objc_getAssociatedObject(v
, "fenster");
96 CGContextRef context
=
97 msg(CGContextRef
, msg(id
, cls("NSGraphicsContext"), "currentContext"),
99 CGColorSpaceRef space
= CGColorSpaceCreateDeviceRGB();
100 CGDataProviderRef provider
= CGDataProviderCreateWithData(
101 NULL
, f
->buf
, f
->width
* f
->height
* 4, NULL
);
103 CGImageCreate(f
->width
, f
->height
, 8, 32, f
->width
* 4, space
,
104 kCGImageAlphaNoneSkipFirst
| kCGBitmapByteOrder32Little
,
105 provider
, NULL
, false, kCGRenderingIntentDefault
);
106 CGColorSpaceRelease(space
);
107 CGDataProviderRelease(provider
);
108 CGContextDrawImage(context
, CGRectMake(0, 0, f
->width
, f
->height
), img
);
112 static BOOL
fenster_should_close(id v
, SEL s
, id w
) {
113 (void)v
, (void)s
, (void)w
;
114 msg1(void, NSApp
, "terminate:", id
, NSApp
);
118 FENSTER_API
int fenster_open(struct fenster
*f
) {
119 msg(id
, cls("NSApplication"), "sharedApplication");
120 msg1(void, NSApp
, "setActivationPolicy:", NSInteger
, 0);
121 f
->wnd
= msg4(id
, msg(id
, cls("NSWindow"), "alloc"),
122 "initWithContentRect:styleMask:backing:defer:", CGRect
,
123 CGRectMake(0, 0, f
->width
, f
->height
), NSUInteger
, 3,
124 NSUInteger
, 2, BOOL
, NO
);
126 objc_allocateClassPair((Class
)cls("NSObject"), "FensterDelegate", 0);
127 class_addMethod(windelegate
, sel_getUid("windowShouldClose:"),
128 (IMP
)fenster_should_close
, "c@:@");
129 objc_registerClassPair(windelegate
);
130 msg1(void, f
->wnd
, "setDelegate:", id
,
131 msg(id
, msg(id
, (id
)windelegate
, "alloc"), "init"));
132 Class c
= objc_allocateClassPair((Class
)cls("NSView"), "FensterView", 0);
133 class_addMethod(c
, sel_getUid("drawRect:"), (IMP
)fenster_draw_rect
, "i@:@@");
134 objc_registerClassPair(c
);
136 id v
= msg(id
, msg(id
, (id
)c
, "alloc"), "init");
137 msg1(void, f
->wnd
, "setContentView:", id
, v
);
138 objc_setAssociatedObject(v
, "fenster", (id
)f
, OBJC_ASSOCIATION_ASSIGN
);
140 id title
= msg1(id
, cls("NSString"), "stringWithUTF8String:", const char *,
142 msg1(void, f
->wnd
, "setTitle:", id
, title
);
143 msg1(void, f
->wnd
, "makeKeyAndOrderFront:", id
, nil
);
144 msg(void, f
->wnd
, "center");
145 msg1(void, NSApp
, "activateIgnoringOtherApps:", BOOL
, YES
);
149 FENSTER_API
void fenster_close(struct fenster
*f
) {
150 msg(void, f
->wnd
, "close");
154 static const uint8_t FENSTER_KEYCODES
[128] = {65,83,68,70,72,71,90,88,67,86,0,66,81,87,69,82,89,84,49,50,51,52,54,53,61,57,55,45,56,48,93,79,85,91,73,80,10,76,74,39,75,59,92,44,47,78,77,46,9,32,96,8,0,27,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,2,3,127,0,5,0,4,0,20,19,18,17,0};
156 FENSTER_API
int fenster_loop(struct fenster
*f
) {
157 msg1(void, msg(id
, f
->wnd
, "contentView"), "setNeedsDisplay:", BOOL
, YES
);
158 id ev
= msg4(id
, NSApp
,
159 "nextEventMatchingMask:untilDate:inMode:dequeue:", NSUInteger
,
160 NSUIntegerMax
, id
, NULL
, id
, NSDefaultRunLoopMode
, BOOL
, YES
);
163 NSUInteger evtype
= msg(NSUInteger
, ev
, "type");
165 case 1: /* NSEventTypeMouseDown */
168 case 2: /* NSEventTypeMouseUp*/
172 case 6: { /* NSEventTypeMouseMoved */
173 CGPoint xy
= msg(CGPoint
, ev
, "locationInWindow");
175 f
->y
= (int)(f
->height
- xy
.y
);
178 case 10: /*NSEventTypeKeyDown*/
179 case 11: /*NSEventTypeKeyUp:*/ {
180 NSUInteger k
= msg(NSUInteger
, ev
, "keyCode");
181 f
->keys
[k
< 127 ? FENSTER_KEYCODES
[k
] : 0] = evtype
== 10;
182 NSUInteger mod
= msg(NSUInteger
, ev
, "modifierFlags") >> 17;
183 f
->mod
= (mod
& 0xc) | ((mod
& 1) << 1) | ((mod
>> 1) & 1);
187 msg1(void, NSApp
, "sendEvent:", id
, ev
);
190 #elif defined(_WIN32)
192 static const uint8_t FENSTER_KEYCODES
[] = {0,27,49,50,51,52,53,54,55,56,57,48,45,61,8,9,81,87,69,82,84,89,85,73,79,80,91,93,10,0,65,83,68,70,71,72,74,75,76,59,39,96,0,92,90,88,67,86,66,78,77,44,46,47,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,17,3,0,20,0,19,0,5,18,4,26,127};
194 typedef struct BINFO
{
195 BITMAPINFOHEADER bmiHeader
;
196 RGBQUAD bmiColors
[3];
198 static LRESULT CALLBACK
fenster_wndproc(HWND hwnd
, UINT msg
, WPARAM wParam
,
200 struct fenster
*f
= (struct fenster
*)GetWindowLongPtr(hwnd
, GWLP_USERDATA
);
204 HDC hdc
= BeginPaint(hwnd
, &ps
);
205 HDC memdc
= CreateCompatibleDC(hdc
);
206 HBITMAP hbmp
= CreateCompatibleBitmap(hdc
, f
->width
, f
->height
);
207 HBITMAP oldbmp
= SelectObject(memdc
, hbmp
);
208 BINFO bi
= {{sizeof(bi
), f
->width
, -f
->height
, 1, 32, BI_BITFIELDS
}};
209 bi
.bmiColors
[0].rgbRed
= 0xff;
210 bi
.bmiColors
[1].rgbGreen
= 0xff;
211 bi
.bmiColors
[2].rgbBlue
= 0xff;
212 SetDIBitsToDevice(memdc
, 0, 0, f
->width
, f
->height
, 0, 0, 0, f
->height
,
213 f
->buf
, (BITMAPINFO
*)&bi
, DIB_RGB_COLORS
);
214 BitBlt(hdc
, 0, 0, f
->width
, f
->height
, memdc
, 0, 0, SRCCOPY
);
215 SelectObject(memdc
, oldbmp
);
225 f
->mouse
= (msg
== WM_LBUTTONDOWN
);
228 f
->y
= HIWORD(lParam
), f
->x
= LOWORD(lParam
);
232 f
->mod
= ((GetKeyState(VK_CONTROL
) & 0x8000) >> 15) |
233 ((GetKeyState(VK_SHIFT
) & 0x8000) >> 14) |
234 ((GetKeyState(VK_MENU
) & 0x8000) >> 13) |
235 (((GetKeyState(VK_LWIN
) | GetKeyState(VK_RWIN
)) & 0x8000) >> 12);
236 f
->keys
[FENSTER_KEYCODES
[HIWORD(lParam
) & 0x1ff]] = !((lParam
>> 31) & 1);
242 return DefWindowProc(hwnd
, msg
, wParam
, lParam
);
247 FENSTER_API
int fenster_open(struct fenster
*f
) {
248 HINSTANCE hInstance
= GetModuleHandle(NULL
);
250 wc
.cbSize
= sizeof(WNDCLASSEX
);
251 wc
.style
= CS_VREDRAW
| CS_HREDRAW
;
252 wc
.lpfnWndProc
= fenster_wndproc
;
253 wc
.hInstance
= hInstance
;
254 wc
.lpszClassName
= f
->title
;
255 RegisterClassEx(&wc
);
256 f
->hwnd
= CreateWindowEx(WS_EX_CLIENTEDGE
, f
->title
, f
->title
,
257 WS_OVERLAPPEDWINDOW
, CW_USEDEFAULT
, CW_USEDEFAULT
,
258 f
->width
, f
->height
, NULL
, NULL
, hInstance
, NULL
);
262 SetWindowLongPtr(f
->hwnd
, GWLP_USERDATA
, (LONG_PTR
)f
);
263 ShowWindow(f
->hwnd
, SW_NORMAL
);
264 UpdateWindow(f
->hwnd
);
268 FENSTER_API
void fenster_close(struct fenster
*f
) { (void)f
; }
270 FENSTER_API
int fenster_loop(struct fenster
*f
) {
272 while (PeekMessage(&msg
, NULL
, 0, 0, PM_REMOVE
)) {
273 if (msg
.message
== WM_QUIT
)
275 TranslateMessage(&msg
);
276 DispatchMessage(&msg
);
278 InvalidateRect(f
->hwnd
, NULL
, TRUE
);
283 static int FENSTER_KEYCODES
[124] = {XK_BackSpace
,8,XK_Delete
,127,XK_Down
,18,XK_End
,5,XK_Escape
,27,XK_Home
,2,XK_Insert
,26,XK_Left
,20,XK_Page_Down
,4,XK_Page_Up
,3,XK_Return
,10,XK_Right
,19,XK_Tab
,9,XK_Up
,17,XK_apostrophe
,39,XK_backslash
,92,XK_bracketleft
,91,XK_bracketright
,93,XK_comma
,44,XK_equal
,61,XK_grave
,96,XK_minus
,45,XK_period
,46,XK_semicolon
,59,XK_slash
,47,XK_space
,32,XK_a
,65,XK_b
,66,XK_c
,67,XK_d
,68,XK_e
,69,XK_f
,70,XK_g
,71,XK_h
,72,XK_i
,73,XK_j
,74,XK_k
,75,XK_l
,76,XK_m
,77,XK_n
,78,XK_o
,79,XK_p
,80,XK_q
,81,XK_r
,82,XK_s
,83,XK_t
,84,XK_u
,85,XK_v
,86,XK_w
,87,XK_x
,88,XK_y
,89,XK_z
,90,XK_0
,48,XK_1
,49,XK_2
,50,XK_3
,51,XK_4
,52,XK_5
,53,XK_6
,54,XK_7
,55,XK_8
,56,XK_9
,57};
285 FENSTER_API
int fenster_open(struct fenster
*f
) {
286 f
->dpy
= XOpenDisplay(NULL
);
287 int screen
= DefaultScreen(f
->dpy
);
288 f
->w
= XCreateSimpleWindow(f
->dpy
, RootWindow(f
->dpy
, screen
), 0, 0, f
->width
,
289 f
->height
, 0, BlackPixel(f
->dpy
, screen
),
290 WhitePixel(f
->dpy
, screen
));
291 f
->gc
= XCreateGC(f
->dpy
, f
->w
, 0, 0);
292 XSelectInput(f
->dpy
, f
->w
,
293 ExposureMask
| KeyPressMask
| KeyReleaseMask
| ButtonPressMask
|
294 ButtonReleaseMask
| PointerMotionMask
);
295 XStoreName(f
->dpy
, f
->w
, f
->title
);
296 XMapWindow(f
->dpy
, f
->w
);
298 f
->img
= XCreateImage(f
->dpy
, DefaultVisual(f
->dpy
, 0), 24, ZPixmap
, 0,
299 (char *)f
->buf
, f
->width
, f
->height
, 32, 0);
302 FENSTER_API
void fenster_close(struct fenster
*f
) { XCloseDisplay(f
->dpy
); }
303 FENSTER_API
int fenster_loop(struct fenster
*f
) {
305 XPutImage(f
->dpy
, f
->w
, f
->gc
, f
->img
, 0, 0, 0, 0, f
->width
, f
->height
);
307 while (XPending(f
->dpy
)) {
308 XNextEvent(f
->dpy
, &ev
);
312 f
->mouse
= (ev
.type
== ButtonPress
);
315 f
->x
= ev
.xmotion
.x
, f
->y
= ev
.xmotion
.y
;
319 int m
= ev
.xkey
.state
;
320 int k
= XkbKeycodeToKeysym(f
->dpy
, ev
.xkey
.keycode
, 0, 0);
321 for (unsigned int i
= 0; i
< 124; i
+= 2) {
322 if (FENSTER_KEYCODES
[i
] == k
) {
323 f
->keys
[FENSTER_KEYCODES
[i
+ 1]] = (ev
.type
== KeyPress
);
327 f
->mod
= (!!(m
& ControlMask
)) | (!!(m
& ShiftMask
) << 1) |
328 (!!(m
& Mod1Mask
) << 2) | (!!(m
& Mod4Mask
) << 3);
337 FENSTER_API
void fenster_sleep(int64_t ms
) { Sleep(ms
); }
338 FENSTER_API
int64_t fenster_time() {
339 LARGE_INTEGER freq
, count
;
340 QueryPerformanceFrequency(&freq
);
341 QueryPerformanceCounter(&count
);
342 return (int64_t)(count
.QuadPart
* 1000.0 / freq
.QuadPart
);
345 FENSTER_API
void fenster_sleep(int64_t ms
) {
347 ts
.tv_sec
= ms
/ 1000;
348 ts
.tv_nsec
= (ms
% 1000) * 1000000;
349 nanosleep(&ts
, NULL
);
351 FENSTER_API
int64_t fenster_time(void) {
352 struct timespec time
;
353 clock_gettime(CLOCK_REALTIME
, &time
);
354 return time
.tv_sec
* 1000 + (time
.tv_nsec
/ 1000000);
364 Fenster(const int w
, const int h
, const char *title
)
365 : f
{.title
= title
, .width
= w
, .height
= h
} {
366 this->f
.buf
= new uint32_t[w
* h
];
368 this->now
= fenster_time();
369 fenster_open(&this->f
);
372 fenster_close(&this->f
);
373 delete[] this->f
.buf
;
375 void clear() { memset(this->f
.buf
, 0, this->f
.width
* this->f
.height
* 4); }
376 bool loop(const int fps
) {
377 int64_t t
= fenster_time();
378 int64_t paint_time
= t
- this->now
;
379 int64_t frame_budget
= 1000 / fps
;
380 int64_t sleep_time
= frame_budget
- paint_time
;
381 if (sleep_time
> 0) {
382 fenster_sleep(sleep_time
);
385 return fenster_loop(&this->f
) == 0;
387 void paint() { fenster_loop(&this->f
); }
388 inline uint32_t &px(const int x
, const int y
) {
389 return fenster_pixel(&this->f
, x
, y
);
391 bool key(int c
) { return c
>= 0 && c
< 128 ? this->f
.keys
[c
] : false; }
392 int x() { return this->f
.x
; }
393 int y() { return this->f
.y
; }
394 int mouse() { return this->f
.mouse
; }
395 int mod() { return this->f
.mod
; }
396 long width() { return this->f
.width
; }
397 long height() { return this->f
.height
; }
399 #endif /* __cplusplus */
401 #endif /* !FENSTER_HEADER */
402 #endif /* FENSTER_H */