Added C implementation (incomplete)
This commit is contained in:
parent
ef5c176822
commit
68c34c5346
11 changed files with 749 additions and 0 deletions
23
pixelnuke/Makefile
Normal file
23
pixelnuke/Makefile
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
.PHONY: default all clean
|
||||||
|
|
||||||
|
CC = gcc
|
||||||
|
CFLAGS = -Wall -pthread -g
|
||||||
|
LIBS = -levent -levent_pthreads -lrt -lGL -lGLEW -lglfw
|
||||||
|
TARGET = pixelnuke
|
||||||
|
|
||||||
|
default: $(TARGET)
|
||||||
|
all: 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)
|
||||||
361
pixelnuke/canvas.c
Normal file
361
pixelnuke/canvas.c
Normal file
|
|
@ -0,0 +1,361 @@
|
||||||
|
#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_width, canvas_height;
|
||||||
|
|
||||||
|
static int canvas_display = -1;
|
||||||
|
static GLFWwindow* canvas_win;
|
||||||
|
static CanvasLayer *canvas_base;
|
||||||
|
static CanvasLayer *canvas_overlay;
|
||||||
|
|
||||||
|
// User callbacks
|
||||||
|
|
||||||
|
void (*canvas_on_close_cb)();
|
||||||
|
void (*canvas_on_resize_cb)();
|
||||||
|
void (*canvas_on_key_cb)();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
return layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void canvas_layer_free(CanvasLayer * canvas) {
|
||||||
|
if (canvas->tex) {
|
||||||
|
glDeleteTextures(1, &(canvas->tex));
|
||||||
|
glDeleteBuffers(1, &(canvas->pbo1));
|
||||||
|
glDeleteBuffers(1, &(canvas->pbo2));
|
||||||
|
}
|
||||||
|
free(canvas->data);
|
||||||
|
free(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
printf("KEY: %u %u %u %u\n", key, scancode, action, mods);
|
||||||
|
if (action != GLFW_PRESS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (canvas_on_key_cb)
|
||||||
|
(*canvas_on_key_cb)(key, scancode, action, mods);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void canvas_on_resize(GLFWwindow* window, int w, int h) {
|
||||||
|
if(canvas_on_resize_cb)
|
||||||
|
(*canvas_on_resize_cb)();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void canvas_window_setup() {
|
||||||
|
GLFWwindow* old_win = 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, old_win);
|
||||||
|
} else {
|
||||||
|
canvas_win = glfwCreateWindow(800, 600, "Pixelflut", NULL, old_win);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canvas_win) {
|
||||||
|
printf("Could not create OpenGL context and/or window");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//glfwSetWindowUserPointer(canvas_win, (void*) this);
|
||||||
|
glfwMakeContextCurrent(canvas_win);
|
||||||
|
glfwSwapInterval(1);
|
||||||
|
glfwSetKeyCallback(canvas_win, &canvas_on_key);
|
||||||
|
glfwSetFramebufferSizeCallback(canvas_win, &canvas_on_resize);
|
||||||
|
|
||||||
|
if (old_win) {
|
||||||
|
glfwDestroyWindow(old_win);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_do_layout = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void canvas_draw_layer(CanvasLayer * layer) {
|
||||||
|
if (!layer)
|
||||||
|
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) {
|
||||||
|
glfwMakeContextCurrent(canvas_win);
|
||||||
|
|
||||||
|
double last_frame = glfwGetTime();
|
||||||
|
|
||||||
|
while ("pixels are coming") {
|
||||||
|
if (glfwWindowShouldClose(canvas_win))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (canvas_do_layout)
|
||||||
|
canvas_window_setup();
|
||||||
|
|
||||||
|
int w, h;
|
||||||
|
glfwGetFramebufferSize(canvas_win, &w, &h);
|
||||||
|
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();
|
||||||
|
|
||||||
|
//int ts = layer->texSize;
|
||||||
|
//if(width > ts || height > ts) {
|
||||||
|
// float scale = std::max(width, height) / (float) ts;
|
||||||
|
// glScalef(scale, scale, 1);
|
||||||
|
//}
|
||||||
|
|
||||||
|
canvas_draw_layer(canvas_base);
|
||||||
|
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;
|
||||||
|
printf("fps: %f\n", 1.0/dt);
|
||||||
|
if (sleep > 0) {
|
||||||
|
usleep(sleep * 1000000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(canvas_on_close_cb)
|
||||||
|
(*canvas_on_close_cb)();
|
||||||
|
|
||||||
|
glfwTerminate();
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public functions
|
||||||
|
|
||||||
|
pthread_t canvas_thread;
|
||||||
|
|
||||||
|
void glfw_error_callback(int error, const char* description) {
|
||||||
|
printf("GLFW Error: %d %s", error, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
void canvas_start(unsigned int texSize, void (*on_close)()) {
|
||||||
|
|
||||||
|
canvas_on_close_cb = on_close;
|
||||||
|
|
||||||
|
glfwSetErrorCallback(glfw_error_callback);
|
||||||
|
if (!glfwInit()) {
|
||||||
|
puts("GLFW initialization failed");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_window_setup();
|
||||||
|
|
||||||
|
int err = glewInit();
|
||||||
|
if (err != GLEW_OK) {
|
||||||
|
puts("GLEW initialization failed");
|
||||||
|
printf("Error: %s\n", glewGetErrorString(err));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//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);
|
||||||
|
|
||||||
|
canvas_base = canvas_layer_alloc(texSize, 0);
|
||||||
|
canvas_overlay = canvas_layer_alloc(texSize, 1);
|
||||||
|
|
||||||
|
glfwMakeContextCurrent(NULL); // Pass context to thread
|
||||||
|
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)
|
||||||
|
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, unsigned int 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;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ptr[0] = r;
|
||||||
|
ptr[1] = g;
|
||||||
|
ptr[2] = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
void canvas_get_px(unsigned int x, unsigned int y, unsigned int *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) {
|
||||||
|
// TODO: Clip on window size
|
||||||
|
*w = canvas_base->size;
|
||||||
|
*h = canvas_base->size;
|
||||||
|
}
|
||||||
|
|
||||||
24
pixelnuke/canvas.h
Normal file
24
pixelnuke/canvas.h
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
#ifndef CANVAS_H_
|
||||||
|
#define CANVAS_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_set_px(unsigned int x, unsigned int y, unsigned int rgba);
|
||||||
|
void canvas_get_px(unsigned int x, unsigned int y, unsigned int *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_ */
|
||||||
199
pixelnuke/net.c
Normal file
199
pixelnuke/net.c
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
#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"
|
||||||
|
|
||||||
|
#define MAX_LINE 128
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if ((line = evbuffer_readln(input, &n, EVBUFFER_EOL_LF))) {
|
||||||
|
if(netcb_on_read)
|
||||||
|
(*netcb_on_read)(client, line);
|
||||||
|
free(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evbuffer_get_length(input) >= 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, MAX_LINE);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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
38
pixelnuke/net.h
Normal 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_ */
|
||||||
104
pixelnuke/pixelnuke.c
Normal file
104
pixelnuke/pixelnuke.c
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
#include "net.h"
|
||||||
|
#include "canvas.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h> //sprintf
|
||||||
|
|
||||||
|
int px_width = 1024;
|
||||||
|
int px_height = 1024;
|
||||||
|
|
||||||
|
const char * px_help_text =
|
||||||
|
"\
|
||||||
|
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";
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
if (*endptr == 0) {
|
||||||
|
char str[64];
|
||||||
|
sprintf(str, "PX %u %u %x", x, y, 0xABCDEF);
|
||||||
|
net_send(client, str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unsigned int c = strtoul((ptr = endptr), &endptr, 16);
|
||||||
|
if (endptr == ptr || errno) {
|
||||||
|
net_err(client,
|
||||||
|
"Third parameter missing or invalid (should be hex color)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
printf("%d %d %u=%x\n", x, y, c, c);
|
||||||
|
} else if (util_str_starts_with("SIZE", line)) {
|
||||||
|
unsigned int w, h;
|
||||||
|
canvas_get_size(&w, &h);
|
||||||
|
char str[64];
|
||||||
|
sprintf(str, "SIZE %d %d", w, h);
|
||||||
|
net_send(client, str);
|
||||||
|
} else if (util_str_starts_with("HELP", line)) {
|
||||||
|
net_send(client, px_help_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void px_on_close(NetClient *client, int error) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void px_on_key(int key, int scancode, int 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 == 81 || key == 256) { // q or ESC
|
||||||
|
canvas_close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void px_on_resize() {}
|
||||||
|
void px_on_window_close() {
|
||||||
|
printf("Window closed\n");
|
||||||
|
net_stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
canvas_start(1024, &px_on_window_close);
|
||||||
|
canvas_setcb_key(&px_on_key);
|
||||||
|
canvas_setcb_resize(&px_on_resize);
|
||||||
|
net_start(1337, &px_on_connect, &px_on_read, &px_on_close);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue