Merge branch 'feature-pixelnuke'

This commit is contained in:
Marcel Hellkamp 2018-03-29 18:41:46 +02:00
commit f0ad2f2e99
13 changed files with 870 additions and 4 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
pixelnuke/.*
pixelnuke/*.o
pixelnuke/pixelnuke
pixelwar/.*
pixelwar/target

View file

@ -20,7 +20,7 @@ Pixelflut defines four main commands that are always supported to get you starte
You can send multiple commands over the same TCP socket by terminating each command with a single newline character (`\n`).
Example: `echo "PX 23 42 ff8000" | netcat pixelflut.xample.com 1337`
Example: `echo "PX 23 42 ff8000" | netcat pixelflut.example.com 1337`
Server Implementations
----------------------
@ -31,20 +31,31 @@ This repository contains multiple implementations of the pixelflut protocol. Pul
Server written in Python, based on gevent and pygame. Easy to hack with, but a bit slow.
sudo apt-get install python-gevent python-pygame python-cairo
cd pixelflut
sudo apt-get install python-gevent python-pygame python-cairo
mkdir save
python pixelflut.py brain.py
#### `/pixelwar` (java server)
Server wirtten in Java8, based on netty and awt. Optimized for speed and large player groups, fast networks or high resolution projectors.
Server written in Java8, based on netty and awt. Optimized for speed and large player groups, fast networks or high resolution projectors.
sudo apt-get install maven openjdk-8-jdk
cd pixelwar
sudo apt-get install maven openjdk-8-jdk
mvn package
java -jar target/pixelwar*-jar-with-dependencies.jar
#### `/pixelnuke` (C server)
Server written in C, based on libevent2, OpenGL, GLFW and pthreads. It won't get any faster than this.
cd pixelnuke
sudo apt-get install build-essential libevent-dev libglew-dev libglfw3-dev
make
./pixelnuke
Pull requests that improve performance or portability (e.g. Windows or RasPI) are always welcomed.
Links and Videos
----------------

26
pixelnuke/Makefile Normal file
View file

@ -0,0 +1,26 @@
.PHONY: default all clean
CC = gcc
CFLAGS = -Wall -pthread -O2
LIBS = -levent -levent_pthreads -lrt -lGL -lGLEW -lglfw
TARGET = pixelnuke
default: $(TARGET)
all: default
debug: CFLAGS += -DDEBUG -g
debug: default
OBJECTS = $(patsubst %.c, %.o, $(wildcard *.c))
HEADERS = $(wildcard *.h)
%.o: %.c $(HEADERS)
$(CC) $(CFLAGS) -c $< -o $@
.PRECIOUS: $(TARGET) $(OBJECTS)
$(TARGET): $(OBJECTS)
$(CC) $(CFLAGS) $(OBJECTS) -Wall $(LIBS) -o $@
clean:
-rm -f *.o $(TARGET)

408
pixelnuke/canvas.c Normal file
View file

@ -0,0 +1,408 @@
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <unistd.h> // usleep
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h> //memcpy
#include "canvas.h"
typedef struct CanvasLayer {
GLuint size;
GLenum format;
GLuint tex;
GLuint pbo1;
GLuint pbo2;
GLubyte *data;
size_t mem;
} CanvasLayer;
// Global state
static int canvas_display = -1;
static unsigned int canvas_tex_size = 1024;
static int canvas_width=0;
static int canvas_height=0;
static GLFWwindow* canvas_win;
static CanvasLayer *canvas_base;
static CanvasLayer *canvas_overlay;
pthread_t canvas_thread;
void glfw_error_callback(int error, const char* description) {
printf("GLFW Error: %d %s", error, description);
}
static inline int min(int a, int b) {
return a < b ? a : b;
}
static inline int max(int a, int b) {
return a > b ? a : b;
}
// User callbacks
void (*canvas_on_close_cb)();
void (*canvas_on_resize_cb)();
void (*canvas_on_key_cb)(int, int, int);
static int canvas_do_layout = 0;
static CanvasLayer* canvas_layer_alloc(int size, int alpha) {
CanvasLayer * layer = malloc(sizeof(CanvasLayer));
layer->size = size;
layer->format = alpha ? GL_RGBA : GL_RGB;
layer->mem = size * size * (alpha ? 4 : 3);
layer->data = malloc(sizeof(GLubyte) * layer->mem);
memset(layer->data, 0, layer->mem);
return layer;
}
static void canvas_layer_bind(CanvasLayer* layer) {
// Create texture object
glGenTextures(1, &(layer->tex));
glBindTexture( GL_TEXTURE_2D, layer->tex);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture( GL_TEXTURE_2D, 0);
// Create two PBOs
glGenBuffers(1, &(layer->pbo1));
glBindBuffer( GL_PIXEL_UNPACK_BUFFER, layer->pbo1);
glBufferData( GL_PIXEL_UNPACK_BUFFER, layer->mem, NULL, GL_STREAM_DRAW);
glGenBuffers(1, &(layer->pbo2));
glBindBuffer( GL_PIXEL_UNPACK_BUFFER, layer->pbo2);
glBufferData( GL_PIXEL_UNPACK_BUFFER, layer->mem, NULL, GL_STREAM_DRAW);
glBindBuffer( GL_PIXEL_UNPACK_BUFFER, 0);
}
static void canvas_layer_unbind(CanvasLayer * layer) {
if (layer->tex) {
glDeleteTextures(1, &(layer->tex));
glDeleteBuffers(1, &(layer->pbo1));
glDeleteBuffers(1, &(layer->pbo2));
}
}
static void canvas_layer_free(CanvasLayer * layer) {
canvas_layer_unbind(layer);
free(layer->data);
free(layer);
}
static void canvas_on_resize(GLFWwindow* window, int w, int h);
static void canvas_on_key(GLFWwindow* window, int key, int scancode, int action,
int mods);
static void canvas_on_key(GLFWwindow* window, int key, int scancode, int action,
int mods) {
if (action == GLFW_PRESS && canvas_on_key_cb)
(*canvas_on_key_cb)(key, scancode, mods);
}
static void canvas_on_resize(GLFWwindow* window, int w, int h) {
canvas_width = w;
canvas_height = h;
if(canvas_on_resize_cb)
(*canvas_on_resize_cb)();
}
static void canvas_window_setup() {
if(canvas_win) {
glfwDestroyWindow(canvas_win);
}
glfwWindowHint(GLFW_DOUBLEBUFFER, 1);
if (canvas_display >= 0) {
int mcount;
GLFWmonitor** monitors = glfwGetMonitors(&mcount);
canvas_display %= mcount;
GLFWmonitor* monitor = monitors[canvas_display];
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
glfwWindowHint(GLFW_RED_BITS, mode->redBits);
glfwWindowHint(GLFW_GREEN_BITS, mode->greenBits);
glfwWindowHint(GLFW_BLUE_BITS, mode->blueBits);
glfwWindowHint(GLFW_REFRESH_RATE, mode->refreshRate);
canvas_win = glfwCreateWindow(mode->width, mode->height, "Pixelflut", monitor, NULL);
} else {
canvas_win = glfwCreateWindow(800, 600, "Pixelflut", NULL, NULL);
}
if (!canvas_win) {
printf("Could not create OpenGL context and/or window");
return;
}
glfwMakeContextCurrent(canvas_win);
// TODO: Move GL stuff to better place
//glShadeModel(GL_FLAT); // shading mathod: GL_SMOOTH or GL_FLAT
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4-byte pixel alignment
//glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
//glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
//glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
glDisable(GL_CULL_FACE);
glEnable(GL_TEXTURE_2D);
//glfwSetWindowUserPointer(canvas_win, (void*) this);
glfwSwapInterval(1);
glfwSetKeyCallback(canvas_win, &canvas_on_key);
glfwSetFramebufferSizeCallback(canvas_win, &canvas_on_resize);
glfwGetFramebufferSize(canvas_win, &canvas_width, &canvas_height);
canvas_on_resize(canvas_win, canvas_width, canvas_height);
canvas_do_layout = 0;
}
static void canvas_draw_layer(CanvasLayer * layer) {
if (!layer || !layer->data)
return;
GLuint pboNext = layer->pbo1;
GLuint pboIndex = layer->pbo2;
layer->pbo1 = pboIndex;
layer->pbo2 = pboNext;
// Switch PBOs on each call. One is updated, one is drawn.
// Update texture from first PBO
glBindBuffer( GL_PIXEL_UNPACK_BUFFER, pboIndex);
glBindTexture( GL_TEXTURE_2D, layer->tex);
glTexImage2D( GL_TEXTURE_2D, 0, layer->format, layer->size, layer->size, 0,
layer->format, GL_UNSIGNED_BYTE, 0);
glBindTexture( GL_TEXTURE_2D, 0);
// Update second PBO with new pixel data
glBindBuffer( GL_PIXEL_UNPACK_BUFFER, pboNext);
GLubyte *ptr = (GLubyte*) glMapBuffer( GL_PIXEL_UNPACK_BUFFER,
GL_WRITE_ONLY);
memcpy(ptr, layer->data, layer->mem);
glUnmapBuffer( GL_PIXEL_UNPACK_BUFFER);
glBindBuffer( GL_PIXEL_UNPACK_BUFFER, 0);
//// Actually draw stuff. The texture should be updated in the meantime.
if (layer->format == GL_RGBA) {
glEnable( GL_BLEND);
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
} else {
glDisable( GL_BLEND);
}
glPushMatrix();
glBindTexture( GL_TEXTURE_2D, layer->tex);
glBegin( GL_QUADS);
glTexCoord2f(0, 0);
glVertex3f(0.0f, 0.0f, 0.0f);
glTexCoord2f(0, 1);
glVertex3f(0.0f, layer->size, 0.0f);
glTexCoord2f(1, 1);
glVertex3f(layer->size, layer->size, 0.0f);
glTexCoord2f(1, 0);
glVertex3f(layer->size, 0.0f, 0.0f);
glEnd();
glBindTexture( GL_TEXTURE_2D, 0);
glPopMatrix();
}
static void* canvas_render_loop(void * arg) {
glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit()) {
puts("GLFW initialization failed");
if(canvas_on_close_cb)
(*canvas_on_close_cb)();
glfwTerminate();
return NULL;
}
canvas_window_setup();
int err = glewInit();
if (err != GLEW_OK) {
puts("GLEW initialization failed");
printf("Error: %s\n", glewGetErrorString(err));
if(canvas_on_close_cb)
(*canvas_on_close_cb)();
return NULL;
}
canvas_layer_bind(canvas_base);
canvas_layer_bind(canvas_overlay);
double last_frame = glfwGetTime();
while ("pixels are coming") {
if (canvas_do_layout) {
canvas_layer_unbind(canvas_base);
canvas_layer_unbind(canvas_overlay);
canvas_window_setup();
canvas_layer_bind(canvas_base);
canvas_layer_bind(canvas_overlay);
}
if (glfwWindowShouldClose(canvas_win))
break;
glfwGetFramebufferSize(canvas_win, &canvas_width, &canvas_height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, canvas_width, canvas_height, 0, -1, 1);
glViewport(0, 0, (GLsizei) canvas_width, (GLsizei) canvas_height);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
GLuint texSize = canvas_base->size;
if(canvas_width > texSize || canvas_height > texSize) {
float scale = ((float) max(canvas_width, canvas_height)) / (float) texSize;
glScalef(scale, scale, 1);
}
canvas_draw_layer(canvas_base);
// TODO: Overlay is not used yet
//canvas_draw_layer(canvas_overlay);
glPopMatrix();
glfwPollEvents();
glfwSwapBuffers(canvas_win);
double now = glfwGetTime();
double dt = now - last_frame;
last_frame = now;
double sleep = 1.0 / 30 - dt;
if (sleep > 0) {
usleep(sleep * 1000000);
}
}
if(canvas_on_close_cb)
(*canvas_on_close_cb)();
glfwTerminate();
canvas_layer_free(canvas_base);
canvas_layer_free(canvas_overlay);
return NULL;
}
// Public functions
void canvas_start(unsigned int texSize, void (*on_close)()) {
canvas_on_close_cb = on_close;
canvas_tex_size = texSize;
canvas_base = canvas_layer_alloc(canvas_tex_size, 0);
canvas_overlay = canvas_layer_alloc(canvas_tex_size, 1);
if (pthread_create(&canvas_thread, NULL, canvas_render_loop, NULL)) {
puts("Failed to start render thread");
exit(1);
}
}
void canvas_setcb_key(void (*on_key)(int key, int scancode, int mods)) {
canvas_on_key_cb = on_key;
}
void canvas_setcb_resize(void (*on_resize)()) {
canvas_on_resize_cb = on_resize;
}
void canvas_close() {
glfwSetWindowShouldClose(canvas_win, 1);
}
void canvas_fullscreen(int display) {
canvas_display = display;
canvas_do_layout = 1;
}
int canvas_get_display() {
return canvas_display;
}
// Return a pointer to the GLubyte for a given pixel, or NULL for out of bound coordinates.
static inline GLubyte* canvas_offset(CanvasLayer * layer, unsigned int x,
unsigned int y) {
if (x < 0 || y < 0 || x >= layer->size || y >= layer->size || layer->data == NULL)
return NULL;
return layer->data
+ ((y * layer->size) + x) * (layer->format == GL_RGBA ? 4 : 3);
}
void canvas_set_px(unsigned int x, unsigned int y, uint32_t rgba) {
CanvasLayer * layer = canvas_base;
GLubyte* ptr = canvas_offset(layer, x, y);
if (ptr == NULL) {
return;
}
GLubyte r = (rgba & 0xff000000) >> 24;
GLubyte g = (rgba & 0x00ff0000) >> 16;
GLubyte b = (rgba & 0x0000ff00) >> 8;
GLubyte a = (rgba & 0x000000ff) >> 0;
if (layer->format == GL_RGBA) {
ptr[0] = r;
ptr[1] = g;
ptr[2] = b;
ptr[3] = a;
return;
}
if (a == 0) {
return;
}
if (a < 0xff) {
GLuint na = 0xff - a;
r = (a * r + na * (ptr[0])) / 0xff;
g = (a * g + na * (ptr[1])) / 0xff;
b = (a * b + na * (ptr[2])) / 0xff;
}
ptr[0] = r;
ptr[1] = g;
ptr[2] = b;
}
void canvas_fill(uint32_t rgba) {
CanvasLayer * layer = canvas_base;
for(int x=0; x<layer->size; x++)
for(int y=0; y<layer->size; y++)
canvas_set_px(x, y, rgba);
}
void canvas_get_px(unsigned int x, unsigned int y, uint32_t *rgba) {
CanvasLayer * layer = canvas_base;
GLubyte* ptr = canvas_offset(layer, x, y);
if (ptr == NULL) {
*rgba = 0x000000;
} else {
*rgba = (ptr[0] << 24) + (ptr[1] << 16) + (ptr[2] << 8) + 0xff;
}
}
void canvas_get_size(unsigned int *w, unsigned int *h) {
int texSize = canvas_base->size;
if(canvas_width > texSize || canvas_height > texSize) {
float scale = ((float) max(canvas_width, canvas_height)) / texSize;
*w = min(texSize, canvas_width/scale);
*h = min(texSize, canvas_height/scale);
} else {
*w = canvas_width;
*h = canvas_height;
}
}

27
pixelnuke/canvas.h Normal file
View file

@ -0,0 +1,27 @@
#ifndef CANVAS_H_
#define CANVAS_H_
#include <stdint.h>
// Open the canvas window and start the gui loop (in a separate thread)
void canvas_start(unsigned int texSize, void (*on_close)());
void canvas_setcb_key(void (*on_key)(int key, int scancode, int mods));
void canvas_setcb_resize(void (*on_resize)());
// Close the canvas window and free any resources and contexts
void canvas_close();
void canvas_fullscreen(int display);
int canvas_get_display();
void canvas_fill(uint32_t rgba);
void canvas_set_px(unsigned int x, unsigned int y, uint32_t rgba);
void canvas_get_px(unsigned int x, unsigned int y, uint32_t *rgba);
// get the current visible canvas size in pixel.
// The actual window might be bigger if scaling is enabled.
void canvas_get_size(unsigned int *width, unsigned int *height);
#endif /* CANVAS_H_ */

208
pixelnuke/net.c Normal file
View file

@ -0,0 +1,208 @@
#include <netinet/in.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/thread.h>
#include <event2/bufferevent.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <err.h>
#include "net.h"
// Lines longer than this are considered an error.
#define NET_MAX_LINE 1024
// The server buffers up to NET_READ_BUFFER bytes per client connection.
// Lower values allow lots of clients to draw at the same time, each with a fair share.
// Higher values increase throughput but fast clients might be able to draw large batches at once.
#define NET_MAX_BUFFER 10240
typedef struct NetClient {
int sock_fd;
struct bufferevent *buf_ev;
int state;
void *user;
} NetClient;
#define NET_CSTATE_OPEN 0
#define NET_CSTATE_CLOSING 1
// global state
static struct event_base *base;
// User defined callbacks
static net_on_connect netcb_on_connect = NULL;
static net_on_read netcb_on_read = NULL;
static net_on_close netcb_on_close = NULL;
// libevent callbacks
static void netev_on_read(struct bufferevent *bev, void *ctx) {
struct evbuffer *input;
char *line;
size_t n;
NetClient *client = ctx;
input = bufferevent_get_input(bev);
// Change while->if for less throughput but more fair pixel distribution across client connections.
while ((line = evbuffer_readln(input, &n, EVBUFFER_EOL_LF))) {
(*netcb_on_read)(client, line);
free(line);
}
if (evbuffer_get_length(input) >= NET_MAX_LINE) {
net_err(client, "Line to long");
}
}
static void netev_on_write(struct bufferevent *bev, void *arg) {
NetClient *client = arg;
if (client->state == NET_CSTATE_CLOSING && evbuffer_get_length(bufferevent_get_output(bev)) == 0) {
if(netcb_on_close)
(*netcb_on_close)(client, 0);
bufferevent_free(bev);
free(client);
}
}
static void netev_on_error(struct bufferevent *bev, short error, void *arg) {
NetClient *client = arg;
// TODO: Some logging?
if (error & BEV_EVENT_EOF) {
} else if (error & BEV_EVENT_ERROR) {
} else if (error & BEV_EVENT_TIMEOUT) {
}
client->state = NET_CSTATE_CLOSING;
if(netcb_on_close)
(*netcb_on_close)(client, error);
bufferevent_free(bev);
free(client);
}
static void on_accept(evutil_socket_t listener, short event, void *arg) {
struct event_base *base = arg;
struct sockaddr_storage ss;
socklen_t slen = sizeof(ss);
int fd = accept(listener, (struct sockaddr*) &ss, &slen);
if (fd < 0) {
perror("accept failed");
} else if (fd > FD_SETSIZE) {
close(fd); // TODO: verify if this is needed. Only for select()? But libevent uses poll/kqueue?
} else {
NetClient *client = calloc(1, sizeof(NetClient));
if (client == NULL) {
perror("client malloc failed");
close(fd);
return;
}
client->sock_fd = fd;
client->buf_ev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
evutil_make_socket_nonblocking(fd);
bufferevent_setcb(client->buf_ev, netev_on_read, netev_on_write, netev_on_error, client);
bufferevent_setwatermark(client->buf_ev, EV_READ, 0, NET_MAX_BUFFER);
if(netcb_on_connect)
(*netcb_on_connect)(client);
if(client->state == NET_CSTATE_OPEN)
bufferevent_enable(client->buf_ev, EV_READ | EV_WRITE);
}
}
// Public functions
void net_start(int port, net_on_connect on_connect, net_on_read on_read, net_on_close on_close) {
evutil_socket_t listener;
struct sockaddr_in sin;
struct event *listener_event;
evthread_use_pthreads();
//setvbuf(stdout, NULL, _IONBF, 0);
netcb_on_connect = on_connect;
netcb_on_read = on_read;
netcb_on_close = on_close;
base = event_base_new();
if (!base)
err(1, "Failed to create event_base");
//evthread_make_base_notifiable(base);
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(1337);
listener = socket(AF_INET, SOCK_STREAM, 0);
evutil_make_socket_nonblocking(listener);
evutil_make_listen_socket_reuseable(listener);
if (bind(listener, (struct sockaddr*) &sin, sizeof(sin)) < 0) {
err(1, "bind failed");
}
if (listen(listener, 16) < 0) {
err(1, "listen failed");
}
listener_event = event_new(base, listener, EV_READ | EV_PERSIST, on_accept, (void*) base);
event_add(listener_event, NULL);
event_base_dispatch(base);
}
void net_stop() {
event_base_loopbreak(base);
}
void net_send(NetClient *client, const char * msg) {
struct evbuffer *output = bufferevent_get_output(client->buf_ev);
evbuffer_add(output, msg, strlen(msg));
evbuffer_add(output, "\n", 1);
}
void net_close(NetClient *client) {
if(client->state == NET_CSTATE_OPEN) {
client->state = NET_CSTATE_CLOSING;
bufferevent_disable(client->buf_ev, EV_READ);
}
}
void net_err(NetClient *client, const char * msg) {
struct evbuffer *output = bufferevent_get_output(client->buf_ev);
evbuffer_add(output, "ERROR: ", 7);
evbuffer_add(output, msg, strlen(msg));
evbuffer_add(output, "\n", 1);
net_close(client);
}
void net_set_user(NetClient *client, void *user) {
client->user = user;
}
void net_get_user(NetClient *client, void **user) {
*user = client->user;
}

38
pixelnuke/net.h Normal file
View file

@ -0,0 +1,38 @@
#ifndef NET_H_
#define NET_H_
typedef struct NetClient NetClient;
#define NET_CSTATE_OPEN 0
#define NET_CSTATE_CLOSING 1
// Callback called immediately after a client connects
typedef void (*net_on_connect)(NetClient *client);
// Callback called for each line of input.
// The char array is null-terminated and does not include the final line break.
// It is NOT owned by the callback and freed as soon as the callback returned.
typedef void (*net_on_read)(NetClient *client, char* line);
// Callback called after a client disconnects.
// The second parameter is 0 for a normal client-induced disconnect and != 0 on errors.
typedef void (*net_on_close)(NetClient *client, int error);
// Start the server and block until it is closed again.
void net_start(int port, net_on_connect on_connect, net_on_read on_read, net_on_close on_close);
// Stop the server as soon as possible
void net_stop();
// Send a string to the client. A newline is added automatically.
void net_send(NetClient *client, const char * msg);
// Stop reading from this clients socket, send all bytes still in the output buffer, then close the connection.
void net_close(NetClient *client);
// Send an error message to the client, then close the connection.
void net_err(NetClient *client, const char * msg);
// Get or set the user attachment, a pointer to an arbitrary data structure or NULL
void net_set_user(NetClient *client, void *user);
void net_get_user(NetClient *client, void **user);
#endif /* NET_H_ */

142
pixelnuke/pixelnuke.c Normal file
View file

@ -0,0 +1,142 @@
#include "net.h"
#include "canvas.h"
#include <stdlib.h>
#include <errno.h>
#include <stdio.h> //sprintf
unsigned int px_width = 1024;
unsigned int px_height = 1024;
// Helper functions
static int util_str_starts_with(const char* prefix, const char* str) {
char cp, cs;
while ((cp = *prefix++) == (cs = *str++)) {
if (cp == 0)
return 1;
}
return !cp;
}
// server callbacks
void px_on_connect(NetClient *client) {
}
void px_on_read(NetClient *client, char *line) {
if (util_str_starts_with("PX ", line)) {
const char * ptr = line + 3;
char * endptr = (char*) ptr;
errno = 0;
unsigned int x = strtoul(ptr, &endptr, 10);
if (endptr == ptr || errno) {
net_err(client,
"First parameter missing or invalid (should be decimal)");
return;
}
unsigned int y = strtoul((ptr = endptr), &endptr, 10);
if (endptr == ptr || errno) {
net_err(client,
"Second parameter missing or invalid (should be decimal)");
return;
}
// PX <x> <y> -> Get RGB color at position (x,y) or '0x000000' for out-of-range queries
if (*endptr == 0) {
uint32_t c;
canvas_get_px(x, y, &c);
char str[64];
sprintf(str, "PX %u %u %06X", x, y, (c >> 8));
net_send(client, str);
return;
}
// PX <x> <y> BB|RRGGBB|RRGGBBAA
unsigned long int c = strtoul((ptr = endptr), &endptr, 16);
if (endptr == ptr || errno) {
net_err(client,
"Third parameter missing or invalid (should be hex color)");
return;
}
if (endptr - 1 - 6 == ptr) {
// RGB -> RGBA
c = (c << 8) + 0xff;
} else if (endptr - 1 - 8 == ptr) {
// done
} else if (endptr - 1 - 2 == ptr) {
// WW -> RGBA
c = (c << 24) + (c << 16) + (c << 8) + 0xff;
} else {
net_err(client,
"Color hex code must be 2, 6 or 8 characters long (WW, RGB or RGBA)");
return;
}
canvas_set_px(x, y, c);
} else if (util_str_starts_with("SIZE", line)) {
char str[64];
sprintf(str, "SIZE %d %d", px_width, px_height);
net_send(client, str);
} else if (util_str_starts_with("HELP", line)) {
net_send(client,
"\
PX x y: Get color at position (x,y)\n\
PX x y rrggbb(aa): Draw a pixel (with optional alpha channel)\n\
SIZE: Get canvas size");
} else {
net_err(client, "Unknown command");
}
}
void px_on_close(NetClient *client, int error) {
}
void px_on_key(int key, int scancode, int mods) {
printf("Key pressed: key:%d scancode:%d mods:%d\n", key, scancode, mods);
if (key == 300) { // F11
int display = canvas_get_display();
if (display < 0)
canvas_fullscreen(0);
else
canvas_fullscreen(-1);
} else if (key == 301) { // F12
canvas_fullscreen(canvas_get_display() + 1);
} else if (key == 67) { // c
canvas_fill(0x00000088);
} else if (key == 81 || key == 256) { // q or ESC
canvas_close();
}
}
void px_on_resize() {
canvas_get_size(&px_width, &px_height);
}
void px_on_window_close() {
printf("Window closed\n");
net_stop();
}
int main(int argc, char **argv) {
canvas_setcb_key(&px_on_key);
canvas_setcb_resize(&px_on_resize);
canvas_start(1024, &px_on_window_close);
net_start(1337, &px_on_connect, &px_on_read, &px_on_close);
return 0;
}