summary refs log tree commit diff
path: root/bin/gfx
diff options
context:
space:
mode:
Diffstat (limited to 'bin/gfx')
-rw-r--r--bin/gfx/cocoa.m162
-rw-r--r--bin/gfx/fb.c87
-rw-r--r--bin/gfx/gfx.h24
-rw-r--r--bin/gfx/none.c24
-rw-r--r--bin/gfx/x11.c135
5 files changed, 432 insertions, 0 deletions
diff --git a/bin/gfx/cocoa.m b/bin/gfx/cocoa.m
new file mode 100644
index 00000000..4837a386
--- /dev/null
+++ b/bin/gfx/cocoa.m
@@ -0,0 +1,162 @@
+/* Copyright (c) 2018, Curtis McEnroe <programble@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import <Cocoa/Cocoa.h>
+#import <err.h>
+#import <stdbool.h>
+#import <stdint.h>
+#import <stdlib.h>
+#import <sysexits.h>
+
+#import "gfx.h"
+
+#define UNUSED __attribute__((unused))
+
+@interface BufferView : NSView {
+    size_t bufSize;
+    uint32_t *buf;
+    CGColorSpaceRef colorSpace;
+    CGDataProviderRef dataProvider;
+}
+@end
+
+@implementation BufferView
+- (instancetype) initWithFrame: (NSRect) frameRect {
+    colorSpace = CGColorSpaceCreateDeviceRGB();
+    return [super initWithFrame: frameRect];
+}
+
+- (void) setWindowTitle {
+    [[self window] setTitle: [NSString stringWithUTF8String: status()]];
+}
+
+- (void) draw {
+    draw(buf, [self frame].size.width, [self frame].size.height);
+    [self setNeedsDisplay: YES];
+}
+
+- (void) setFrameSize: (NSSize) newSize {
+    [super setFrameSize: newSize];
+    size_t newBufSize = 4 * newSize.width * newSize.height;
+    if (newBufSize > bufSize) {
+        bufSize = newBufSize;
+        buf = malloc(bufSize);
+        if (!buf) err(EX_OSERR, "malloc(%zu)", bufSize);
+        CGDataProviderRelease(dataProvider);
+        dataProvider = CGDataProviderCreateWithData(NULL, buf, bufSize, NULL);
+    }
+    [self draw];
+}
+
+- (void) drawRect: (NSRect) UNUSED dirtyRect {
+    NSSize size = [self frame].size;
+    CGContextRef ctx = [[NSGraphicsContext currentContext] CGContext];
+    CGImageRef image = CGImageCreate(
+        size.width, size.height,
+        8, 32, 4 * size.width,
+        colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
+        dataProvider,
+        NULL, false, kCGRenderingIntentDefault
+    );
+    CGContextDrawImage(ctx, [self frame], image);
+    CGImageRelease(image);
+}
+
+- (BOOL) acceptsFirstResponder {
+    return YES;
+}
+
+- (void) keyDown: (NSEvent *) event {
+    char in;
+    BOOL converted = [
+        [event characters]
+        getBytes: &in
+        maxLength: 1
+        usedLength: NULL
+        encoding: NSASCIIStringEncoding
+        options: 0
+        range: NSMakeRange(0, 1)
+        remainingRange: NULL
+    ];
+    if (converted) {
+        if (!input(in)) {
+            [NSApp terminate: self];
+        }
+        [self setWindowTitle];
+        [self draw];
+    }
+}
+@end
+
+@interface Delegate : NSObject <NSApplicationDelegate>
+@end
+
+@implementation Delegate
+- (BOOL) applicationShouldTerminateAfterLastWindowClosed:
+    (NSApplication *) UNUSED sender {
+    return YES;
+}
+@end
+
+int main(int argc, char *argv[]) {
+    int error = init(argc, argv);
+    if (error) return error;
+
+    [NSApplication sharedApplication];
+    [NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];
+    [NSApp setDelegate: [Delegate new]];
+
+    NSString *name = [[NSProcessInfo processInfo] processName];
+    NSMenu *menu = [NSMenu new];
+    [
+        menu
+        addItemWithTitle: @"Close Window"
+        action: @selector(performClose:)
+        keyEquivalent: @"w"
+    ];
+    [menu addItem: [NSMenuItem separatorItem]];
+    [
+        menu
+        addItemWithTitle: [@"Quit " stringByAppendingString: name]
+        action: @selector(terminate:)
+        keyEquivalent: @"q"
+    ];
+    NSMenuItem *menuItem = [NSMenuItem new];
+    [menuItem setSubmenu: menu];
+    [NSApp setMainMenu: [NSMenu new]];
+    [[NSApp mainMenu] addItem: menuItem];
+
+    NSUInteger style = NSTitledWindowMask
+        | NSClosableWindowMask
+        | NSMiniaturizableWindowMask
+        | NSResizableWindowMask;
+    NSWindow *window = [
+        [NSWindow alloc]
+        initWithContentRect: NSMakeRect(0, 0, 800, 600)
+        styleMask: style
+        backing: NSBackingStoreBuffered
+        defer: YES
+    ];
+    [window center];
+
+    BufferView *view = [[BufferView alloc] initWithFrame: [window frame]];
+    [window setContentView: view];
+    [view setWindowTitle];
+
+    [window makeKeyAndOrderFront: nil];
+    [NSApp activateIgnoringOtherApps: YES];
+    [NSApp run];
+}
diff --git a/bin/gfx/fb.c b/bin/gfx/fb.c
new file mode 100644
index 00000000..94a3245e
--- /dev/null
+++ b/bin/gfx/fb.c
@@ -0,0 +1,87 @@
+/* Copyright (c) 2018, Curtis McEnroe <programble@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <fcntl.h>
+#include <linux/fb.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sysexits.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "gfx.h"
+
+static struct termios saveTerm;
+static void restoreTerm(void) {
+    tcsetattr(STDERR_FILENO, TCSADRAIN, &saveTerm);
+}
+
+int main(int argc, char *argv[]) {
+    int error;
+
+    error = init(argc, argv);
+    if (error) return error;
+
+    const char *path = getenv("FRAMEBUFFER");
+    if (!path) path = "/dev/fb0";
+
+    int fb = open(path, O_RDWR);
+    if (fb < 0) err(EX_OSFILE, "%s", path);
+
+    struct fb_var_screeninfo info;
+    error = ioctl(fb, FBIOGET_VSCREENINFO, &info);
+    if (error) err(EX_IOERR, "%s", path);
+
+    size_t size = 4 * info.xres * info.yres;
+    uint32_t *buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fb, 0);
+    if (buf == MAP_FAILED) err(EX_IOERR, "%s", path);
+
+    error = tcgetattr(STDERR_FILENO, &saveTerm);
+    if (error) err(EX_IOERR, "tcgetattr");
+    atexit(restoreTerm);
+
+    struct termios term = saveTerm;
+    term.c_lflag &= ~(ICANON | ECHO);
+    error = tcsetattr(STDERR_FILENO, TCSADRAIN, &term);
+    if (error) err(EX_IOERR, "tcsetattr");
+
+    uint32_t saveBg = buf[0];
+
+    uint32_t back[info.xres * info.yres];
+    for (;;) {
+        draw(back, info.xres, info.yres);
+        memcpy(buf, back, size);
+
+        char in;
+        ssize_t len = read(STDERR_FILENO, &in, 1);
+        if (len < 0) err(EX_IOERR, "read");
+        if (!len) return EX_DATAERR;
+
+        if (!input(in)) {
+            for (uint32_t i = 0; i < info.xres * info.yres; ++i) {
+                buf[i] = saveBg;
+            }
+            fprintf(stderr, "%s\n", status());
+            return EX_OK;
+        }
+    }
+}
diff --git a/bin/gfx/gfx.h b/bin/gfx/gfx.h
new file mode 100644
index 00000000..cd59ea3d
--- /dev/null
+++ b/bin/gfx/gfx.h
@@ -0,0 +1,24 @@
+/* Copyright (c) 2018, Curtis McEnroe <programble@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+extern int init(int argc, char *argv[]);
+extern const char *status(void);
+extern void draw(uint32_t *buf, size_t width, size_t height);
+extern bool input(char in);
diff --git a/bin/gfx/none.c b/bin/gfx/none.c
new file mode 100644
index 00000000..6decb24b
--- /dev/null
+++ b/bin/gfx/none.c
@@ -0,0 +1,24 @@
+/* Copyright (c) 2018, Curtis McEnroe <programble@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <sysexits.h>
+
+#include "gfx.h"
+
+int main() {
+    errx(EX_CONFIG, "no gfx frontend");
+}
diff --git a/bin/gfx/x11.c b/bin/gfx/x11.c
new file mode 100644
index 00000000..108d6459
--- /dev/null
+++ b/bin/gfx/x11.c
@@ -0,0 +1,135 @@
+/* Copyright (c) 2018, Curtis McEnroe <programble@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <X11/Xlib.h>
+#include <err.h>
+#include <sysexits.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include "gfx.h"
+
+static size_t width = 800;
+static size_t height = 600;
+
+static Display *display;
+static Window window;
+static Atom WM_DELETE_WINDOW;
+static GC windowGc;
+static XImage *image;
+
+static size_t bufSize;
+static uint32_t *buf;
+
+static size_t pixmapWidth;
+static size_t pixmapHeight;
+static Pixmap pixmap;
+
+static void createWindow(void) {
+    display = XOpenDisplay(NULL);
+    if (!display) errx(EX_UNAVAILABLE, "XOpenDisplay: %s", XDisplayName(NULL));
+
+    Window root = DefaultRootWindow(display);
+    window = XCreateSimpleWindow(display, root, 0, 0, width, height, 0, 0, 0);
+
+    WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", false);
+    XSetWMProtocols(display, window, &WM_DELETE_WINDOW, 1);
+
+    windowGc = XCreateGC(display, window, 0, NULL);
+
+    image = XCreateImage(display, NULL, 24, ZPixmap, 0, NULL, width, height, 32, 0);
+}
+
+static void resizePixmap(void) {
+    size_t newSize = 4 * width * height;
+    if (newSize > bufSize) {
+        bufSize = newSize;
+        free(buf);
+        buf = malloc(bufSize);
+        if (!buf) err(EX_OSERR, "malloc(%zu)", bufSize);
+    }
+
+    image->data = (char *)buf;
+    image->width = width;
+    image->height = height;
+    image->bytes_per_line = 4 * width;
+
+    if (width > pixmapWidth || height > pixmapHeight) {
+        pixmapWidth = width;
+        pixmapHeight = height;
+        if (pixmap) XFreePixmap(display, pixmap);
+        pixmap = XCreatePixmap(display, window, pixmapWidth, pixmapHeight, 24);
+    }
+}
+
+static void drawWindow(void) {
+    draw(buf, width, height);
+    XPutImage(display, pixmap, windowGc, image, 0, 0, 0, 0, width, height);
+    XCopyArea(display, pixmap, window, windowGc, 0, 0, width, height, 0, 0);
+}
+
+int main(int argc, char *argv[]) {
+    int error = init(argc, argv);
+    if (error) return error;
+
+    createWindow();
+    resizePixmap();
+    drawWindow();
+
+    XStoreName(display, window, status());
+    XMapWindow(display, window);
+
+    XEvent event;
+    XSelectInput(display, window, ExposureMask | StructureNotifyMask | KeyPressMask);
+    for (;;) {
+        XNextEvent(display, &event);
+
+        switch (event.type) {
+            case KeyPress: {
+                XKeyEvent key = event.xkey;
+                KeySym sym = XLookupKeysym(&key, key.state & ShiftMask);
+                if (sym > 0x80) break;
+                if (!input(sym)) return EX_OK;
+                XStoreName(display, window, status());
+                drawWindow();
+            } break;
+
+            case ConfigureNotify: {
+                XConfigureEvent configure = event.xconfigure;
+                width = configure.width;
+                height = configure.height;
+                resizePixmap();
+                drawWindow();
+            } break;
+
+            case Expose: {
+                XExposeEvent expose = event.xexpose;
+                XCopyArea(
+                    display, pixmap, window, windowGc,
+                    expose.x, expose.y,
+                    expose.width, expose.height,
+                    expose.x, expose.y
+                );
+            } break;
+
+            case ClientMessage: {
+                XClientMessageEvent client = event.xclient;
+                if ((Atom)client.data.l[0] == WM_DELETE_WINDOW) return EX_OK;
+            } break;
+        }
+    }
+}