├── features ├── tests ├── llist │ ├── .gitignore │ ├── main.h │ ├── Makefile │ └── main.c └── ringbuff │ ├── .gitignore │ ├── Makefile │ └── main.c ├── .gitignore ├── main.h ├── numa.h ├── linuxfb.h ├── sdl.h ├── textrender.h ├── workqueue.h ├── vnc.h ├── LICENSE ├── util.h ├── llist.h ├── network.h ├── statistics.h ├── llist.c ├── frontend.c ├── ring.c ├── Makefile ├── frontend.h ├── workqueue.c ├── sdl.c ├── textrender.c ├── framebuffer.h ├── ring.h ├── framebuffer.c ├── vnc.c ├── linuxfb.c ├── README.md ├── statistics.c ├── main.c └── network.c /features: -------------------------------------------------------------------------------- 1 | numa sdl vnc -------------------------------------------------------------------------------- /tests/llist/.gitignore: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /tests/ringbuff/.gitignore: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /tests/llist/main.h: -------------------------------------------------------------------------------- 1 | #include "../../llist.h" 2 | 3 | struct data { 4 | int value; 5 | struct llist_entry list; 6 | }; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.out 2 | *.o 3 | *.elf 4 | *.so 5 | .gdb_history 6 | shoreline 7 | *.data 8 | *.s 9 | *.old 10 | *.fnt 11 | *.bak 12 | -------------------------------------------------------------------------------- /main.h: -------------------------------------------------------------------------------- 1 | #ifndef _MAIN_H_ 2 | #define _MAIN_H_ 3 | 4 | #include "framebuffer.h" 5 | 6 | extern struct statistics stats; 7 | 8 | void draw_overlays(struct fb* fb); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /tests/llist/Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CCFLAGS=-O0 -Wall -ggdb 3 | RM=rm -f 4 | 5 | all: clean test 6 | 7 | test: 8 | $(CC) $(CCFLAGS) ../../llist.c main.c -o test 9 | 10 | clean: 11 | $(RM) test 12 | -------------------------------------------------------------------------------- /tests/ringbuff/Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CCFLAGS=-O0 -Wall -ggdb 3 | RM=rm -f 4 | 5 | all: clean test 6 | 7 | test: 8 | $(CC) $(CCFLAGS) ../../ring.c main.c -o test 9 | 10 | clean: 11 | $(RM) test 12 | -------------------------------------------------------------------------------- /numa.h: -------------------------------------------------------------------------------- 1 | #ifndef _NUMA_H_ 2 | #define _NUMA_H_ 3 | 4 | #include 5 | 6 | #ifdef FEATURE_NUMA 7 | #include 8 | #else 9 | #define numa_set_preferred(x) ((void)x) 10 | #define numa_available() ((int)false) 11 | #define numa_max_node() 1; 12 | #endif 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /linuxfb.h: -------------------------------------------------------------------------------- 1 | #ifndef _LINUXFB_H_ 2 | #define _LINUXFB_H_ 3 | 4 | #include "framebuffer.h" 5 | #include "frontend.h" 6 | 7 | struct linuxfb { 8 | struct frontend front; 9 | struct fb* fb; 10 | char* fbdev; 11 | int fd; 12 | char* fbmem; 13 | struct fb_var_screeninfo vscreen; 14 | unsigned int pixel_offset; 15 | }; 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /sdl.h: -------------------------------------------------------------------------------- 1 | #ifndef _SDL_H_ 2 | #define _SDL_H_ 3 | 4 | #include 5 | 6 | #include "framebuffer.h" 7 | #include "frontend.h" 8 | 9 | #define SDL_PXFMT SDL_PIXELFORMAT_RGBA8888 10 | 11 | struct sdl; 12 | 13 | typedef int (*sdl_cb_resize)(struct sdl* sdl, unsigned int width, unsigned int height); 14 | 15 | struct sdl { 16 | SDL_Window* window; 17 | SDL_Renderer* renderer; 18 | SDL_Texture* texture; 19 | 20 | struct fb* fb; 21 | 22 | sdl_cb_resize resize_cb; 23 | void* cb_private; 24 | struct frontend front; 25 | }; 26 | 27 | 28 | struct sdl_param { 29 | void* cb_private; 30 | sdl_cb_resize resize_cb; 31 | }; 32 | 33 | int sdl_alloc(struct frontend** ret, struct fb* fb, void* c); 34 | void sdl_free(struct frontend* front); 35 | int sdl_update(struct frontend* front); 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /textrender.h: -------------------------------------------------------------------------------- 1 | #ifndef _TEXTRENDER_H_ 2 | #define _TEXTRENDER_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include FT_FREETYPE_H 9 | 10 | // FT_Error_String is not supported by older libfreetype2 versions 11 | #ifndef FT_Error_String 12 | #define FT_Error_String(_) ("FT_Error_String not supported") 13 | #endif 14 | 15 | struct textrender { 16 | pthread_mutex_t font_lock; 17 | FT_Library ftlib; 18 | FT_Face ftface; 19 | }; 20 | 21 | // Management 22 | int textrender_alloc(struct textrender** ret, char* fontfile); 23 | void textrender_free(struct textrender* txtrndr); 24 | 25 | // Drawing 26 | int textrender_draw_string(struct textrender* txtrndr, struct fb* fb, unsigned int x, unsigned int y, const char* text, unsigned int size); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /workqueue.h: -------------------------------------------------------------------------------- 1 | #ifndef _WORKQUEUE_H_ 2 | #define _WORKQUEUE_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "llist.h" 8 | 9 | typedef int (*wqueue_cb)(void* priv); 10 | typedef int (*wqueue_err)(int err, void* priv); 11 | typedef void (*wqueue_cleanup)(int err, void* priv); 12 | 13 | struct workqueue_entry { 14 | int (*cb)(void* priv); 15 | int (*err)(int err, void* priv); 16 | void (*cleanup)(int err, void* priv); 17 | void* priv; 18 | 19 | struct llist_entry list; 20 | }; 21 | 22 | struct workqueue { 23 | struct llist entries; 24 | unsigned numa_node; 25 | bool do_exit; 26 | 27 | pthread_t thread; 28 | pthread_cond_t cond; 29 | pthread_mutex_t lock; 30 | }; 31 | 32 | int workqueue_init(); 33 | void workqueue_deinit(); 34 | int workqueue_enqueue(unsigned numa_node, void* priv, wqueue_cb cb, wqueue_err err, wqueue_cleanup cleanup); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /vnc.h: -------------------------------------------------------------------------------- 1 | #ifndef _VNC_H_ 2 | #define _VNC_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "framebuffer.h" 8 | #include "frontend.h" 9 | 10 | #define VNC_FONT_HEIGHT 16 11 | 12 | struct vnc { 13 | rfbScreenInfoPtr server; 14 | rfbFontDataPtr font; 15 | struct fb* fb; 16 | struct fb* fb_overlay; 17 | struct frontend front; 18 | pthread_mutex_t draw_lock; 19 | bool flickerfree; 20 | }; 21 | 22 | int vnc_alloc(struct frontend** ret, struct fb* fb, void* priv); 23 | int vnc_start(struct frontend* front); 24 | void vnc_free(struct frontend* front); 25 | int vnc_update(struct frontend* front); 26 | int vnc_draw_string(struct frontend* front, unsigned x, unsigned y, char* str); 27 | int vnc_configure_port(struct frontend* front, char* value); 28 | int vnc_configure_font(struct frontend* front, char* value); 29 | int vnc_configure_flicker(struct frontend* front, char* value); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tobias Schramm 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 | -------------------------------------------------------------------------------- /tests/llist/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../llist.h" 6 | 7 | #include "main.h" 8 | 9 | void print_list(struct llist* list) { 10 | printf("===LIST BEGIN===\n"); 11 | struct llist_entry* cursor; 12 | llist_for_each(list, cursor) { 13 | struct data* data = llist_entry_get_value(cursor, struct data, list); 14 | printf("%d\n", data->value); 15 | } 16 | printf("====LIST END====\n"); 17 | } 18 | 19 | int main(int argc, char** argv) { 20 | struct llist llist; 21 | llist_init(&llist); 22 | 23 | struct data one = { .value = 1, .list = LLIST_ENTRY_INIT }; 24 | struct data two = { .value = 2, .list = LLIST_ENTRY_INIT }; 25 | struct data three = { .value = 3, .list = LLIST_ENTRY_INIT }; 26 | 27 | llist_append(&llist, &one.list); 28 | llist_append(&llist, &two.list); 29 | llist_append(&llist, &three.list); 30 | 31 | print_list(&llist); 32 | 33 | llist_remove(&two.list); 34 | 35 | print_list(&llist); 36 | 37 | llist_append(&llist, &two.list); 38 | 39 | print_list(&llist); 40 | 41 | llist_remove(&two.list); 42 | 43 | print_list(&llist); 44 | 45 | llist_remove(&one.list); 46 | 47 | print_list(&llist); 48 | 49 | llist_remove(&three.list); 50 | 51 | print_list(&llist); 52 | } 53 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | #ifndef _SHORELINE_UTIL_H_ 2 | #define _SHORELINE_UTIL_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #ifndef max 12 | #define max(a, b) ((a) > (b) ? (a) : (b)) 13 | #endif 14 | #ifndef min 15 | #define min(a, b) ((a) < (b) ? (a) : (b)) 16 | #endif 17 | 18 | #define container_of(ptr, type, member) ({ \ 19 | const typeof(((type *)0)->member) * __mptr = (ptr); \ 20 | (type *)((char *)__mptr - ((size_t) &((type *)0)->member)); }) 21 | 22 | #define ARRAY_SHUFFLE(arr, len) \ 23 | { \ 24 | typeof((len)) i, j; \ 25 | typeof(*(arr)) tmp; \ 26 | for(i = 0; i + 1 < (len); i++) { \ 27 | j = i + rand() / (RAND_MAX / ((len) - i) + 1); \ 28 | tmp = (arr)[j]; \ 29 | (arr)[j] = (arr)[i]; \ 30 | (arr)[i] = tmp; \ 31 | } \ 32 | } 33 | 34 | #define ARRAY_LEN(arr) (sizeof((arr)) / sizeof(*(arr))) 35 | 36 | static inline unsigned get_numa_node() { 37 | #ifdef SYS_getcpu 38 | unsigned numa_node; 39 | if(syscall(SYS_getcpu, NULL, &numa_node, NULL) == -1) { 40 | numa_node = 0; 41 | } 42 | return numa_node; 43 | #else 44 | return 0; 45 | #endif 46 | } 47 | 48 | static inline long long int get_timespec_diff(struct timespec* a, struct timespec* b) { 49 | return (a->tv_sec - b->tv_sec) * 1000000000LL + (a->tv_nsec - b->tv_nsec); 50 | } 51 | 52 | static inline bool is_big_endian() { 53 | return htobe32(0x11223344) == 0x11223344; 54 | } 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /llist.h: -------------------------------------------------------------------------------- 1 | #ifndef _LLIST_H_ 2 | #define _LLIST_H_ 3 | 4 | #include 5 | 6 | #include "util.h" 7 | 8 | struct llist_entry; 9 | 10 | struct llist { 11 | struct llist_entry* head; 12 | struct llist_entry* tail; 13 | pthread_mutex_t lock; 14 | }; 15 | 16 | struct llist_entry { 17 | struct llist_entry* next; 18 | struct llist_entry* prev; 19 | struct llist* list; 20 | }; 21 | 22 | #define LLIST_ENTRY_INIT ((struct llist_entry){ NULL, NULL, NULL }) 23 | 24 | #define llist_is_empty(list) (!(list)->head) 25 | 26 | #define llist_entry_get_value(entry, type, member) \ 27 | container_of(entry, type, member) 28 | 29 | #define llist_for_each(list, cursor) \ 30 | for(cursor = (list)->head; (cursor) != NULL; (cursor) = (cursor)->next) 31 | 32 | #define llist_for_each_safe(list, cursor, nxt) \ 33 | for(cursor = (list)->head, (nxt) = (list)->head ? (list)->head->next : NULL; (cursor) != NULL; (cursor) = (nxt), (nxt) = (cursor) ? (cursor)->next : NULL) 34 | 35 | #define llist_lock(llist) pthread_mutex_lock(&(llist)->lock); 36 | #define llist_unlock(llist) pthread_mutex_unlock(&(llist)->lock); 37 | 38 | void llist_init(struct llist* llist); 39 | void llist_entry_init(struct llist_entry* entry); 40 | 41 | int llist_alloc(struct llist** ret); 42 | void llist_free(struct llist* ret); 43 | 44 | 45 | void llist_append(struct llist* llist, struct llist_entry* entry); 46 | void llist_remove(struct llist_entry* entry); 47 | 48 | size_t llist_length(struct llist* list); 49 | struct llist_entry* llist_get_entry(struct llist* list, unsigned int index); 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /network.h: -------------------------------------------------------------------------------- 1 | #ifndef _NETWORK_H_ 2 | #define _NETWORK_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct net; 10 | 11 | #include "framebuffer.h" 12 | #include "llist.h" 13 | #include "ring.h" 14 | #include "statistics.h" 15 | 16 | enum { 17 | NET_STATE_IDLE, 18 | NET_STATE_LISTEN, 19 | NET_STATE_SHUTDOWN, 20 | NET_STATE_EXIT 21 | }; 22 | 23 | struct net_threadargs { 24 | struct net* net; 25 | }; 26 | 27 | struct net_thread { 28 | pthread_t thread; 29 | struct net_threadargs threadargs; 30 | bool initialized; 31 | pthread_mutex_t list_lock; 32 | 33 | struct llist* threadlist; 34 | }; 35 | 36 | struct net { 37 | size_t ring_size; 38 | 39 | unsigned int state; 40 | 41 | int socket; 42 | 43 | struct fb* fb; 44 | 45 | unsigned int num_threads; 46 | struct net_thread* threads; 47 | struct fb_size* fb_size; 48 | pthread_mutex_t fb_lock; 49 | struct llist* fb_list; 50 | }; 51 | 52 | struct net_connection_threadargs { 53 | struct net* net; 54 | struct net_thread* net_thread; 55 | int socket; 56 | }; 57 | 58 | struct net_connection_thread { 59 | pthread_t thread; 60 | struct llist_entry list; 61 | struct net_connection_threadargs threadargs; 62 | struct { 63 | unsigned int x; 64 | unsigned int y; 65 | } offset; 66 | uint32_t byte_count; 67 | 68 | struct ring* ring; 69 | }; 70 | 71 | #define likely(x) __builtin_expect((x),1) 72 | #define unlikely(x) __builtin_expect((x),0) 73 | 74 | int net_alloc(struct net** network, struct fb* fb, struct llist* fb_list, struct fb_size* fb_size, size_t ring_size); 75 | void net_free(struct net* net); 76 | 77 | 78 | void net_shutdown(struct net* net); 79 | int net_listen(struct net* net, unsigned int num_threads, struct sockaddr_storage* addr, size_t addr_len); 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /statistics.h: -------------------------------------------------------------------------------- 1 | #ifndef _STATISTICS_H_ 2 | #define _STATISTICS_H_ 3 | 4 | #include 5 | #include 6 | 7 | struct statistics; 8 | 9 | #include "network.h" 10 | 11 | #define STATISTICS_NUM_AVERAGES 20 12 | 13 | struct statistics { 14 | unsigned long long num_bytes; 15 | unsigned long long num_pixels; 16 | unsigned long long num_connections; 17 | int average_index; 18 | unsigned long long bytes_per_second[STATISTICS_NUM_AVERAGES]; 19 | unsigned long long pixels_per_second[STATISTICS_NUM_AVERAGES]; 20 | unsigned long long frames_per_second[STATISTICS_NUM_AVERAGES]; 21 | unsigned long long last_num_frames; 22 | unsigned long long num_frames; 23 | struct timespec last_update; 24 | }; 25 | 26 | #include "frontend.h" 27 | 28 | struct statistics_frontend { 29 | struct frontend front; 30 | int socket; 31 | char* listen_port; 32 | char* listen_address; 33 | struct sockaddr_storage listen_addr; 34 | bool thread_created; 35 | pthread_t listen_thread; 36 | pthread_mutex_t stats_lock; 37 | struct statistics stats; 38 | struct addrinfo* addr_list; 39 | bool exit; 40 | }; 41 | 42 | void statistics_update(struct statistics* stats, struct net* net); 43 | 44 | const char* statistics_traffic_get_unit(struct statistics* stats); 45 | double statistics_traffic_get_scaled(struct statistics* stats); 46 | const char* statistics_throughput_get_unit(struct statistics* stats); 47 | double statistics_throughput_get_scaled(struct statistics* stats); 48 | 49 | const char* statistics_pixels_get_unit(struct statistics* stats); 50 | double statistics_pixels_get_scaled(struct statistics* stats); 51 | const char* statistics_pps_get_unit(struct statistics* stats); 52 | double statistics_pps_get_scaled(struct statistics* stats); 53 | 54 | int statistics_get_frames_per_second(struct statistics* stats); 55 | #endif 56 | -------------------------------------------------------------------------------- /llist.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "llist.h" 9 | 10 | 11 | void llist_init(struct llist* llist) { 12 | llist->head = NULL; 13 | llist->tail = NULL; 14 | pthread_mutex_init(&llist->lock, NULL); 15 | } 16 | 17 | void llist_entry_init(struct llist_entry* entry) { 18 | entry->next = NULL; 19 | entry->prev = NULL; 20 | entry->list = NULL; 21 | } 22 | 23 | 24 | int llist_alloc(struct llist** ret) { 25 | struct llist* llist = malloc(sizeof(struct llist)); 26 | if(!llist) { 27 | return -ENOMEM; 28 | } 29 | 30 | llist_init(llist); 31 | *ret = llist; 32 | 33 | return 0; 34 | } 35 | 36 | void llist_free(struct llist* llist) { 37 | free(llist); 38 | } 39 | 40 | 41 | void llist_append(struct llist* llist, struct llist_entry* entry) { 42 | llist_lock(llist); 43 | entry->list = llist; 44 | if(!llist->head || !llist->tail) { 45 | assert(!llist->tail); 46 | entry->next = NULL; 47 | entry->prev = NULL; 48 | llist->head = entry; 49 | llist->tail = entry; 50 | } else { 51 | entry->next = NULL; 52 | llist->tail->next = entry; 53 | entry->prev = llist->tail; 54 | llist->tail = entry; 55 | } 56 | llist_unlock(llist); 57 | } 58 | 59 | void llist_remove(struct llist_entry* entry) { 60 | struct llist* llist = entry->list; 61 | llist_lock(llist); 62 | if(entry == llist->head) { 63 | llist->head = entry->next; 64 | } 65 | if(entry == llist->tail) { 66 | llist->tail = entry->prev; 67 | } 68 | if(entry->next) { 69 | entry->next->prev = entry->prev; 70 | } 71 | if(entry->prev) { 72 | entry->prev->next = entry->next; 73 | } 74 | entry->next = NULL; 75 | entry->prev = NULL; 76 | entry->list = NULL; 77 | llist_unlock(llist); 78 | } 79 | 80 | size_t llist_length(struct llist* list) { 81 | size_t len = 0; 82 | struct llist_entry* cursor; 83 | llist_for_each(list, cursor) { 84 | (void)cursor; 85 | len++; 86 | } 87 | return len; 88 | } 89 | 90 | struct llist_entry* llist_get_entry(struct llist* list, unsigned int index) { 91 | struct llist_entry* cursor; 92 | llist_for_each(list, cursor) { 93 | if(!index--) { 94 | return cursor; 95 | } 96 | } 97 | return NULL; 98 | } 99 | -------------------------------------------------------------------------------- /frontend.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "frontend.h" 7 | 8 | #ifdef FEATURE_SDL 9 | extern struct frontend_def front_sdl; 10 | #endif 11 | #ifdef FEATURE_VNC 12 | extern struct frontend_def front_vnc; 13 | #endif 14 | #ifdef FEATURE_STATISTICS 15 | extern struct frontend_def front_statistics; 16 | #endif 17 | #ifdef FEATURE_FBDEV 18 | extern struct frontend_def front_linuxfb; 19 | #endif 20 | 21 | struct frontend_id frontends[] = { 22 | #ifdef FEATURE_SDL 23 | { "sdl", &front_sdl }, 24 | #endif 25 | #ifdef FEATURE_VNC 26 | { "vnc", &front_vnc }, 27 | #endif 28 | #ifdef FEATURE_STATISTICS 29 | { "statistics", &front_statistics }, 30 | #endif 31 | #ifdef FEATURE_FBDEV 32 | { "fbdev", &front_linuxfb }, 33 | #endif 34 | { NULL, NULL } 35 | }; 36 | 37 | struct frontend_def* frontend_get_def(char* id) { 38 | struct frontend_id* front = frontends; 39 | for(; front->def != NULL; front++) { 40 | if(strcmp(id, front->id) == 0) { 41 | return front->def; 42 | } 43 | } 44 | return NULL; 45 | } 46 | 47 | char* frontend_spec_extract_name(char* spec) { 48 | char* sep = strchr(spec, ','); 49 | char* limit = spec + strlen(spec); 50 | if(sep) { 51 | *sep = '\0'; 52 | return sep + 1 < limit ? sep + 1 : NULL; 53 | } 54 | return NULL; 55 | } 56 | 57 | static int frontend_configure_option(struct frontend* front, char* option) { 58 | char* sep = strchr(option, '='); 59 | char* sep_limit = strchr(option, ','); 60 | char* limit = option + strlen(option); 61 | char* value = NULL; 62 | const struct frontend_arg* args = front->def->args; 63 | if(sep && (sep < sep_limit || !sep_limit)) { 64 | *sep = '\0'; 65 | value = sep + 1 < limit ? sep + 1 : NULL; 66 | } 67 | while(args && strlen(args->name)) { 68 | if(strcmp(option, args->name) == 0) { 69 | return args->configure(front, value); 70 | } 71 | args++; 72 | } 73 | return -ENOENT; 74 | } 75 | 76 | int frontend_configure(struct frontend* front, char* options) { 77 | int err; 78 | char* sep = NULL; 79 | char* limit = options + strlen(options); 80 | while(options < limit && (sep = strchr(options, ','))) { 81 | *sep = '\0'; 82 | if((err = frontend_configure_option(front, options))) { 83 | return err; 84 | } 85 | options = sep + 1; 86 | } 87 | if(options < limit) { 88 | if((err = frontend_configure_option(front, options))) { 89 | return err; 90 | } 91 | } 92 | return 0; 93 | } 94 | -------------------------------------------------------------------------------- /ring.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "ring.h" 11 | 12 | int ring_alloc(struct ring** ret, size_t size) { 13 | int err; 14 | 15 | struct ring* ring = malloc(sizeof(struct ring)); 16 | if(!ring) { 17 | err = -ENOMEM; 18 | goto fail; 19 | } 20 | 21 | ring->data = malloc(size); 22 | if(!ring->data) { 23 | err = -ENOMEM; 24 | goto fail_ring; 25 | } 26 | ring->ptr_read = ring->data; 27 | ring->ptr_write = ring->data; 28 | ring->size = size; 29 | 30 | *ret = ring; 31 | 32 | return 0; 33 | 34 | fail_ring: 35 | free(ring); 36 | fail: 37 | return err; 38 | } 39 | 40 | void ring_free(struct ring* ring) { 41 | free(ring->data); 42 | free(ring); 43 | } 44 | 45 | 46 | // Take a peek into the ring buffer 47 | int ring_peek(struct ring* ring, char* data, size_t len) { 48 | size_t avail_contig; 49 | 50 | if(ring_available(ring) < len) { 51 | return -EINVAL; 52 | } 53 | 54 | avail_contig = ring_available_contig(ring); 55 | 56 | if(avail_contig >= len) { 57 | memcpy(data, ring->ptr_read, len); 58 | } else { 59 | memcpy(data, ring->ptr_read, avail_contig); 60 | memcpy(data + avail_contig, ring->data, len - avail_contig); 61 | } 62 | 63 | return 0; 64 | } 65 | 66 | // Read from this ring buffer 67 | int ring_read(struct ring* ring, char* data, size_t len) { 68 | size_t avail_contig; 69 | 70 | if(ring_available(ring) < len) { 71 | return -EINVAL; 72 | } 73 | 74 | avail_contig = ring_available_contig(ring); 75 | 76 | if(avail_contig >= len) { 77 | memcpy(data, ring->ptr_read, len); 78 | ring->ptr_read = ring_next(ring, ring->ptr_read + len - 1); 79 | } else { 80 | memcpy(data, ring->ptr_read, avail_contig); 81 | memcpy(data + avail_contig, ring->data, len - avail_contig); 82 | ring->ptr_read = ring->data + len - avail_contig; 83 | } 84 | 85 | return 0; 86 | } 87 | 88 | // Write to this ring buffer 89 | int ring_write(struct ring* ring, char* data, size_t len) { 90 | size_t free_contig; 91 | 92 | if(ring_free_space(ring) < len) { 93 | return -EINVAL; 94 | } 95 | 96 | free_contig = ring_free_space_contig(ring); 97 | 98 | if(free_contig >= len) { 99 | memcpy(ring->ptr_write, data, len); 100 | ring->ptr_write = ring_next(ring, ring->ptr_write + len - 1); 101 | } else { 102 | memcpy(ring->ptr_write, data, free_contig); 103 | memcpy(ring->data, data + free_contig, len - free_contig); 104 | ring->ptr_write = ring->data + len - free_contig; 105 | } 106 | return 0; 107 | } 108 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC ?= gcc 2 | RM = rm -f 3 | INCLUDE_DIR ?= /usr/include 4 | OPTFLAGS ?= -Ofast -march=native 5 | 6 | # Default: Enable all features that do not impact performance 7 | FEATURES ?= SIZE OFFSET STATISTICS SDL NUMA VNC TTF FBDEV #PIXEL_COUNT BROKEN_PTHREAD ALPHA_BLENDING 8 | 9 | # Declare features compiled conditionally 10 | CODE_FEATURES = STATISTICS SDL NUMA VNC TTF FBDEV 11 | 12 | SOURCE_SDL = sdl.c 13 | HEADER_SDL = sdl.h 14 | DEPS_SDL = sdl2 15 | CCFLAGS_sdl2 = -I$(INCLUDE_DIR)SDL2 -D_REENTRANT 16 | LDFLAGS_sdl2 = -lSDL2 17 | 18 | SOURCE_VNC = vnc.c 19 | HEADER_VNC = vnc.h 20 | DEPS_VNC = libvncserver 21 | LDFLAGS_libvncserver = -lvncserver 22 | 23 | SOURCE_TTF = textrender.c 24 | HEADER_TTF = textrender.h 25 | DEPS_TTF = freetype2 26 | CCFLAGS_freetype2 = -I$(INCLUDE_DIR)freetype2 -I$(INCLUDE_DIR)libpng16 -I$(INCLUDE_DIR)harfbuzz -I$(INCLUDE_DIR)glib-2.0 -I/usr/lib/glib-2.0/include 27 | LDFLAGS_freetype2 = -lfreetype 28 | 29 | SOURCE_STATISTICS = statistics.c 30 | HEADER_STATISTICS = statistics.h 31 | 32 | SOURCE_FBDEV = linuxfb.c 33 | HEADER_FBDEV = linuxfb.h 34 | 35 | DEPS_NUMA = numa 36 | LDFLAGS_numa = -lnuma 37 | 38 | # Create lists of features components 39 | FEATURE_SOURCES = $(foreach feature,$(CODE_FEATURES),$(SOURCE_$(feature))) 40 | FEATURE_HEADERS = $(foreach feature,$(CODE_FEATURES),$(HEADER_$(feature))) 41 | FEATURE_DEPS = $(foreach feature,$(CODE_FEATURES),$(DEPS_$(feature))) 42 | 43 | # Set default compile flags 44 | CCFLAGS = -Wall -D_GNU_SOURCE 45 | ifneq ($(DEBUG),) 46 | CCFLAGS += -O1 -ggdb -DDEBUG=$(DEBUG) 47 | else 48 | CCFLAGS += $(OPTFLAGS) 49 | endif 50 | CCFLAGS += $(foreach feature,$(FEATURES),-DFEATURE_$(feature)) 51 | 52 | # Build dependency compile flags 53 | DEPFLAGS_CC = 54 | DEPS = $(filter $(FEATURE_DEPS),$(foreach feature,$(FEATURES),$(DEPS_$(feature)))) 55 | # Try fetching compile flags from pkg-config, use static ones on failure 56 | DEPFLAGS_CC += $(foreach feature,$(FEATURES),\ 57 | $(foreach dep,$(DEPS_$(feature)),\ 58 | $(shell pkg-config --cflags $(dep) || ((1>&2 echo Missing pkg-config file for $(dep), trying $(CCFLAGS_$(dep)) && echo "$(CCFLAGS_$(dep))") )))) 59 | 60 | # Build dependency linker flags 61 | DEPFLAGS_LD = -lpthread 62 | # Try fetching linker flags from pkg-config, use static ones on failure 63 | DEPFLAGS_LD += $(foreach feature,$(FEATURES),\ 64 | $(foreach dep,$(DEPS_$(feature)),\ 65 | $(shell pkg-config --libs $(dep) || ((1>&2 echo Missing pkg-config file for $(dep), trying $(LDFLAGS_$(dep)) && echo "$(LDFLAGS_$(dep))") )))) 66 | 67 | # Select source files 68 | FEATURE_SOURCE = $(foreach feature,$(FEATURES),$(SOURCE_$feature)) 69 | SOURCE = $(filter-out $(FEATURE_SOURCES),$(wildcard *.c)) 70 | SOURCE += $(foreach feature,$(FEATURES),$(SOURCE_$(feature))) 71 | OBJS = $(patsubst %.c,%.o,$(SOURCE)) 72 | HDRS = $(filter-out $(FEATURE_HEADERS),$(wildcard *.h)) 73 | HDRS += $(foreach feature,$(FEATURES),$(HEADER_$(feature))) 74 | 75 | all: shoreline 76 | 77 | %.o : %.c $(HDRS) Makefile 78 | $(CC) -c $(CPPFLAGS) $(CFLAGS) $(CCFLAGS) $(DEPFLAGS_CC) $< -o $@ 79 | 80 | shoreline: $(OBJS) 81 | $(CC) $(LDFLAGS) $(CFLAGS) $(CCFLAGS) $^ $(DEPFLAGS_LD) -o shoreline 82 | 83 | clean: 84 | $(RM) $(OBJS) 85 | $(RM) shoreline 86 | 87 | .PHONY: all clean 88 | -------------------------------------------------------------------------------- /frontend.h: -------------------------------------------------------------------------------- 1 | #ifndef _FRONTEND_H_ 2 | #define _FRONTEND_H_ 3 | 4 | #include 5 | 6 | struct frontend; 7 | 8 | #include "framebuffer.h" 9 | #include "llist.h" 10 | 11 | #define FRONTEND_ALLOC(name) int (*name)(struct frontend** res, struct fb* fb, void* priv) 12 | #define FRONTEND_START(name) int (*name)(struct frontend* front) 13 | #define FRONTEND_FREE(name) void (*name)(struct frontend* front) 14 | #define FRONTEND_UPDATE(name) int (*name)(struct frontend* front) 15 | #define FRONTEND_DRAW_STRING(name) int (*name)(struct frontend* front, unsigned x, unsigned y, char* str) 16 | 17 | typedef int (*frontend_alloc)(struct frontend** res, struct fb* fb, void* priv); 18 | typedef int (*frontend_start)(struct frontend* front); 19 | typedef void (*frontend_free)(struct frontend* front); 20 | typedef int (*frontend_update)(struct frontend* front); 21 | typedef int (*frontend_draw_string)(struct frontend* front, unsigned x, unsigned y, char* str); 22 | 23 | struct frontend_ops { 24 | FRONTEND_ALLOC(alloc); 25 | FRONTEND_START(start); 26 | FRONTEND_FREE(free); 27 | FRONTEND_UPDATE(update); 28 | FRONTEND_DRAW_STRING(draw_string); 29 | }; 30 | 31 | struct frontend_arg { 32 | char* name; 33 | int (*configure)(struct frontend* front, char* value); 34 | }; 35 | 36 | struct frontend_def { 37 | char* name; 38 | const struct frontend_ops* ops; 39 | bool handles_signals; 40 | const struct frontend_arg* args; 41 | }; 42 | 43 | #define DECLARE_FRONTEND(name, frontname, frontops, sig) \ 44 | struct frontend_def name = {frontname, frontops, sig, NULL} 45 | 46 | #define DECLARE_FRONTEND_SIG(name, frontname, frontops) \ 47 | DECLARE_FRONTEND(name, frontname, frontops, true) 48 | 49 | #define DECLARE_FRONTEND_NOSIG(name, frontname, frontops) \ 50 | DECLARE_FRONTEND(name, frontname, frontops, false) 51 | 52 | #define DECLARE_FRONTEND_ARGS(name, frontname, frontops, sig, arg_names) \ 53 | struct frontend_def name = {frontname, frontops, sig, arg_names} 54 | 55 | #define DECLARE_FRONTEND_SIG_ARGS(name, frontname, frontops, arg_names) \ 56 | DECLARE_FRONTEND_ARGS(name, frontname, frontops, true, arg_names) 57 | 58 | #define DECLARE_FRONTEND_NOSIG_ARGS(name, frontname, frontops, arg_names) \ 59 | DECLARE_FRONTEND_ARGS(name, frontname, frontops, false, arg_names) 60 | 61 | #define frontend_alloc(def, front, fb, priv) ((def)->ops->alloc((front), (fb), (priv))) 62 | #define frontend_start(front) ((front)->def->ops->start((front))) 63 | #define frontend_free(front) ((front)->def->ops->free((front))) 64 | #define frontend_update(front) ((front)->def->ops->update((front))) 65 | #define frontend_draw_string(front, x, y, str) ((front)->def->ops->draw_string((front), (x), (y), (str))) 66 | 67 | #define frontend_can_configure(front) (!!(front)->def->args) 68 | #define frontend_can_start(front) (!!(front)->def->ops->start) 69 | #define frontend_can_draw_string(front) (!!(front)->def->ops->draw_string) 70 | 71 | struct frontend { 72 | struct frontend_def* def; 73 | struct llist_entry list; 74 | bool sync_overlay_draw; 75 | }; 76 | 77 | struct frontend_id { 78 | char* id; 79 | struct frontend_def* def; 80 | }; 81 | 82 | struct frontend_def* frontend_get_def(char* id); 83 | char* frontend_spec_extract_name(char* spec); 84 | int frontend_configure(struct frontend* front, char* options); 85 | 86 | #endif 87 | -------------------------------------------------------------------------------- /workqueue.c: -------------------------------------------------------------------------------- 1 | #include "workqueue.h" 2 | 3 | #include 4 | #include "numa.h" 5 | #include 6 | #include 7 | #include 8 | 9 | static struct workqueue* workqueues; 10 | static int num_workqueues = 0; 11 | 12 | static int num_workqueue_items = 0; 13 | 14 | static void* work_thread(void* priv) { 15 | int err; 16 | struct workqueue* wqueue = (struct workqueue*)priv; 17 | // If there is more than one workqueue we need to take care of allocation policies 18 | if(num_workqueues > 1) { 19 | numa_set_preferred(wqueue->numa_node); 20 | } 21 | pthread_mutex_lock(&wqueue->lock); 22 | while(!wqueue->do_exit) { 23 | pthread_cond_wait(&wqueue->cond, &wqueue->lock); 24 | while(!(llist_is_empty(&wqueue->entries) || wqueue->do_exit)) { 25 | struct llist_entry* llentry = llist_get_entry(&wqueue->entries, 0); 26 | struct workqueue_entry* entry = llist_entry_get_value(llentry, struct workqueue_entry, list); 27 | llist_remove(llentry); 28 | num_workqueue_items--; 29 | if((err = entry->cb(entry->priv))) { 30 | if(!entry->err) { 31 | if(entry->cleanup) { 32 | entry->cleanup(err, entry->priv); 33 | } 34 | goto next; 35 | } 36 | if(entry->err(err, entry->priv)) { 37 | if(entry->cleanup) { 38 | entry->cleanup(err, entry->priv); 39 | } 40 | pthread_mutex_unlock(&wqueue->lock); 41 | free(entry); 42 | goto fail; 43 | } 44 | } 45 | next: 46 | free(entry); 47 | } 48 | } 49 | 50 | fail: 51 | return NULL; 52 | } 53 | 54 | static void stop_workqueue(struct workqueue* wqueue) { 55 | wqueue->do_exit = true; 56 | pthread_cond_broadcast(&wqueue->cond); 57 | pthread_join(wqueue->thread, NULL); 58 | while(!llist_is_empty(&wqueue->entries)) { 59 | struct llist_entry* llentry = llist_get_entry(&wqueue->entries, 0); 60 | struct workqueue_entry* entry = llist_entry_get_value(llentry, struct workqueue_entry, list); 61 | llist_remove(llentry); 62 | if(entry->cleanup) { 63 | entry->cleanup(0, entry->priv); 64 | } 65 | free(entry); 66 | } 67 | } 68 | 69 | 70 | // TODO: Handle CPU hotplug? 71 | int workqueue_init() { 72 | int err = 0, i; 73 | 74 | num_workqueues = 1; 75 | if(numa_available()) { 76 | num_workqueues = numa_max_node(); 77 | } 78 | 79 | workqueues = calloc(num_workqueues, sizeof(struct workqueue)); 80 | if(!workqueues) { 81 | err = -ENOMEM; 82 | goto fail; 83 | } 84 | 85 | for(i = 0; i < num_workqueues; i++) { 86 | struct workqueue* wqueue = &workqueues[i]; 87 | wqueue->numa_node = i; 88 | pthread_mutex_init(&wqueue->lock, NULL); 89 | pthread_cond_init(&wqueue->cond, NULL); 90 | llist_init(&wqueue->entries); 91 | 92 | if((err = -pthread_create(&wqueue->thread, NULL, work_thread, wqueue))) { 93 | goto fail_threads; 94 | } 95 | } 96 | 97 | return 0; 98 | 99 | fail_threads: 100 | while(i-- > 0) { 101 | stop_workqueue(&workqueues[i]); 102 | } 103 | free(workqueues); 104 | fail: 105 | return err; 106 | } 107 | 108 | void workqueue_deinit() { 109 | int i; 110 | for(i = 0; i < num_workqueues; i++) { 111 | stop_workqueue(&workqueues[i]); 112 | } 113 | num_workqueues = 0; 114 | free(workqueues); 115 | } 116 | 117 | int workqueue_enqueue(unsigned numa_node, void* priv, wqueue_cb cb, wqueue_err err, wqueue_cleanup cleanup) { 118 | struct workqueue* wqueue; 119 | struct workqueue_entry* entry; 120 | 121 | if(numa_node >= num_workqueues) { 122 | return -EINVAL; 123 | } 124 | 125 | entry = calloc(1, sizeof(struct workqueue_entry)); 126 | if(!entry) { 127 | return -ENOMEM; 128 | } 129 | 130 | entry->priv = priv; 131 | entry->cb = cb; 132 | entry->err = err; 133 | entry->cleanup = cleanup; 134 | 135 | wqueue = &workqueues[numa_node]; 136 | pthread_mutex_lock(&wqueue->lock); 137 | llist_append(&wqueue->entries, &entry->list); 138 | num_workqueue_items++; 139 | pthread_mutex_unlock(&wqueue->lock); 140 | pthread_cond_broadcast(&wqueue->cond); 141 | 142 | return 0; 143 | } 144 | -------------------------------------------------------------------------------- /sdl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "sdl.h" 10 | #include "framebuffer.h" 11 | #include "frontend.h" 12 | #include "util.h" 13 | 14 | static const struct frontend_ops fops = { 15 | .alloc = sdl_alloc, 16 | .free = sdl_free, 17 | .update = sdl_update, 18 | }; 19 | 20 | DECLARE_FRONTEND_NOSIG(front_sdl, "SDL2 Frontend", &fops); 21 | 22 | int sdl_alloc(struct frontend** ret, struct fb* fb, void* priv) { 23 | int err = 0; 24 | struct sdl_param* params = priv; 25 | struct sdl* sdl = calloc(1, sizeof(struct sdl)); 26 | struct fb_size* size; 27 | if(!sdl) { 28 | err = -ENOMEM; 29 | goto fail; 30 | } 31 | 32 | sdl->fb = fb; 33 | size = fb_get_size(fb); 34 | 35 | sdl->cb_private = params->cb_private; 36 | sdl->resize_cb = params->resize_cb; 37 | 38 | SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1"); 39 | 40 | if(SDL_Init(SDL_INIT_VIDEO)) { 41 | err = -1; 42 | fprintf(stderr, "Failed to initialize SDL: %s\n", SDL_GetError()); 43 | goto fail_sdl; 44 | } 45 | 46 | SDL_ShowCursor(0); 47 | 48 | sdl->window = SDL_CreateWindow("shoreline", 49 | SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, size->width, 50 | size->height, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); 51 | 52 | if(!sdl->window) { 53 | err = -1; 54 | fprintf(stderr, "Failed to create SDL window: %s\n", SDL_GetError()); 55 | goto fail_sdl_init; 56 | } 57 | 58 | sdl->renderer = SDL_CreateRenderer(sdl->window, -1, SDL_RENDERER_ACCELERATED 59 | | SDL_RENDERER_PRESENTVSYNC); 60 | 61 | if(!sdl->renderer) { 62 | err = -1; 63 | fprintf(stderr, "Failed to create SDL renderer: %s\n", SDL_GetError()); 64 | goto fail_sdl_window; 65 | } 66 | SDL_RenderClear(sdl->renderer); 67 | 68 | sdl->texture = SDL_CreateTexture(sdl->renderer, SDL_PXFMT, 69 | SDL_TEXTUREACCESS_STREAMING, size->width, size->height); 70 | 71 | if(!sdl->texture) { 72 | err = -1; 73 | fprintf(stderr, "Failed to create SDL texture: %s\n", SDL_GetError()); 74 | goto fail_sdl_renderer; 75 | } 76 | 77 | *ret = &sdl->front; 78 | 79 | return 0; 80 | 81 | fail_sdl_renderer: 82 | SDL_DestroyRenderer(sdl->renderer); 83 | fail_sdl_window: 84 | SDL_DestroyWindow(sdl->window); 85 | fail_sdl_init: 86 | SDL_Quit(); 87 | fail_sdl: 88 | free(sdl); 89 | fail: 90 | return err; 91 | }; 92 | 93 | void sdl_free(struct frontend* front) { 94 | struct sdl* sdl = container_of(front, struct sdl, front); 95 | SDL_DestroyTexture(sdl->texture); 96 | SDL_DestroyRenderer(sdl->renderer); 97 | SDL_DestroyWindow(sdl->window); 98 | SDL_Quit(); 99 | free(sdl); 100 | } 101 | 102 | 103 | int sdl_update(struct frontend* front) { 104 | struct sdl* sdl = container_of(front, struct sdl, front); 105 | struct fb_size* size = fb_get_size(sdl->fb); 106 | 107 | int width, height, err; 108 | SDL_Window* window; 109 | SDL_Texture* texture; 110 | SDL_Event event; 111 | 112 | while(SDL_PollEvent(&event)) { 113 | if(event.type == SDL_WINDOWEVENT) { 114 | if(event.window.event == SDL_WINDOWEVENT_RESIZED) { 115 | window = SDL_GetWindowFromID(event.window.windowID); 116 | assert(window == sdl->window); 117 | 118 | SDL_GetWindowSize(window, &width, &height); 119 | assert(width >= 0); 120 | assert(height >= 0); 121 | printf("Resizing to %dx%d px\n", width, height); 122 | 123 | if(sdl->resize_cb) { 124 | if((err = sdl->resize_cb(sdl, width, height))) { 125 | return err; 126 | } 127 | } 128 | 129 | texture = SDL_CreateTexture(sdl->renderer, SDL_PXFMT, 130 | SDL_TEXTUREACCESS_STREAMING, width, height); 131 | if(!texture) { 132 | fprintf(stderr, "Failed to allocate new texture\n"); 133 | continue; 134 | } 135 | SDL_DestroyTexture(sdl->texture); 136 | sdl->texture = texture; 137 | fb_resize(sdl->fb, width, height); 138 | } 139 | } else if(event.type == SDL_QUIT) { 140 | return 1; 141 | } 142 | } 143 | 144 | SDL_UpdateTexture(sdl->texture, NULL, sdl->fb->pixels, size->width * sizeof(union fb_pixel)); 145 | SDL_RenderCopy(sdl->renderer, sdl->texture, NULL, NULL); 146 | SDL_RenderPresent(sdl->renderer); 147 | 148 | return 0; 149 | } 150 | -------------------------------------------------------------------------------- /textrender.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "framebuffer.h" 7 | #include "textrender.h" 8 | #include "util.h" 9 | 10 | #define DEFAULT_FONT_SIZE 16 11 | #define CACHE_CHUNK_SIZE 8 12 | 13 | #define TEXT_BORDER 3 14 | 15 | #define DPI 100 16 | 17 | #define PIXEL_TO_CARTESIAN(x) (64 * (x)) 18 | #define CARTESIAN_TO_PIXELS(x) ((x) / 64) 19 | 20 | int textrender_alloc(struct textrender** ret, char* fontfile) { 21 | int err = 0; 22 | FT_Error fterr; 23 | struct textrender* txtrndr; 24 | 25 | txtrndr = calloc(1, sizeof(struct textrender)); 26 | if(!txtrndr) { 27 | err = -ENOMEM; 28 | goto fail; 29 | } 30 | 31 | pthread_mutex_init(&txtrndr->font_lock, NULL); 32 | 33 | fterr = FT_Init_FreeType(&txtrndr->ftlib); 34 | if(fterr) { 35 | err = fterr; 36 | fprintf(stderr, "Failed to initialize free type library: %s(%d)\n", FT_Error_String(fterr), err); 37 | goto fail_alloc; 38 | } 39 | 40 | fterr = FT_New_Face(txtrndr->ftlib, fontfile, 0, &txtrndr->ftface); 41 | if(fterr) { 42 | err = fterr; 43 | fprintf(stderr, "Failed to load font face \"%s\": %s(%d)\n", fontfile, FT_Error_String(fterr), err); 44 | goto fail_ft; 45 | } 46 | 47 | *ret = txtrndr; 48 | return 0; 49 | 50 | fail_ft: 51 | FT_Done_FreeType(txtrndr->ftlib); 52 | fail_alloc: 53 | free(txtrndr); 54 | fail: 55 | return err; 56 | } 57 | 58 | void textrender_free(struct textrender* txtrndr) { 59 | FT_Done_Face(txtrndr->ftface); 60 | FT_Done_FreeType(txtrndr->ftlib); 61 | free(txtrndr); 62 | } 63 | 64 | static int draw_bitmap(struct fb* fb, FT_Bitmap* ftbmp, unsigned int x, unsigned int y, unsigned int base) { 65 | size_t i; 66 | 67 | y -= base; 68 | 69 | if(ftbmp->pixel_mode != FT_PIXEL_MODE_GRAY) { 70 | return -EINVAL; 71 | } 72 | 73 | for(i = 0; i < ftbmp->rows; i++) { 74 | union fb_pixel* line_base = fb_get_line_base(fb, y + i); 75 | union fb_pixel* line_start = &line_base[x]; 76 | unsigned char* gray_base = &ftbmp->buffer[ftbmp->width * i]; 77 | if((y + i) < fb->size.height) { 78 | size_t j; 79 | for(j = 0; j < ftbmp->width; j++) { 80 | if((x +j) < fb->size.width) { 81 | line_start[j].abgr = FB_GRAY8_TO_PIXEL(gray_base[j]); 82 | } 83 | } 84 | } 85 | } 86 | return 0; 87 | } 88 | 89 | int textrender_draw_string(struct textrender* txtrndr, struct fb* fb, unsigned int x, unsigned int y, const char* text, unsigned int size) { 90 | int err = 0, i; 91 | FT_Error fterr; 92 | FT_GlyphSlot ftslot; 93 | FT_Vector ftpen; 94 | unsigned int xmin = x, ymin = y, xmax = x, ymax = y; 95 | 96 | pthread_mutex_lock(&txtrndr->font_lock); 97 | fterr = FT_Set_Char_Size(txtrndr->ftface, PIXEL_TO_CARTESIAN(size), 0, DPI, DPI); 98 | if(fterr) { 99 | err = fterr; 100 | fprintf(stderr, "Failed to set font size to %u: %s(%d)\n", size, FT_Error_String(fterr), err); 101 | goto fail; 102 | } 103 | 104 | ftslot = txtrndr->ftface->glyph; 105 | 106 | ftpen.x = ftpen.y = 0; 107 | 108 | for(i = 0; i < strlen(text); i++) { 109 | fterr = FT_Load_Char(txtrndr->ftface, text[i], FT_LOAD_RENDER); 110 | if(fterr) { 111 | err = fterr; 112 | fprintf(stderr, "Warning: Failed to find glyph for char '%c': %s(%d)\n", text[i], FT_Error_String(fterr), err); 113 | continue; 114 | } 115 | 116 | xmin = min(xmin, x + CARTESIAN_TO_PIXELS(ftpen.x)); 117 | ymin = min(ymin, y + CARTESIAN_TO_PIXELS(ftpen.y) - ftslot->bitmap_top); 118 | xmax = max(xmax, x + CARTESIAN_TO_PIXELS(ftpen.x) + CARTESIAN_TO_PIXELS(ftslot->metrics.width) + ftslot->bitmap_left); 119 | ymax = max(ymax, y + CARTESIAN_TO_PIXELS(ftpen.y) + CARTESIAN_TO_PIXELS(ftslot->metrics.height) - ftslot->bitmap_top); 120 | ftpen.x += ftslot->advance.x; 121 | ftpen.y += ftslot->advance.y; 122 | } 123 | 124 | fb_clear_rect(fb, xmin, ymin, xmax - xmin, ymax - ymin); 125 | 126 | ftpen.x = ftpen.y = 0; 127 | 128 | for(i = 0; i < strlen(text); i++) { 129 | fterr = FT_Load_Char(txtrndr->ftface, text[i], FT_LOAD_RENDER); 130 | if(fterr) { 131 | err = fterr; 132 | fprintf(stderr, "Warning: Failed to find glyph for char '%c': %s(%d)\n", text[i], FT_Error_String(fterr), err); 133 | continue; 134 | } 135 | 136 | draw_bitmap(fb, &ftslot->bitmap, x + CARTESIAN_TO_PIXELS(ftpen.x), y + CARTESIAN_TO_PIXELS(ftpen.y), ftslot->bitmap_top); 137 | 138 | ftpen.x += ftslot->advance.x; 139 | ftpen.y += ftslot->advance.y; 140 | } 141 | fail: 142 | pthread_mutex_unlock(&txtrndr->font_lock); 143 | return err; 144 | } 145 | -------------------------------------------------------------------------------- /framebuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef _FRAMEBUFFER_H_ 2 | #define _FRAMEBUFFER_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "llist.h" 8 | #include "util.h" 9 | 10 | #define COLORDEPTH 24 11 | 12 | struct fb_size { 13 | unsigned int width; 14 | unsigned int height; 15 | }; 16 | 17 | // RGBA32 18 | union fb_pixel { 19 | struct { 20 | uint8_t alpha; 21 | union { 22 | struct { 23 | uint8_t blue; 24 | uint8_t green; 25 | uint8_t red; 26 | } color_bgr; 27 | unsigned char bgr[3]; 28 | }; 29 | } color; 30 | struct { 31 | union { 32 | struct { 33 | uint8_t red; 34 | uint8_t green; 35 | uint8_t blue; 36 | } color_bgr; 37 | unsigned char bgr[3]; 38 | }; 39 | uint8_t alpha; 40 | } color_be; 41 | uint32_t abgr; 42 | }; 43 | 44 | struct fb { 45 | struct fb_size size; 46 | union fb_pixel* pixels; 47 | unsigned numa_node; 48 | struct llist_entry list; 49 | #ifdef FEATURE_STATISTICS 50 | #ifdef FEATURE_PIXEL_COUNT 51 | uint32_t pixel_count; 52 | #endif 53 | #endif 54 | }; 55 | 56 | 57 | // Management 58 | int fb_alloc(struct fb** framebuffer, unsigned int width, unsigned int height); 59 | void fb_free(struct fb* fb); 60 | void fb_free_all(struct llist* fbs); 61 | struct fb* fb_get_fb_on_node(struct llist* fbs, unsigned numa_node); 62 | 63 | // Manipulation 64 | static inline void fb_set_pixel(struct fb* fb, unsigned int x, unsigned int y, union fb_pixel* pixel) { 65 | union fb_pixel* target; 66 | assert(x < fb->size.width); 67 | assert(y < fb->size.height); 68 | 69 | target = &(fb->pixels[y * fb->size.width + x]); 70 | *target = *pixel; 71 | } 72 | 73 | void fb_set_pixel_rgb(struct fb* fb, unsigned int x, unsigned int y, uint8_t red, uint8_t green, uint8_t blue); 74 | void fb_clear_rect(struct fb* fb, unsigned int x, unsigned int y, unsigned int width, unsigned int height); 75 | int fb_resize(struct fb* fb, unsigned int width, unsigned int height); 76 | int fb_coalesce(struct fb* fb, struct llist* fbs); 77 | void fb_copy(struct fb* dst, struct fb* src); 78 | 79 | static inline union fb_pixel fb_get_pixel(struct fb* fb, unsigned int x, unsigned int y) { 80 | assert(x < fb->size.width); 81 | assert(y < fb->size.height); 82 | 83 | return fb->pixels[y * fb->size.width + x]; 84 | } 85 | 86 | #define FB_ALPHA_BLEND_CHANNEL(oldc, newc, olda, newa, outa) \ 87 | (((uint32_t)(newc) * (newa) + (uint32_t)(oldc) * (olda) * (0xff - (newa)) / 255) / (outa)) 88 | 89 | #define FB_ALPHA_BLEND_PIXEL(outpx, newpx, oldpx) do { \ 90 | if (is_big_endian()) { \ 91 | uint8_t outa = (newpx).color_be.alpha + (oldpx).color_be.alpha * (255 - (newpx).color_be.alpha) / 255; \ 92 | \ 93 | if (outa) { \ 94 | (outpx).color_be.color_bgr.blue = FB_ALPHA_BLEND_CHANNEL((oldpx).color_be.color_bgr.blue, (newpx).color_be.color_bgr.blue, (oldpx).color_be.alpha, (newpx).color_be.alpha, outa); \ 95 | (outpx).color_be.color_bgr.green = FB_ALPHA_BLEND_CHANNEL((oldpx).color_be.color_bgr.green, (newpx).color_be.color_bgr.green, (oldpx).color_be.alpha, (newpx).color_be.alpha, outa); \ 96 | (outpx).color_be.color_bgr.red = FB_ALPHA_BLEND_CHANNEL((oldpx).color_be.color_bgr.red, (newpx).color_be.color_bgr.red, (oldpx).color_be.alpha, (newpx).color_be.alpha, outa); \ 97 | } else { \ 98 | (outpx).abgr = 0; \ 99 | } \ 100 | (outpx).color_be.alpha = outa; \ 101 | } else { \ 102 | uint8_t outa = (newpx).color.alpha + (oldpx).color.alpha * (255 - (newpx).color.alpha) / 255; \ 103 | \ 104 | if (outa) { \ 105 | (outpx).color.color_bgr.blue = FB_ALPHA_BLEND_CHANNEL((oldpx).color.color_bgr.blue, (newpx).color.color_bgr.blue, (oldpx).color.alpha, (newpx).color.alpha, outa); \ 106 | (outpx).color.color_bgr.green = FB_ALPHA_BLEND_CHANNEL((oldpx).color.color_bgr.green, (newpx).color.color_bgr.green, (oldpx).color.alpha, (newpx).color.alpha, outa); \ 107 | (outpx).color.color_bgr.red = FB_ALPHA_BLEND_CHANNEL((oldpx).color.color_bgr.red, (newpx).color.color_bgr.red, (oldpx).color.alpha, (newpx).color.alpha, outa); \ 108 | } else { \ 109 | (outpx).abgr = 0; \ 110 | } \ 111 | (outpx).color.alpha = outa; \ 112 | } \ 113 | } while (0) 114 | 115 | // Info 116 | static inline struct fb_size* fb_get_size(struct fb* fb) { 117 | return &fb->size; 118 | } 119 | 120 | static inline union fb_pixel* fb_get_line_base(struct fb* fb, unsigned int line) { 121 | return &fb->pixels[fb->size.width * line]; 122 | } 123 | 124 | // Pixel fmt conversion 125 | #define FB_GRAY8_TO_PIXEL(c) ( 0x000000ff | (c) << 24 | (c) << 16 | (c) << 8 ) 126 | 127 | #endif 128 | -------------------------------------------------------------------------------- /ring.h: -------------------------------------------------------------------------------- 1 | #ifndef _RING_H_ 2 | #define _RING_H_ 3 | 4 | #include 5 | 6 | struct ring { 7 | size_t size; 8 | char* data; 9 | char* ptr_read, *ptr_write; 10 | }; 11 | 12 | int ring_alloc(struct ring** ret, size_t size); 13 | void ring_free(struct ring* ring); 14 | 15 | int ring_peek(struct ring* ring, char* data, size_t len); 16 | int ring_read(struct ring* ring, char* data, size_t len); 17 | int ring_write(struct ring* ring, char* data, size_t len); 18 | 19 | // Optimized inline funnctions 20 | static inline bool ring_any_available(struct ring* ring) { 21 | return ring->ptr_read != ring->ptr_write; 22 | } 23 | 24 | // Take a small peek into the buffer 25 | static inline char ring_peek_one(struct ring* ring) { 26 | return *ring->ptr_read; 27 | } 28 | 29 | // Peek previous byte 30 | static inline char ring_peek_prev(struct ring* ring) { 31 | if(ring->ptr_read - 1 < ring->data) { 32 | return *(ring->data + ring->size - 1); 33 | } 34 | return *(ring->ptr_read - 1); 35 | } 36 | 37 | // Pointer to next byte to read from ringbuffer 38 | static inline char* ring_next(struct ring* ring, char* ptr) { 39 | if(ptr < ring->data + ring->size - 1) { 40 | return ptr + 1; 41 | } 42 | return ring->data; 43 | } 44 | 45 | // Read one byte from the buffer 46 | static inline char ring_read_one(struct ring* ring) { 47 | char c = *ring->ptr_read; 48 | ring->ptr_read = ring_next(ring, ring->ptr_read); 49 | return c; 50 | } 51 | 52 | static inline void ring_inc_read(struct ring* ring) { 53 | ring->ptr_read = ring_next(ring, ring->ptr_read); 54 | } 55 | 56 | // Number of contiguous free bytes after ring->write_ptr 57 | static inline size_t ring_free_space_contig(struct ring* ring) { 58 | if(ring->ptr_read > ring->ptr_write) { 59 | return ring->ptr_read - ring->ptr_write - 1; 60 | } 61 | return ring->size - (ring->ptr_write - ring->data) - (ring->ptr_read == ring->data ? 1 : 0); 62 | } 63 | 64 | static inline void ring_advance_read(struct ring* ring, off_t offset) { 65 | assert(offset >= 0); 66 | assert(offset <= ring->size); 67 | 68 | if(offset) { 69 | if(ring->ptr_read + offset < ring->data + ring->size) { 70 | ring->ptr_read = ring_next(ring, ring->ptr_read + offset - 1); 71 | } else { 72 | ring->ptr_read = offset - ring->size + ring->ptr_read; 73 | } 74 | } 75 | } 76 | 77 | static inline void ring_advance_write(struct ring* ring, off_t offset) { 78 | assert(offset >= 0); 79 | assert(offset <= ring->size); 80 | 81 | if(offset) { 82 | if(ring->ptr_write + offset < ring->data + ring->size) { 83 | ring->ptr_write = ring_next(ring, ring->ptr_write + offset - 1); 84 | } else { 85 | ring->ptr_write = offset - ring->size + ring->ptr_write; 86 | } 87 | } 88 | } 89 | 90 | // Number of bytes that can be read from ringbuffer 91 | static inline size_t ring_available(struct ring* ring) { 92 | if(ring->ptr_write >= ring->ptr_read) { 93 | return ring->ptr_write - ring->ptr_read; 94 | } 95 | return ring->size - (ring->ptr_read - ring->ptr_write); 96 | } 97 | 98 | // Number of virtually contiguous bytes that can be read from ringbuffer 99 | static inline size_t ring_available_contig(struct ring* ring) { 100 | if(ring->ptr_write >= ring->ptr_read) { 101 | return ring->ptr_write - ring->ptr_read; 102 | } 103 | return ring->size - (ring->ptr_read - ring->data); 104 | } 105 | 106 | // Number of free bytes 107 | static inline size_t ring_free_space(struct ring* ring) { 108 | if(ring->ptr_read > ring->ptr_write) { 109 | return ring->ptr_read - ring->ptr_write - 1; 110 | } 111 | return ring->size - (ring->ptr_write - ring->ptr_read) - 1; 112 | } 113 | 114 | 115 | /* 116 | Behaves totally different from memcmp! 117 | Return: 118 | < 0 error (not enough data in buffer) 119 | = 0 match 120 | > 0 no match 121 | */ 122 | static inline int ring_memcmp(struct ring* ring, char* ref, unsigned int len, char** next_pos) { 123 | size_t avail_contig; 124 | 125 | if(ring_available(ring) < len) { 126 | return -EINVAL; 127 | } 128 | 129 | avail_contig = ring_available_contig(ring); 130 | 131 | if(avail_contig >= len) { 132 | // We are lucky 133 | if(memcmp(ring->ptr_read, ref, len)) { 134 | return 1; 135 | } 136 | if(next_pos) { 137 | *next_pos = ring_next(ring, ring->ptr_read + len - 1); 138 | } else { 139 | ring_advance_read(ring, len); 140 | } 141 | return 0; 142 | } 143 | 144 | 145 | // We (may) need to perform two memcmps 146 | if(memcmp(ring->ptr_read, ref, avail_contig) || memcmp(ring->data, ref + avail_contig, len - avail_contig)) { 147 | return 1; 148 | } 149 | if(next_pos) { 150 | *next_pos = ring->data + len - avail_contig; 151 | } else { 152 | ring_advance_read(ring, len); 153 | } 154 | return 0; 155 | } 156 | 157 | #endif 158 | -------------------------------------------------------------------------------- /framebuffer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "framebuffer.h" 8 | 9 | int fb_alloc(struct fb** framebuffer, unsigned int width, unsigned int height) { 10 | int err = 0; 11 | size_t fb_size; 12 | 13 | struct fb* fb = malloc(sizeof(struct fb)); 14 | if(!fb) { 15 | err = -ENOMEM; 16 | goto fail; 17 | } 18 | 19 | fb->size.width = width; 20 | fb->size.height = height; 21 | fb_size = width * height; 22 | 23 | fb->pixels = calloc(width * height, sizeof(union fb_pixel)); 24 | if(!fb->pixels) { 25 | err = -ENOMEM; 26 | goto fail_fb; 27 | } 28 | 29 | while (fb_size--) { 30 | if (is_big_endian()) { 31 | fb->pixels[fb_size].color_be.alpha = 0xff; 32 | } else { 33 | fb->pixels[fb_size].color.alpha = 0xff; 34 | } 35 | } 36 | 37 | fb->numa_node = get_numa_node(); 38 | fb->list = LLIST_ENTRY_INIT; 39 | 40 | *framebuffer = fb; 41 | return 0; 42 | 43 | fail_fb: 44 | free(fb); 45 | fail: 46 | return err; 47 | } 48 | 49 | struct fb* fb_get_fb_on_node(struct llist* fbs, unsigned numa_node) { 50 | struct llist_entry* cursor; 51 | struct fb* fb; 52 | llist_for_each(fbs, cursor) { 53 | fb = llist_entry_get_value(cursor, struct fb, list); 54 | if(fb->numa_node == numa_node) { 55 | return fb; 56 | } 57 | } 58 | return NULL; 59 | } 60 | 61 | void fb_free(struct fb* fb) { 62 | free(fb->pixels); 63 | free(fb); 64 | } 65 | 66 | void fb_free_all(struct llist* fbs) { 67 | struct llist_entry* cursor, *next; 68 | llist_for_each_safe(fbs, cursor, next) { 69 | fb_free((struct fb*)llist_entry_get_value(cursor, struct fb, list)); 70 | } 71 | } 72 | 73 | void fb_set_pixel_rgb(struct fb* fb, unsigned int x, unsigned int y, uint8_t red, uint8_t green, uint8_t blue) { 74 | union fb_pixel* target; 75 | assert(x < fb->size.width); 76 | assert(y < fb->size.height); 77 | 78 | target = &(fb->pixels[y * fb->size.width + x]); 79 | target->color.color_bgr.red = red; 80 | target->color.color_bgr.green = green; 81 | target->color.color_bgr.blue = blue; 82 | } 83 | 84 | void fb_clear_rect(struct fb* fb, unsigned int x, unsigned int y, unsigned int width, unsigned int height) { 85 | while(height--) { 86 | if(y + height >= fb->size.height) { 87 | continue; 88 | } 89 | union fb_pixel* pix = fb_get_line_base(fb, y + height); 90 | pix += x; 91 | memset(pix, 0, sizeof(union fb_pixel) * max(0, min(width, (int)fb->size.width - (int)x))); 92 | } 93 | } 94 | 95 | static void fb_set_size(struct fb* fb, unsigned int width, unsigned int height) { 96 | fb->size.width = width; 97 | fb->size.height = height; 98 | } 99 | 100 | int fb_resize(struct fb* fb, unsigned int width, unsigned int height) { 101 | int err = 0; 102 | union fb_pixel* fbmem, *oldmem; 103 | struct fb_size oldsize = *fb_get_size(fb); 104 | size_t memsize = width * height * sizeof(union fb_pixel); 105 | size_t oldmemsize = oldsize.width * oldsize.height * sizeof(union fb_pixel); 106 | fbmem = malloc(memsize); 107 | if(!fbmem) { 108 | err = -ENOMEM; 109 | goto fail; 110 | } 111 | memset(fbmem, 0, memsize); 112 | 113 | oldmem = fb->pixels; 114 | // Try to prevent oob writes 115 | if(oldmemsize > memsize) { 116 | fb_set_size(fb, width, height); 117 | fb->pixels = fbmem; 118 | } else { 119 | fb->pixels = fbmem; 120 | fb_set_size(fb, width, height); 121 | } 122 | free(oldmem); 123 | fail: 124 | return err; 125 | } 126 | 127 | void fb_copy(struct fb* dst, struct fb* src) { 128 | assert(dst->size.width == src->size.width); 129 | assert(dst->size.height == src->size.height); 130 | memcpy(dst->pixels, src->pixels, dst->size.width * dst->size.height * sizeof(union fb_pixel)); 131 | } 132 | 133 | int fb_coalesce(struct fb* fb, struct llist* fbs) { 134 | struct llist_entry* cursor; 135 | struct fb* other; 136 | size_t i, j, fb_size = fb->size.width * fb->size.height, num_fbs = llist_length(fbs); 137 | unsigned int indices[num_fbs]; 138 | for(i = 0; i < num_fbs; i++) { 139 | indices[i] = i; 140 | } 141 | ARRAY_SHUFFLE(indices, num_fbs); 142 | for(i = 0; i < num_fbs; i++) { 143 | cursor = llist_get_entry(fbs, indices[i]); 144 | other = llist_entry_get_value(cursor, struct fb, list); 145 | if(fb->size.width != other->size.width || fb->size.height != other->size.height) { 146 | return -EINVAL; 147 | } 148 | for(j = 0; j < fb_size; j++) { 149 | if(other->pixels[j].color.alpha == 0) { 150 | continue; 151 | } 152 | #ifdef FEATURE_ALPHA_BLENDING 153 | if(other->pixels[j].color.alpha == 0xff) { 154 | fb->pixels[j] = other->pixels[j]; 155 | } else { 156 | FB_ALPHA_BLEND_PIXEL(fb->pixels[j], other->pixels[j], fb->pixels[j]); 157 | } 158 | #else 159 | fb->pixels[j] = other->pixels[j]; 160 | #endif 161 | // Reset to fully transparent 162 | other->pixels[j].color.alpha = 0; 163 | } 164 | } 165 | return 0; 166 | } 167 | -------------------------------------------------------------------------------- /vnc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "main.h" 5 | #include "vnc.h" 6 | #include "framebuffer.h" 7 | 8 | static const struct frontend_ops fops = { 9 | .alloc = vnc_alloc, 10 | .start = vnc_start, 11 | .free = vnc_free, 12 | .update = vnc_update, 13 | .draw_string = vnc_draw_string, 14 | }; 15 | 16 | static const struct frontend_arg fargs[] = { 17 | { .name = "port", .configure = vnc_configure_port }, 18 | { .name = "font", .configure = vnc_configure_font }, 19 | { .name = "flickerfree", .configure = vnc_configure_flicker }, 20 | { .name = "", .configure = NULL }, 21 | }; 22 | 23 | DECLARE_FRONTEND_SIG_ARGS(front_vnc, "VNC server frontend", &fops, fargs); 24 | 25 | static void set_shared(struct vnc* vnc, bool shared) { 26 | vnc->server->alwaysShared = shared ? TRUE : FALSE; 27 | vnc->server->neverShared = shared ? FALSE : TRUE; 28 | } 29 | 30 | static void pre_display_cb(struct _rfbClientRec* client) { 31 | struct vnc* vnc = client->screen->screenData; 32 | if(vnc->front.sync_overlay_draw) { 33 | pthread_mutex_lock(&vnc->draw_lock); 34 | draw_overlays(vnc->fb_overlay); 35 | } 36 | } 37 | 38 | static void post_display_cb(struct _rfbClientRec* client, int result) { 39 | struct vnc* vnc = client->screen->screenData; 40 | if(vnc->front.sync_overlay_draw) { 41 | pthread_mutex_unlock(&vnc->draw_lock); 42 | } 43 | } 44 | 45 | int vnc_alloc(struct frontend** ret, struct fb* fb, void* priv) { 46 | int err = 0; 47 | struct vnc* vnc = calloc(1, sizeof(struct vnc)); 48 | struct fb_size* size; 49 | if(!vnc) { 50 | err = -ENOMEM; 51 | goto fail; 52 | } 53 | 54 | pthread_mutex_init(&vnc->draw_lock, NULL); 55 | vnc->fb = fb; 56 | size = fb_get_size(fb); 57 | 58 | if((err = fb_alloc(&vnc->fb_overlay, size->width, size->height))) { 59 | goto fail_vnc; 60 | } 61 | 62 | vnc->server = rfbGetScreen(NULL, NULL, size->width, size->height, 8, 3, 4); 63 | if(!vnc->server) { 64 | err = -ENOMEM; 65 | goto fail_fb; 66 | } 67 | 68 | vnc->server->bitsPerPixel = 32; 69 | vnc->server->depth = 24; 70 | rfbPixelFormat* format = &vnc->server->serverFormat; 71 | format->depth = 24; 72 | format->redMax = 0xFF; 73 | format->greenMax = 0xFF; 74 | format->blueMax = 0xFF; 75 | format->redShift = 24; 76 | format->greenShift = 16; 77 | format->blueShift = 8; 78 | 79 | vnc->server->displayHook = pre_display_cb; 80 | vnc->server->displayFinishedHook = post_display_cb; 81 | vnc->server->screenData = vnc; 82 | vnc->server->frameBuffer = (char *)vnc->fb_overlay->pixels; 83 | vnc->server->desktopName = "shoreline"; 84 | set_shared(vnc, true); 85 | 86 | *ret = &vnc->front; 87 | 88 | return 0; 89 | 90 | fail_fb: 91 | fb_free(vnc->fb_overlay); 92 | fail_vnc: 93 | free(vnc); 94 | fail: 95 | return err; 96 | }; 97 | 98 | int vnc_start(struct frontend* front) { 99 | struct vnc* vnc = container_of(front, struct vnc, front); 100 | rfbInitServer(vnc->server); 101 | rfbRunEventLoop(vnc->server, -1, TRUE); 102 | return 0; 103 | } 104 | 105 | void vnc_free(struct frontend* front) { 106 | struct vnc* vnc = container_of(front, struct vnc, front); 107 | rfbShutdownServer(vnc->server, TRUE); 108 | rfbScreenCleanup(vnc->server); 109 | free(vnc); 110 | } 111 | 112 | int vnc_update(struct frontend* front) { 113 | struct vnc* vnc = container_of(front, struct vnc, front); 114 | if(front->sync_overlay_draw) { 115 | pthread_mutex_lock(&vnc->draw_lock); 116 | } 117 | fb_copy(vnc->fb_overlay, vnc->fb); 118 | if(front->sync_overlay_draw) { 119 | pthread_mutex_unlock(&vnc->draw_lock); 120 | } 121 | rfbMarkRectAsModified(vnc->server, 0, 0, vnc->fb->size.width, vnc->fb->size.height); 122 | return !rfbIsActive(vnc->server); 123 | } 124 | 125 | int vnc_draw_string(struct frontend* front, unsigned x, unsigned y, char* str) { 126 | int space, width; 127 | struct vnc* vnc = container_of(front, struct vnc, front); 128 | if(vnc->font) { 129 | space = rfbWidthOfString(vnc->font, " "); 130 | width = rfbWidthOfString(vnc->font, str); 131 | rfbFillRect(vnc->server, x, y, x + width + 2 * space, y + VNC_FONT_HEIGHT + 4, 0x00000000); 132 | rfbDrawString(vnc->server, vnc->font, x + space, y + VNC_FONT_HEIGHT + 2, str, 0xffffffff); 133 | } 134 | return 0; 135 | } 136 | 137 | int vnc_configure_port(struct frontend* front, char* value) { 138 | struct vnc* vnc = container_of(front, struct vnc, front); 139 | if(!value) { 140 | return -EINVAL; 141 | } 142 | 143 | int port = atoi(value); 144 | if(port < 0 || port > 65535) { 145 | return -EINVAL; 146 | } 147 | 148 | vnc->server->port = vnc->server->ipv6port = port; 149 | vnc->server->autoPort = FALSE; 150 | 151 | return 0; 152 | } 153 | 154 | int vnc_configure_font(struct frontend* front, char* value) { 155 | struct vnc* vnc = container_of(front, struct vnc, front); 156 | if(!value) { 157 | return -EINVAL; 158 | } 159 | 160 | vnc->font = rfbLoadConsoleFont(value); 161 | if(!vnc->font) { 162 | return -EINVAL; 163 | } 164 | 165 | return 0; 166 | } 167 | 168 | int vnc_configure_flicker(struct frontend* front, char* value) { 169 | struct vnc* vnc = container_of(front, struct vnc, front); 170 | front->sync_overlay_draw = true; 171 | set_shared(vnc, false); 172 | return 0; 173 | } 174 | -------------------------------------------------------------------------------- /tests/ringbuff/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../../ring.h" 8 | 9 | #define RING_SIZE 256 10 | 11 | char* rand_chars = "0123456789abcdef"; 12 | 13 | void show_ring_stats(struct ring* ring) { 14 | printf("Free bytes: %zu\n", ring_free_space(ring)); 15 | printf("Free (contiguous) bytes: %zu\n", ring_free_space_contig(ring)); 16 | printf("Available bytes: %zu\n", ring_available(ring)); 17 | printf("Available (contiguous) bytes: %zu\n", ring_available_contig(ring)); 18 | } 19 | 20 | int main(int argc, char** argv) { 21 | int err = 0, i, j; 22 | long seed; 23 | struct timeval time; 24 | char strbuff[RING_SIZE + 1]; 25 | strbuff[RING_SIZE] = 0; 26 | 27 | gettimeofday(&time, NULL); 28 | seed = time.tv_sec * 1000000L + time.tv_usec; 29 | 30 | printf("Using seed %ld\n", seed); 31 | srand(seed); 32 | 33 | struct ring* ring; 34 | 35 | if((err = ring_alloc(&ring, RING_SIZE))) { 36 | fprintf(stderr, "Failed to allocate ring buffer\n"); 37 | goto fail; 38 | } 39 | 40 | printf("Ring buffer created\n"); 41 | printf("Data: %p\n", ring->data); 42 | printf("Read ptr: %p\n", ring->ptr_read); 43 | printf("Write ptr: %p\n", ring->ptr_write); 44 | 45 | /* show_ring_stats(ring); 46 | 47 | // =========== 48 | // 16 49 | // =========== 50 | printf("Writing 16 byte string (including \\x00) to ring buffer\n"); 51 | 52 | if((err = ring_write(ring, "___Hello World!", 16))) { 53 | fprintf(stderr, "Write failed. %d => %s\n", err, strerror(err)); 54 | show_ring_stats(ring); 55 | goto fail_ring; 56 | } 57 | 58 | show_ring_stats(ring); 59 | 60 | memset(strbuff, '?', RING_SIZE); 61 | 62 | printf("Reading back 16 byte string from ring buffer\n"); 63 | 64 | if((err = ring_read(ring, strbuff, 16))) { 65 | fprintf(stderr, "Read failed. %d => %s\n", err, strerror(err)); 66 | show_ring_stats(ring); 67 | goto fail_ring; 68 | } 69 | 70 | printf("Length of string is %lu\n", strlen(strbuff)); 71 | printf("String is %s\n", strbuff); 72 | 73 | show_ring_stats(ring); 74 | 75 | // =========== 76 | // 32 77 | // =========== 78 | printf("Writing 32 byte string (including \\x00) to ring buffer\n"); 79 | 80 | if((err = ring_write(ring, "!dlroW olleH______Hello World!", 32))) { 81 | fprintf(stderr, "Write failed. %d => %s\n", err, strerror(err)); 82 | show_ring_stats(ring); 83 | goto fail_ring; 84 | } 85 | 86 | show_ring_stats(ring); 87 | 88 | memset(strbuff, '?', RING_SIZE); 89 | 90 | printf("Reading back 32 byte string from ring buffer\n"); 91 | 92 | if((err = ring_read(ring, strbuff, 32))) { 93 | fprintf(stderr, "Read failed. %d => %s\n", err, strerror(err)); 94 | show_ring_stats(ring); 95 | goto fail_ring; 96 | } 97 | 98 | printf("Length of string is %lu\n", strlen(strbuff)); 99 | printf("String is %s\n", strbuff); 100 | 101 | show_ring_stats(ring); 102 | 103 | // =========== 104 | // 256 105 | // =========== 106 | printf("Writing 256 byte string (including \\x00) to ring buffer\n"); 107 | 108 | if((err = ring_write(ring, "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTUVWXYZ1234567890", 255))) { 109 | fprintf(stderr, "Write failed. %d => %s\n", err, strerror(err)); 110 | show_ring_stats(ring); 111 | goto fail_ring; 112 | } 113 | 114 | show_ring_stats(ring); 115 | 116 | memset(strbuff, '?', RING_SIZE); 117 | 118 | printf("Reading back 256 byte string from ring buffer\n"); 119 | 120 | if((err = ring_read(ring, strbuff, 255))) { 121 | fprintf(stderr, "Read failed. %d => %s\n", err, strerror(err)); 122 | show_ring_stats(ring); 123 | goto fail_ring; 124 | } 125 | 126 | printf("Length of string is %lu\n", strlen(strbuff)); 127 | printf("String is %s\n", strbuff); 128 | 129 | show_ring_stats(ring); 130 | */ 131 | char refbuff[RING_SIZE]; 132 | for(i = 0; i < 1000; i++) { 133 | memset(refbuff, '?', RING_SIZE); 134 | size_t len = rand() % RING_SIZE; 135 | for(j = 0; j < len; j++) { 136 | refbuff[j] = rand_chars[rand() % strlen(rand_chars)]; 137 | } 138 | if(len > 0) 139 | refbuff[len - 1] = 0; 140 | 141 | if((err = ring_write(ring, refbuff, len))) { 142 | fprintf(stderr, "Write failed, len %zu, %s\n", len, strerror(-err)); 143 | goto fail_ring; 144 | } 145 | 146 | if((err = ring_read(ring, strbuff, len))) { 147 | fprintf(stderr, "Read failed, len %zu, %s\n", len, strerror(-err)); 148 | goto fail_ring; 149 | } 150 | 151 | if(ring_available(ring)) { 152 | fprintf(stderr, "There should be no more bytes available but there were (len %zu)\n", len); 153 | goto fail_ring; 154 | } 155 | 156 | printf("Actual: %s\n", strbuff); 157 | printf("Ref: %s\n", refbuff); 158 | 159 | if(memcmp(refbuff, strbuff, len) != 0) { 160 | fprintf(stderr, "Cmp failed, len %zu\n", len); 161 | goto fail_ring; 162 | } 163 | 164 | printf("Round %d passed with length %zu\n", i, len); 165 | } 166 | 167 | 168 | for(i = 0; i < 1000; i++) { 169 | memset(refbuff, '?', RING_SIZE); 170 | size_t len = rand() % RING_SIZE; 171 | for(j = 0; j < len; j++) { 172 | refbuff[j] = rand_chars[rand() % strlen(rand_chars)]; 173 | } 174 | if(len > 0) 175 | refbuff[len - 1] = 0; 176 | 177 | if((err = ring_write(ring, refbuff, len))) { 178 | fprintf(stderr, "Write failed, len %zu, %s\n", len, strerror(-err)); 179 | goto fail_ring; 180 | } 181 | 182 | if(len != ring_available(ring)) { 183 | fprintf(stderr, "Wrong number of bytes available\n"); 184 | goto fail_ring; 185 | } 186 | 187 | if(ring_memcmp(ring, refbuff, ring_available(ring), NULL)) { 188 | fprintf(stderr, "Memcmp failed, len %zu\n", len); 189 | goto fail_ring; 190 | } 191 | 192 | if(ring_available(ring)) { 193 | fprintf(stderr, "There should be no more bytes available but there were (len %zu)\n", len); 194 | goto fail_ring; 195 | } 196 | 197 | printf("Round %d (memcmp) passed with length %zu\n", i, len); 198 | } 199 | 200 | printf("All tests passed!\n"); 201 | 202 | fail_ring: 203 | ring_free(ring); 204 | 205 | fail: 206 | return err; 207 | } 208 | 209 | -------------------------------------------------------------------------------- /linuxfb.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "linuxfb.h" 12 | #include "util.h" 13 | 14 | char* default_fbdev = "/dev/fb0"; 15 | 16 | int linuxfb_alloc(struct frontend** ret, struct fb* fb, void* priv) { 17 | int err; 18 | struct linuxfb* linuxfb = calloc(1, sizeof(struct linuxfb)); 19 | if(!linuxfb) { 20 | fprintf(stderr, "Failed to allocate linuxfb frontend, out of memory\n"); 21 | err = -ENOMEM; 22 | goto fail; 23 | } 24 | 25 | linuxfb->fb = fb; 26 | linuxfb->fbdev = default_fbdev; 27 | linuxfb->fd = -1; 28 | 29 | *ret = &linuxfb->front; 30 | return 0; 31 | 32 | fail: 33 | return err; 34 | } 35 | 36 | static int linuxfb_start(struct frontend* front) { 37 | struct linuxfb* linuxfb = container_of(front, struct linuxfb, front); 38 | char* fbmem; 39 | int err; 40 | int fd = open(linuxfb->fbdev, O_RDWR); 41 | if(fd < 0) { 42 | fprintf(stderr, "Failed to open fbdev '%s': %s(%d)\n", linuxfb->fbdev, strerror(errno), errno); 43 | err = -errno; 44 | goto fail; 45 | } 46 | 47 | if((err = ioctl(fd, FBIOGET_VSCREENINFO, &linuxfb->vscreen) < 0)) { 48 | fprintf(stderr, "Failed to get var screeninfo: %s(%d)\n", strerror(errno), errno); 49 | err = -errno; 50 | goto fail_fd; 51 | } 52 | 53 | switch(linuxfb->vscreen.bits_per_pixel) { 54 | case 8: 55 | if(linuxfb->vscreen.grayscale != 1) { 56 | goto fail_depth; 57 | } 58 | break; 59 | case 16: 60 | case 24: 61 | case 32: 62 | if(linuxfb->vscreen.grayscale != 0) { 63 | goto fail_depth; 64 | } 65 | break; 66 | default: 67 | fail_depth: 68 | fprintf(stderr, "Unsupported bitdepth %u (%s) on fbdev\n", linuxfb->vscreen.bits_per_pixel, 69 | linuxfb->vscreen.grayscale == 0 ? "color" : linuxfb->vscreen.grayscale == 1 ? "grayscale" : "fourcc"); 70 | goto fail_fd; 71 | } 72 | 73 | printf("vscreen offsets:\n"); 74 | printf(" red: %u.%u\n", linuxfb->vscreen.red.offset, linuxfb->vscreen.red.length); 75 | printf(" green: %u.%u\n", linuxfb->vscreen.green.offset, linuxfb->vscreen.green.length); 76 | printf(" blue: %u.%u\n", linuxfb->vscreen.blue.offset, linuxfb->vscreen.blue.length); 77 | 78 | fbmem = calloc(linuxfb->vscreen.bits_per_pixel / 8, linuxfb->vscreen.xres_virtual * linuxfb->vscreen.yres_virtual + linuxfb->pixel_offset); 79 | if(!fbmem) { 80 | fprintf(stderr, "Failed to allocate buffer for fb color format, out of memory\n"); 81 | err = -ENOMEM; 82 | goto fail_fd; 83 | } 84 | 85 | linuxfb->fd = fd; 86 | linuxfb->fbmem = fbmem; 87 | 88 | return 0; 89 | 90 | fail_fd: 91 | close(fd); 92 | fail: 93 | return err; 94 | } 95 | 96 | int linuxfb_update(struct frontend* front) { 97 | struct linuxfb* linuxfb = container_of(front, struct linuxfb, front); 98 | union fb_pixel px; 99 | unsigned int x, y; 100 | ssize_t write_len = 0; 101 | size_t len; 102 | char* fbmem; 103 | unsigned int px_index = linuxfb->pixel_offset; 104 | bool is_be = is_big_endian(); 105 | 106 | px_index += linuxfb->vscreen.yoffset * linuxfb->vscreen.xres_virtual; 107 | for(y = 0; y < min(linuxfb->fb->size.height, linuxfb->vscreen.yres); y++) { 108 | px_index += linuxfb->vscreen.xoffset * (linuxfb->vscreen.bits_per_pixel / 8); 109 | for(x = 0; x < min(linuxfb->fb->size.width, linuxfb->vscreen.xres); x++) { 110 | px = fb_get_pixel(linuxfb->fb, x, y); 111 | switch(linuxfb->vscreen.bits_per_pixel) { 112 | case 16: // BGR 565 113 | if(is_be) { 114 | linuxfb->fbmem[px_index++] = (px.color_be.color_bgr.blue >> 3) | (((px.color_be.color_bgr.green >> 2) & 0x07) << 5); 115 | linuxfb->fbmem[px_index++] = (((px.color_be.color_bgr.green >> 2) & 0x38) >> 3) | (px.color_be.color_bgr.red & 0xF8); 116 | } else { 117 | linuxfb->fbmem[px_index++] = (px.color.color_bgr.blue >> 3) | (((px.color.color_bgr.green >> 2) & 0x07) << 5); 118 | linuxfb->fbmem[px_index++] = (((px.color.color_bgr.green >> 2) & 0x38) >> 3) | (px.color.color_bgr.red & 0xF8); 119 | } 120 | break; 121 | case 32: // ABGR 8888 122 | if(is_be) { 123 | linuxfb->fbmem[px_index++] = px.color_be.alpha; 124 | } else { 125 | linuxfb->fbmem[px_index++] = px.color.alpha; 126 | } 127 | case 24: // BGR 888 128 | if(is_be) { 129 | linuxfb->fbmem[px_index++] = px.color_be.color_bgr.blue; 130 | linuxfb->fbmem[px_index++] = px.color_be.color_bgr.green; 131 | linuxfb->fbmem[px_index++] = px.color_be.color_bgr.red; 132 | } else { 133 | linuxfb->fbmem[px_index++] = px.color.color_bgr.blue; 134 | linuxfb->fbmem[px_index++] = px.color.color_bgr.green; 135 | linuxfb->fbmem[px_index++] = px.color.color_bgr.red; 136 | } 137 | break; 138 | case 8: // 8 bit grayscale 139 | // interprete red channel only. While this is not correct it is at least something 140 | if(is_be) { 141 | linuxfb->fbmem[px_index++] = px.color_be.color_bgr.red; 142 | } else { 143 | linuxfb->fbmem[px_index++] = px.color.color_bgr.red; 144 | } 145 | break; 146 | default: 147 | fprintf(stderr, "Invalid pixel format %u. This should not happen!\n", linuxfb->vscreen.bits_per_pixel); 148 | return -EINVAL; 149 | } 150 | } 151 | px_index += (linuxfb->vscreen.xres_virtual - x) * (linuxfb->vscreen.bits_per_pixel / 8); 152 | } 153 | 154 | fbmem = linuxfb->fbmem; 155 | len = linuxfb->vscreen.bits_per_pixel / 8 * linuxfb->vscreen.xres_virtual * linuxfb->vscreen.yres_virtual; 156 | lseek(linuxfb->fd, 0, SEEK_SET); 157 | while(len > 0) { 158 | if((write_len = write(linuxfb->fd, fbmem, len)) < 0) { 159 | break; 160 | } 161 | len -= write_len; 162 | fbmem += write_len; 163 | } 164 | if(write_len < 0) { 165 | fprintf(stderr, "Write to fbdev failed: %s(%d)\n", strerror(errno), errno); 166 | return -errno; 167 | } 168 | 169 | return 0; 170 | } 171 | 172 | static int configure_fbdev(struct frontend* front, char* value) { 173 | struct linuxfb* linuxfb = container_of(front, struct linuxfb, front); 174 | int err; 175 | char* fbdev = strdup(value); 176 | if(!fbdev) { 177 | fprintf(stderr, "Failed to allocate space for fb device path, out of memory\n"); 178 | err = -ENOMEM; 179 | goto fail; 180 | } 181 | 182 | linuxfb->fbdev = fbdev; 183 | return 0; 184 | 185 | fail: 186 | return err; 187 | } 188 | 189 | static int configure_offset(struct frontend* front, char* value) { 190 | struct linuxfb* linuxfb = container_of(front, struct linuxfb, front); 191 | int offset = atoi(value); 192 | if(offset < 0) { 193 | fprintf(stderr, "Offset mut be positive\n"); 194 | return -EINVAL; 195 | } 196 | 197 | linuxfb->pixel_offset = offset; 198 | return 0; 199 | } 200 | 201 | void linuxfb_free(struct frontend* front) { 202 | struct linuxfb* linuxfb = container_of(front, struct linuxfb, front); 203 | if(linuxfb->fbdev != default_fbdev) { 204 | free(linuxfb->fbdev); 205 | } 206 | if(linuxfb->fd > 0) { 207 | close(linuxfb->fd); 208 | } 209 | free(linuxfb); 210 | } 211 | 212 | static const struct frontend_ops fops = { 213 | .alloc = linuxfb_alloc, 214 | .start = linuxfb_start, 215 | .free = linuxfb_free, 216 | .update = linuxfb_update, 217 | }; 218 | 219 | static const struct frontend_arg fargs[] = { 220 | { .name = "fb", .configure = configure_fbdev }, 221 | { .name = "offset", .configure = configure_offset }, 222 | { .name = "", .configure = NULL }, 223 | }; 224 | 225 | DECLARE_FRONTEND_SIG_ARGS(front_linuxfb, "Linux framebuffer frontend", &fops, fargs); 226 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Shoreline 2 | ========= 3 | 4 | A very fast pixelflut server with full IPv6 support written in C 5 | 6 | # Compiling 7 | 8 | ## Dependencies 9 | 10 | * SDL2 11 | * libpthread 12 | * libvncserver 13 | * libnuma (numactl) 14 | * libfreetype2 15 | 16 | On \*buntu/Debian distros use `sudo apt install git build-essential libsdl2-dev libpthread-stubs0-dev libvncserver-dev libnuma-dev libfreetype6-dev` to install the dependencies. 17 | 18 | Use ```make``` to build shoreline 19 | 20 | Optionally the following environment variables can be set to control how shoreline is built: 21 | 22 | * INCLUDE_DIR: Allows to select an include dir other than /usr/include 23 | * OPTFLAGS: Allows to set the optimization flags used for this build 24 | * FEATURES: Allows to select what features shoreline will be built with (see Makefile for available features) 25 | 26 | # Usage 27 | 28 | By default Shoreline runs in headless mode. In headless mode all user frontends are disabled. Use ```shoreline -f sdl``` to get a sdl window for drawing 29 | 30 | There are a few more commandline switches: 31 | 32 | ``` 33 | Options: 34 | -p Port to listen on (default 1234) 35 | -b
Address to listen on (default ::) 36 | -w Width of drawing surface (default 1024) 37 | -h Height of drawing surface (default 768) 38 | -r Screen update rate in HZ (default 60) 39 | -s Size of network ring buffer in bytes (default 65536) 40 | -l Number of threads used to listen for incoming connections (default 10) 41 | -f Frontend to use as a display. May be specified multiple times. Use -f ? to list available frontends and options 42 | -t Enable fancy text rendering using TTF, OTF or CFF font from 43 | -d Set description text to be displayed in upper left corner (default https://github.com/TobleMiner/shoreline) 44 | -? Show this help 45 | ``` 46 | 47 | ## Frontend options 48 | 49 | When specifying a frontend frontend-specific options may be passed to the frontend. For example the VNC frontend can be configured 50 | to use a nonstandard port: 51 | 52 | `shoreline -f vnc,port=2342` 53 | 54 | All available frontends and their options can be listed using `shoreline -f ?`. 55 | 56 | ## Supported Pixelflut commands 57 | 58 | ``` 59 | PX # Set pixel @(x,y) to specified hex color 60 | PX # Get pixel @(x,y) as hex 61 | SIZE # Get size of drawing surface 62 | OFFSET # Apply offset (x,y) to all further pixel draws on this connection 63 | ``` 64 | 65 | ## Alpha blending 66 | 67 | By default alpha blending is disabled. This might cause images that contain transparency to be displayed incorrectly. You can enable alpha blending 68 | at compile time by adding `ALPHA_BLENDING` to the `FEATURES` environment variable. However, be warned this will have a large impact on performance 69 | since each pixel drawn turns into a memory read, modify, write instead of just a memory write. 70 | 71 | ## Statistics 72 | 73 | To enable on-screen statistics display shoreline needs to be passed a TTF (other formats supported by libfreetype2 will work, too) font via the `-t` option. 74 | 75 | When running a system with a graphical frontend chances are you do have some TTF fonts in `/usr/share/fonts/TTF/`. 76 | 77 | ### Statistics API 78 | 79 | There is a special frontend called `statistics`. When enabled it serves a simple TCP based statistics API (default port 1235). Upon 80 | connecting to the statistics API it dumps a JSON object and closes the connection. 81 | 82 | This is an example JSON object returned by the API: 83 | 84 | ``` 85 | { 86 | "traffic": { 87 | "bytes": 7292518314, 88 | "pixels": 405139493 89 | }, 90 | "throughput": { 91 | "bytes": 512289334, 92 | "pixels": 28460518 93 | }, 94 | "connections": 10, 95 | "fps": 59 96 | } 97 | ``` 98 | 99 | ## Container setup 100 | 101 | The awesome Sebastian Bernauer built a Docker based environment that makes 102 | running shoreline with fancy monitoring and colorful statistics a breeze: 103 | [pixelflut-infrastructure](https://github.com/sbernauer/pixelflut-infrastructure) 104 | 105 | For optimum performance shoreline should be run on a host separate from the Docker 106 | host. Trying to run shoreline inside a Docker container might impact performance 107 | severely depending on your hardware and Docker configuration. 108 | 109 | # Troubleshooting 110 | 111 | When using shoreline with a lot of clients default resource limits applied to processes may not be sufficient. 112 | 113 | ## sysctl 114 | 115 | The default maximum number of allowed vm mappings may not be enough. Consider increasing it by setting 116 | 117 | ``` 118 | vm.max_map_count = 1000000 119 | ``` 120 | 121 | in `/etc/sysctl.d/50-vm.conf` and reloading it using `sysctl --system`. 122 | 123 | ## security/limits 124 | 125 | Shoreline can hit the maximum number of allowed file descriptors quite easily. Increase it by setting 126 | 127 | ``` 128 | * soft nofile 262144 129 | * hard nofile 262144 130 | ``` 131 | in `/etc/security/limits.conf`. 132 | 133 | ## systemd 134 | 135 | When running shoreline through a systemd service you will need to increase the default cgroup limits for larger installations. 136 | Consider adding the following to the `[Service]` section of your shoreline unit file. 137 | 138 | ``` 139 | TasksMax=infinity 140 | LimitSTACK=infinity 141 | LimitNOFILE=infinity 142 | LimitNPROC=infinity 143 | ``` 144 | 145 | ## Hardware vulnerability mitigations 146 | 147 | Over the past years an ever-growing set of mitigations for hardware vulnerabilities have been added to Linux. If you are only 148 | running trusted code on your system you might want to add `mitigations=off` to your kernel cmdline. This can result in a 149 | considerable performance increase. On Debian this change can by persisted by adding `mitigations=off` to GRUB_CMDLINE_LINUX in 150 | `/etc/default/grub` and running `update-grub`. 151 | 152 | ## Reliability 153 | 154 | Misbehaving VNC clients can occasionally trigger assertions in the VNC server library. When running shoreline with the VNC frontend 155 | exposed to arbitrary clients, adding a VNC multiplexer like [VNCmux](https://github.com/TobleMiner/vncmux/) can improve reliability of 156 | shoreline considerably. 157 | 158 | # IP-Based Rate-Limiting 159 | 160 | In order to rate-limit every IP to 10 established connections, you can simply use iptables: 161 | 162 | `iptables -A INPUT -p tcp -m tcp --dport 1234 --tcp-flags FIN,SYN,RST,ACK SYN -m connlimit --connlimit-above 10 --connlimit-mask 32 --connlimit-saddr -j REJECT --reject-with icmp-port-unreachable` 163 | `ip6tables -A INPUT -p tcp -m tcp --dport 1234 --tcp-flags FIN,SYN,RST,ACK SYN -m connlimit --connlimit-above 10 --connlimit-mask 128 --connlimit-saddr -j REJECT --reject-with icmp6-port-unreachable` 164 | 165 | # Performance 166 | 167 | Shoreline can easily handle full 10G line speed traffic on half way decent hardware (i7-6700, 32 GB dual channel DDR4 memory @2400 MHz) 168 | 169 | On more beefy hardware (2x AMD EPYC 7821, 10x 8GB DDR4 ECC memory @2666 MHz, 6 memory channels) we are at about 37 Gbit/s 170 | 171 | These results were obtained using [Sturmflut](https://github.com/TobleMiner/sturmflut) as a client 172 | 173 | ## VNC 174 | 175 | With many VNC clients performance can degrade. Running a VNC multiplexer like [VNCmux](https://github.com/TobleMiner/vncmux/), even on the same host, 176 | can improve performance drastically. 177 | -------------------------------------------------------------------------------- /statistics.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "statistics.h" 9 | #include "llist.h" 10 | #include "main.h" 11 | 12 | #define STATISTICS_API_LISTEN_PORT_DEFAULT "1235" 13 | #define STATISTICS_API_LISTEN_ADDRESS_DEFAULT "::" 14 | 15 | static const char* UNITS[] = { 16 | "", 17 | "k", 18 | "M", 19 | "G", 20 | "T", 21 | "P" 22 | }; 23 | 24 | void statistics_update(struct statistics* stats, struct net* net) { 25 | int i = net->num_threads; 26 | struct timespec now; 27 | unsigned long long bytes_prev = stats->num_bytes; 28 | unsigned long long num_connections = 0; 29 | #ifdef FEATURE_PIXEL_COUNT 30 | unsigned long long pixels_prev = stats->num_pixels; 31 | struct llist_entry* cursor; 32 | #endif 33 | 34 | clock_gettime(CLOCK_MONOTONIC, &now); 35 | while(i-- > 0) { 36 | struct net_thread* thread = &net->threads[i]; 37 | struct llist* threadlist = thread->threadlist; 38 | struct llist_entry* cursor; 39 | 40 | if(thread->initialized) { 41 | llist_lock(threadlist); 42 | llist_for_each(threadlist, cursor) { 43 | struct net_connection_thread* conn_thread = llist_entry_get_value(cursor, struct net_connection_thread, list); 44 | stats->num_bytes += conn_thread->byte_count; 45 | conn_thread->byte_count = 0; 46 | num_connections++; 47 | } 48 | llist_unlock(threadlist); 49 | } 50 | } 51 | stats->num_connections = num_connections; 52 | stats->bytes_per_second[stats->average_index] = (stats->num_bytes - bytes_prev) * 1000000000UL / get_timespec_diff(&now, &stats->last_update); 53 | 54 | #ifdef FEATURE_PIXEL_COUNT 55 | llist_lock(net->fb_list); 56 | llist_for_each(net->fb_list, cursor) { 57 | struct fb* fb = llist_entry_get_value(cursor, struct fb, list); 58 | stats->num_pixels += fb->pixel_count; 59 | fb->pixel_count = 0; 60 | } 61 | llist_unlock(net->fb_list); 62 | stats->pixels_per_second[stats->average_index] = (stats->num_pixels - pixels_prev) * 1000000000UL / get_timespec_diff(&now, &stats->last_update); 63 | #else 64 | // Do a crude estimation if actual pixel count is not available. Average Pixel is about |PX XXX YYY rrggbb\n| = 18 bytes 65 | stats->num_pixels += (stats->num_bytes - bytes_prev) / 18; 66 | stats->pixels_per_second[stats->average_index] = stats->bytes_per_second[stats->average_index] / 18; 67 | #endif 68 | stats->frames_per_second[stats->average_index] = (stats->num_frames - stats->last_num_frames) * 1000000000UL / get_timespec_diff(&now, &stats->last_update); 69 | 70 | stats->average_index++; 71 | stats->average_index %= STATISTICS_NUM_AVERAGES; 72 | stats->last_update = now; 73 | stats->last_num_frames = stats->num_frames; 74 | } 75 | 76 | #define GET_AVERAGE(var, stats, field) \ 77 | do { \ 78 | int i_; \ 79 | (var) = 0; \ 80 | for(i_ = 0; i_ < STATISTICS_NUM_AVERAGES; i_++) { \ 81 | (var) += ((stats)->field)[i_]; \ 82 | } \ 83 | (var) /= STATISTICS_NUM_AVERAGES; \ 84 | } while(0) 85 | 86 | #define DEFINE_UNIT(base) \ 87 | static const char* value_get_unit_##base(unsigned long long value) { \ 88 | int i = 0; \ 89 | while(value > base) { \ 90 | if(i == ARRAY_LEN(UNITS) - 1) { \ 91 | break; \ 92 | } \ 93 | i++; \ 94 | value /= base; \ 95 | } \ 96 | return UNITS[i]; \ 97 | } \ 98 | static double value_get_scaled_##base(unsigned long long value) { \ 99 | double val = value; \ 100 | int i = 0; \ 101 | while(val > base) { \ 102 | i++; \ 103 | if(i >= ARRAY_LEN(UNITS)) { \ 104 | break; \ 105 | } \ 106 | val /= base; \ 107 | } \ 108 | return val; \ 109 | } 110 | 111 | DEFINE_UNIT(1024); 112 | DEFINE_UNIT(1000); 113 | 114 | const char* statistics_traffic_get_unit(struct statistics* stats) { 115 | return value_get_unit_1024(stats->num_bytes); 116 | } 117 | 118 | double statistics_traffic_get_scaled(struct statistics* stats) { 119 | return value_get_scaled_1024(stats->num_bytes); 120 | } 121 | 122 | const char* statistics_throughput_get_unit(struct statistics* stats) { 123 | unsigned long long bytes_per_second; 124 | GET_AVERAGE(bytes_per_second, stats, bytes_per_second); 125 | return value_get_unit_1000(bytes_per_second * 8ULL); 126 | } 127 | 128 | double statistics_throughput_get_scaled(struct statistics* stats) { 129 | unsigned long long bytes_per_second; 130 | GET_AVERAGE(bytes_per_second, stats, bytes_per_second); 131 | return value_get_scaled_1000(bytes_per_second * 8ULL); 132 | } 133 | 134 | const char* statistics_pixels_get_unit(struct statistics* stats) { 135 | return value_get_unit_1000(stats->num_pixels); 136 | } 137 | 138 | double statistics_pixels_get_scaled(struct statistics* stats) { 139 | return value_get_scaled_1000(stats->num_pixels); 140 | } 141 | 142 | const char* statistics_pps_get_unit(struct statistics* stats) { 143 | unsigned long long pixels_per_second; 144 | GET_AVERAGE(pixels_per_second, stats, pixels_per_second); 145 | return value_get_unit_1000(pixels_per_second); 146 | } 147 | 148 | double statistics_pps_get_scaled(struct statistics* stats) { 149 | unsigned long long pixels_per_second; 150 | GET_AVERAGE(pixels_per_second, stats, pixels_per_second); 151 | return value_get_scaled_1000(pixels_per_second); 152 | } 153 | 154 | int statistics_get_frames_per_second(struct statistics* stats) { 155 | unsigned int fps; 156 | GET_AVERAGE(fps, stats, frames_per_second); 157 | return fps; 158 | } 159 | 160 | 161 | static int statistics_frontend_alloc(struct frontend** ret, struct fb* fb, void* priv) { 162 | struct statistics_frontend* sfront = calloc(1, sizeof(struct statistics_frontend)); 163 | if(!sfront) { 164 | return -ENOMEM; 165 | } 166 | sfront->socket = -1; 167 | sfront->listen_port = STATISTICS_API_LISTEN_PORT_DEFAULT; 168 | sfront->listen_address = STATISTICS_API_LISTEN_ADDRESS_DEFAULT; 169 | pthread_mutex_init(&sfront->stats_lock, NULL); 170 | *ret = &sfront->front; 171 | return 0; 172 | } 173 | 174 | #define API_STRBUF_LEN 1024 175 | 176 | static void* api_thread(void* args) { 177 | struct statistics_frontend* sfront = args; 178 | char strbuf[API_STRBUF_LEN]; 179 | unsigned long long bytes_per_second; 180 | unsigned long long pixels_per_second; 181 | unsigned long long frames_per_second; 182 | 183 | while(!sfront->exit) { 184 | size_t len; 185 | ssize_t write_len; 186 | char* buf = strbuf; 187 | int sock = accept(sfront->socket, NULL, NULL); 188 | if(sock < 0) { 189 | fprintf(stderr, "Listener failed, bailing out: %d => %s\n", errno, strerror(errno)); 190 | break; 191 | } 192 | pthread_mutex_lock(&sfront->stats_lock); 193 | GET_AVERAGE(bytes_per_second, &sfront->stats, bytes_per_second); 194 | GET_AVERAGE(pixels_per_second, &sfront->stats, pixels_per_second); 195 | GET_AVERAGE(frames_per_second, &sfront->stats, frames_per_second); 196 | 197 | len = snprintf(strbuf, sizeof(strbuf), "{ \"traffic\": { \"bytes\": %llu, \"pixels\": %llu }, " 198 | "\"throughput\": { \"bytes\": %llu, \"pixels\": %llu }, \"connections\": %llu, \"fps\": %llu }\n", 199 | sfront->stats.num_bytes, sfront->stats.num_pixels, 200 | bytes_per_second, pixels_per_second, 201 | sfront->stats.num_connections, 202 | frames_per_second); 203 | pthread_mutex_unlock(&sfront->stats_lock); 204 | while(len > 0) { 205 | write_len = write(sock, buf, len); 206 | if(write_len < 0) { 207 | fprintf(stderr, "Failed to write to API client: %d => %s\n", errno, strerror(errno)); 208 | break; 209 | } 210 | len -= write_len; 211 | buf += write_len; 212 | } 213 | shutdown(sock, SHUT_RDWR); 214 | close(sock); 215 | } 216 | return NULL; 217 | } 218 | 219 | static int statistics_frontend_start(struct frontend* front) { 220 | struct statistics_frontend* sfront = container_of(front, struct statistics_frontend, front); 221 | int err; 222 | int sock; 223 | int one = 1; 224 | struct addrinfo* addr_list; 225 | size_t addr_len; 226 | struct sockaddr_storage* listen_addr; 227 | 228 | if((err = -getaddrinfo(sfront->listen_address, sfront->listen_port, NULL, &addr_list))) { 229 | fprintf(stderr, "Failed to resolve listen address for statistics '%s', %d => %s\n", sfront->listen_address, err, gai_strerror(-err)); 230 | goto fail; 231 | } 232 | sfront->addr_list = addr_list; 233 | listen_addr = (struct sockaddr_storage*)addr_list->ai_addr; 234 | addr_len = addr_list->ai_addrlen; 235 | 236 | if((sock = socket(listen_addr->ss_family, SOCK_STREAM, 0)) < 0) { 237 | err = -errno; 238 | goto fail_addrlist; 239 | } 240 | setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)); 241 | 242 | if(bind(sock, (struct sockaddr*)listen_addr, addr_len) < 0) { 243 | fprintf(stderr, "Failed to bind to %s:%s %d => %s\n", sfront->listen_address, sfront->listen_port, errno, strerror(errno)); 244 | err = -errno; 245 | goto fail_socket; 246 | } 247 | 248 | if(listen(sock, 10)) { 249 | fprintf(stderr, "Failed to start listening: %d => %s\n", errno, strerror(errno)); 250 | err = -errno; 251 | goto fail_socket; 252 | } 253 | sfront->socket = sock; 254 | 255 | if((err = -pthread_create(&sfront->listen_thread, NULL, api_thread, sfront))) { 256 | goto fail_socket; 257 | } 258 | sfront->thread_created = true; 259 | 260 | return 0; 261 | 262 | fail_socket: 263 | close(sock); 264 | sfront->socket = -1; 265 | fail_addrlist: 266 | freeaddrinfo(addr_list); 267 | sfront->addr_list = NULL; 268 | fail: 269 | return err; 270 | } 271 | 272 | static void statistics_frontend_free(struct frontend* front) { 273 | struct statistics_frontend* sfront = container_of(front, struct statistics_frontend, front); 274 | sfront->exit = true; 275 | if(sfront->thread_created) { 276 | pthread_cancel(sfront->listen_thread); 277 | pthread_join(sfront->listen_thread, NULL); 278 | } 279 | if(sfront->socket >= 0) { 280 | close(sfront->socket); 281 | } 282 | if(sfront->addr_list) { 283 | freeaddrinfo(sfront->addr_list); 284 | } 285 | free(sfront); 286 | } 287 | 288 | static int statistics_frontend_update_statistics(struct frontend* front) { 289 | struct statistics_frontend* sfront = container_of(front, struct statistics_frontend, front); 290 | pthread_mutex_lock(&sfront->stats_lock); 291 | sfront->stats = stats; 292 | pthread_mutex_unlock(&sfront->stats_lock); 293 | return 0; 294 | } 295 | 296 | static int statistics_frontend_configure_port(struct frontend* front, char* value) { 297 | struct statistics_frontend* sfront = container_of(front, struct statistics_frontend, front); 298 | if(!value) { 299 | return -EINVAL; 300 | } 301 | 302 | int port = atoi(value); 303 | if(port < 0 || port > 65535) { 304 | return -EINVAL; 305 | } 306 | 307 | sfront->listen_port = value; 308 | 309 | return 0; 310 | } 311 | 312 | static int statistics_frontend_configure_listen_address(struct frontend* front, char* value) { 313 | struct statistics_frontend* sfront = container_of(front, struct statistics_frontend, front); 314 | if(!value) { 315 | return -EINVAL; 316 | } 317 | 318 | sfront->listen_address = value; 319 | return 0; 320 | } 321 | 322 | static const struct frontend_ops fops = { 323 | .alloc = statistics_frontend_alloc, 324 | .start = statistics_frontend_start, 325 | .free = statistics_frontend_free, 326 | .update = statistics_frontend_update_statistics, 327 | }; 328 | 329 | static const struct frontend_arg fargs[] = { 330 | { .name = "port", .configure = statistics_frontend_configure_port }, 331 | { .name = "listen", .configure = statistics_frontend_configure_listen_address }, 332 | { .name = "", .configure = NULL }, 333 | }; 334 | 335 | DECLARE_FRONTEND_SIG_ARGS(front_statistics, "Statistics API provider frontend", &fops, fargs); 336 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "framebuffer.h" 17 | #ifdef FEATURE_SDL 18 | #include "sdl.h" 19 | #endif 20 | #include "network.h" 21 | #include "llist.h" 22 | #include "util.h" 23 | #include "frontend.h" 24 | #include "workqueue.h" 25 | #ifdef FEATURE_TTF 26 | #include "textrender.h" 27 | #endif 28 | #ifdef FEATURE_STATISTICS 29 | #include "statistics.h" 30 | #endif 31 | 32 | 33 | #define PORT_DEFAULT "1234" 34 | #define LISTEN_DEFAULT "::" 35 | #define RATE_DEFAULT 60 36 | #define WIDTH_DEFAULT 1024 37 | #define HEIGHT_DEFAULT 768 38 | #define RINGBUFFER_DEFAULT 65536 39 | #define LISTEN_THREADS_DEFAULT 10 40 | #define MAX_STAT_LENGTH 265 41 | 42 | #define MAX_FRONTENDS 16 43 | 44 | #define REPO_URL "https://github.com/TobleMiner/shoreline" 45 | 46 | static char* default_description = REPO_URL; 47 | 48 | static bool do_exit = false; 49 | 50 | extern struct frontend_id frontends[]; 51 | 52 | void show_frontends() { 53 | fprintf(stderr, "Available frontends:\n"); 54 | struct frontend_id* front = frontends; 55 | for(; front->def != NULL; front++) { 56 | const struct frontend_arg* options = front->def->args; 57 | fprintf(stderr, "\t%s: %s ", front->id, front->def->name); 58 | if(options) { 59 | fprintf(stderr, "(Options:"); 60 | while(strlen(options->name)) { 61 | fprintf(stderr, " %s", options->name); 62 | options++; 63 | } 64 | fprintf(stderr, ")"); 65 | } 66 | fprintf(stderr, "\n"); 67 | } 68 | } 69 | 70 | void doshutdown(int sig) 71 | { 72 | printf("Shutting down\n"); 73 | do_exit = true; 74 | } 75 | 76 | void show_usage(char* binary) { 77 | fprintf(stderr, "Usage: %s [-p ] [-b ] [-w ] [-h ] [-r ] "\ 78 | "[-s ] [-l ] [-f ] [-t ] [-d ] [-?]\n", binary); 79 | fprintf(stderr, "Options:\n"); 80 | fprintf(stderr, " -p Port to listen on (default %s)\n", PORT_DEFAULT); 81 | fprintf(stderr, " -b
Address to listen on (default %s)\n", LISTEN_DEFAULT); 82 | fprintf(stderr, " -w Width of drawing surface (default %u)\n", WIDTH_DEFAULT); 83 | fprintf(stderr, " -h Height of drawing surface (default %u)\n", HEIGHT_DEFAULT); 84 | fprintf(stderr, " -r Screen update rate in HZ (default %u)\n", RATE_DEFAULT); 85 | fprintf(stderr, " -s Size of network ring buffer in bytes (default %u)\n", RINGBUFFER_DEFAULT); 86 | fprintf(stderr, " -l Number of threads used to listen for incoming connections (default %u)\n", LISTEN_THREADS_DEFAULT); 87 | fprintf(stderr, " -f Frontend to use as a display. May be specified multiple times. "\ 88 | "Use -f ? to list available frontends and options\n"); 89 | fprintf(stderr, " -t Enable fancy text rendering using TTF, OTF or CFF font from \n"); 90 | fprintf(stderr, " -d Set description text to be displayed in upper left corner (default %s)\n", REPO_URL); 91 | fprintf(stderr, " -? Show this help\n"); 92 | } 93 | 94 | struct resize_wq_priv { 95 | struct fb* fb; 96 | struct fb_size size; 97 | }; 98 | 99 | int resize_wq_cb(void* priv) { 100 | struct resize_wq_priv* resize_priv = priv; 101 | return fb_resize(resize_priv->fb, resize_priv->size.width, resize_priv->size.height); 102 | } 103 | 104 | int resize_wq_err(int err, void* priv) { 105 | doshutdown(SIGTERM); 106 | return err; 107 | } 108 | 109 | void resize_wq_cleanup(int err, void* priv) { 110 | free(priv); 111 | } 112 | 113 | #ifdef FEATURE_SDL 114 | int resize_cb(struct sdl* sdl, unsigned int width, unsigned int height) { 115 | struct llist* fb_list = sdl->cb_private; 116 | struct llist_entry* cursor; 117 | struct fb* fb; 118 | int err = 0; 119 | 120 | llist_for_each(fb_list, cursor) { 121 | struct resize_wq_priv* resize_priv = malloc(sizeof(struct resize_wq_priv)); 122 | if(!resize_priv) { 123 | err = -ENOMEM; 124 | goto fail; 125 | } 126 | 127 | resize_priv->size.width = width; 128 | resize_priv->size.height = height; 129 | fb = llist_entry_get_value(cursor, struct fb, list); 130 | resize_priv->fb = fb; 131 | 132 | // TODO: Add error callback 133 | if((err = workqueue_enqueue(fb->numa_node, resize_priv, resize_wq_cb, resize_wq_err, resize_wq_cleanup))) { 134 | free(resize_priv); 135 | goto fail; 136 | } 137 | } 138 | 139 | fail: 140 | return err; 141 | } 142 | #endif 143 | 144 | #ifdef FEATURE_STATISTICS 145 | struct statistics stats = { 0 }; 146 | #endif 147 | #ifdef FEATURE_TTF 148 | struct textrender* txtrndr = NULL; 149 | #endif 150 | char* description = REPO_URL; 151 | 152 | void draw_overlays(struct fb* fb) { 153 | #ifdef FEATURE_STATISTICS 154 | char stat_line[MAX_STAT_LENGTH]; 155 | snprintf(stat_line, sizeof(stat_line), "Traffic: %.3f %sB / %.3f %sPixels " 156 | "Throughput: %.3f %sb/s / %.3f %sPixels/s FPS: %d frames/s %llu connections", 157 | statistics_traffic_get_scaled(&stats), statistics_traffic_get_unit(&stats), 158 | statistics_pixels_get_scaled(&stats), statistics_pixels_get_unit(&stats), 159 | statistics_throughput_get_scaled(&stats), statistics_throughput_get_unit(&stats), 160 | statistics_pps_get_scaled(&stats), statistics_pps_get_unit(&stats), 161 | statistics_get_frames_per_second(&stats), stats.num_connections); 162 | #endif 163 | #ifdef FEATURE_TTF 164 | if(txtrndr) { 165 | textrender_draw_string(txtrndr, fb, 100, fb->size.height / 20, description, 16); 166 | #ifdef FEATURE_STATISTICS 167 | textrender_draw_string(txtrndr, fb, 100, fb->size.height - fb->size.height / 10, stat_line, 16); 168 | #endif 169 | } 170 | #endif 171 | } 172 | 173 | int main(int argc, char** argv) { 174 | int err, opt; 175 | struct fb* fb; 176 | struct llist fb_list; 177 | struct sockaddr_storage* inaddr; 178 | struct addrinfo* addr_list; 179 | struct net* net; 180 | struct llist fronts; 181 | struct llist_entry* cursor, *next; 182 | struct frontend* front; 183 | #ifdef FEATURE_SDL 184 | struct sdl_param sdl_param; 185 | #endif 186 | size_t addr_len; 187 | #ifdef FEATURE_STATISTICS 188 | char stat_line[MAX_STAT_LENGTH]; 189 | #endif 190 | unsigned int frontend_cnt = 0; 191 | char* frontend_names[MAX_FRONTENDS]; 192 | bool handle_signals = true; 193 | 194 | char* port = PORT_DEFAULT; 195 | char* listen_address = LISTEN_DEFAULT; 196 | 197 | int width = WIDTH_DEFAULT; 198 | int height = HEIGHT_DEFAULT; 199 | int screen_update_rate = RATE_DEFAULT; 200 | 201 | int ringbuffer_size = RINGBUFFER_DEFAULT; 202 | int listen_threads = LISTEN_THREADS_DEFAULT; 203 | 204 | struct timespec before, after; 205 | long long time_delta; 206 | 207 | while((opt = getopt(argc, argv, "p:b:w:h:r:s:l:f:t:d:?")) != -1) { 208 | switch(opt) { 209 | case('p'): 210 | port = optarg; 211 | break; 212 | case('b'): 213 | listen_address = optarg; 214 | break; 215 | case('w'): 216 | width = atoi(optarg); 217 | if(width <= 0) { 218 | fprintf(stderr, "Width must be > 0\n"); 219 | err = -EINVAL; 220 | goto fail; 221 | } 222 | break; 223 | case('h'): 224 | height = atoi(optarg); 225 | if(height <= 0) { 226 | fprintf(stderr, "Height must be > 0\n"); 227 | err = -EINVAL; 228 | goto fail; 229 | } 230 | break; 231 | case('r'): 232 | screen_update_rate = atoi(optarg); 233 | if(screen_update_rate <= 0) { 234 | fprintf(stderr, "Screen update rate must be > 0\n"); 235 | err = -EINVAL; 236 | goto fail; 237 | } 238 | break; 239 | case('s'): 240 | ringbuffer_size = atoi(optarg); 241 | if(ringbuffer_size < 2) { 242 | fprintf(stderr, "Ring buffer size must be >= 2\n"); 243 | err = -EINVAL; 244 | goto fail; 245 | } 246 | break; 247 | case('l'): 248 | listen_threads = atoi(optarg); 249 | if(listen_threads <= 0) { 250 | fprintf(stderr, "Number of listening threads must be > 0\n"); 251 | err = -EINVAL; 252 | goto fail; 253 | } 254 | break; 255 | case('f'): 256 | if(frontend_cnt >= MAX_FRONTENDS) { 257 | fprintf(stderr, "Maximum number of frontends reached.\n"); 258 | err = -EINVAL; 259 | goto fail; 260 | } 261 | frontend_names[frontend_cnt] = strdup(optarg); 262 | if(!frontend_names[frontend_cnt]) { 263 | fprintf(stderr, "Can not copy frontend spec. Out of memory\n"); 264 | err = -ENOMEM; 265 | goto fail; 266 | } 267 | frontend_cnt++; 268 | break; 269 | case('t'): 270 | #ifdef FEATURE_TTF 271 | if((err = textrender_alloc(&txtrndr, optarg))) { 272 | fprintf(stderr, "Failed to allocate freetype text renderer\n"); 273 | goto fail; 274 | } 275 | #else 276 | fprintf(stderr, "Shoreline was compiled without font rendering support!\n"); 277 | err = -EINVAL; 278 | goto fail; 279 | #endif 280 | break; 281 | case('d'): 282 | description = strdup(optarg); 283 | if(!description) { 284 | fprintf(stderr, "Failed to allocate memory for description string\n"); 285 | err = -ENOMEM; 286 | goto fail; 287 | } 288 | break; 289 | default: 290 | show_usage(argv[0]); 291 | err = -EINVAL; 292 | goto fail; 293 | } 294 | } 295 | 296 | if(frontend_cnt == 0) { 297 | printf("WARNING: No frontends specified, continuing without any frontends\n"); 298 | } 299 | 300 | if((err = workqueue_init())) { 301 | fprintf(stderr, "Failed to initialize workqueues: %d => %s\n", err, strerror(-err)); 302 | goto fail; 303 | } 304 | 305 | if((err = fb_alloc(&fb, width, height))) { 306 | fprintf(stderr, "Failed to allocate framebuffer: %d => %s\n", err, strerror(-err)); 307 | goto fail; 308 | } 309 | 310 | llist_init(&fb_list); 311 | #ifdef FEATURE_SDL 312 | sdl_param.cb_private = &fb_list; 313 | sdl_param.resize_cb = resize_cb; 314 | #endif 315 | llist_init(&fronts); 316 | while(frontend_cnt > 0 && frontend_cnt--) { 317 | char* frontid = frontend_names[frontend_cnt]; 318 | char* options = frontend_spec_extract_name(frontid); 319 | struct frontend_def* frontdef = frontend_get_def(frontid); 320 | if(!frontdef) { 321 | fprintf(stderr, "Unknown frontend '%s'\n", frontid); 322 | show_frontends(); 323 | goto fail_fronts_free_name; 324 | } 325 | handle_signals = handle_signals && !frontdef->handles_signals; 326 | #ifdef FEATURE_SDL 327 | if((err = frontend_alloc(frontdef, &front, fb, &sdl_param))) { 328 | #else 329 | if((err = frontend_alloc(frontdef, &front, fb, NULL))) { 330 | #endif 331 | fprintf(stderr, "Failed to allocate frontend '%s'\n", frontdef->name); 332 | goto fail_fronts_free_name; 333 | } 334 | front->def = frontdef; 335 | llist_append(&fronts, &front->list); 336 | 337 | if(frontend_can_configure(front) && options) { 338 | if((err = frontend_configure(front, options))) { 339 | fprintf(stderr, "Failed to configure frontend '%s'\n", frontdef->name); 340 | goto fail_fronts_free_name; 341 | } 342 | } 343 | 344 | if(frontend_can_start(front)) { 345 | if((err = frontend_start(front))) { 346 | fprintf(stderr, "Failed to start frontend '%s'\n", frontdef->name); 347 | goto fail_fronts_free_name; 348 | } 349 | } 350 | 351 | free(frontid); 352 | } 353 | 354 | if((err = net_alloc(&net, fb, &fb_list, &fb->size, ringbuffer_size))) { 355 | fprintf(stderr, "Failed to initialize network: %d => %s\n", err, strerror(-err)); 356 | goto fail_fronts; 357 | } 358 | if(handle_signals) { 359 | if(signal(SIGINT, doshutdown)) { 360 | fprintf(stderr, "Failed to bind signal\n"); 361 | err = -EINVAL; 362 | goto fail_net; 363 | } 364 | if(signal(SIGPIPE, SIG_IGN)) { 365 | fprintf(stderr, "Failed to bind signal\n"); 366 | err = -EINVAL; 367 | goto fail_net; 368 | } 369 | } 370 | 371 | if((err = -getaddrinfo(listen_address, port, NULL, &addr_list))) { 372 | fprintf(stderr, "Failed to resolve listen address '%s', %d => %s\n", listen_address, err, gai_strerror(-err)); 373 | goto fail_net; 374 | } 375 | 376 | inaddr = (struct sockaddr_storage*)addr_list->ai_addr; 377 | addr_len = addr_list->ai_addrlen; 378 | 379 | if((err = net_listen(net, listen_threads, inaddr, addr_len))) { 380 | fprintf(stderr, "Failed to start listening: %d => %s\n", err, strerror(-err)); 381 | goto fail_addrinfo; 382 | } 383 | 384 | nice(-20); 385 | while(!do_exit) { 386 | clock_gettime(CLOCK_MONOTONIC, &before); 387 | llist_lock(&fb_list); 388 | fb_coalesce(fb, &fb_list); 389 | llist_unlock(&fb_list); 390 | #ifdef FEATURE_STATISTICS 391 | statistics_update(&stats, net); 392 | snprintf(stat_line, sizeof(stat_line), "Traffic: %.3f %sB / %.3f %sPixels " 393 | "Throughput: %.3f %sb/s / %.3f %sPixels/s FPS: %d frames/s %llu connections", 394 | statistics_traffic_get_scaled(&stats), statistics_traffic_get_unit(&stats), 395 | statistics_pixels_get_scaled(&stats), statistics_pixels_get_unit(&stats), 396 | statistics_throughput_get_scaled(&stats), statistics_throughput_get_unit(&stats), 397 | statistics_pps_get_scaled(&stats), statistics_pps_get_unit(&stats), 398 | statistics_get_frames_per_second(&stats), stats.num_connections); 399 | #endif 400 | draw_overlays(fb); 401 | #ifdef FEATURE_TTF 402 | if(txtrndr) { 403 | textrender_draw_string(txtrndr, fb, 100, fb->size.height / 20, description, 16); 404 | #ifdef FEATURE_STATISTICS 405 | textrender_draw_string(txtrndr, fb, 100, fb->size.height - fb->size.height / 10, stat_line, 16); 406 | #endif 407 | } 408 | #endif 409 | llist_for_each(&fronts, cursor) { 410 | front = llist_entry_get_value(cursor, struct frontend, list); 411 | #ifdef FEATURE_TTF 412 | if(!txtrndr) { 413 | #endif 414 | if(frontend_can_draw_string(front)) { 415 | frontend_draw_string(front, 0, 0, description); 416 | #ifdef FEATURE_STATISTICS 417 | frontend_draw_string(front, 0, fb->size.height - fb->size.height / 10, stat_line); 418 | #endif 419 | } 420 | #ifdef FEATURE_TTF 421 | } 422 | #endif 423 | if((err = frontend_update(front))) { 424 | fprintf(stderr, "Failed to update frontend '%s', %d => %s, bailing out\n", front->def->name, err, strerror(-err)); 425 | doshutdown(SIGINT); 426 | break; 427 | } 428 | } 429 | #ifdef FEATURE_STATISTICS 430 | stats.num_frames++; 431 | #endif 432 | clock_gettime(CLOCK_MONOTONIC, &after); 433 | time_delta = get_timespec_diff(&after, &before); 434 | time_delta = 1000000000UL / screen_update_rate - time_delta; 435 | if(time_delta > 0) { 436 | usleep(time_delta / 1000UL); 437 | } 438 | } 439 | net_shutdown(net); 440 | 441 | fb_free_all(&fb_list); 442 | fail_addrinfo: 443 | freeaddrinfo(addr_list); 444 | fail_net: 445 | net_free(net); 446 | fail_fronts: 447 | llist_for_each_safe(&fronts, cursor, next) { 448 | front = llist_entry_get_value(cursor, struct frontend, list); 449 | printf("Shutting down frontend '%s'\n", front->def->name); 450 | frontend_free(front); 451 | } 452 | //fail_fb: 453 | fb_free(fb); 454 | fail: 455 | while(frontend_cnt > 0 && frontend_cnt--) { 456 | free(frontend_names[frontend_cnt]); 457 | } 458 | #ifdef FEATURE_TTF 459 | if(txtrndr) { 460 | textrender_free(txtrndr); 461 | } 462 | #endif 463 | if(description && description != default_description) { 464 | free(description); 465 | } 466 | workqueue_deinit(); 467 | return err; 468 | 469 | fail_fronts_free_name: 470 | frontend_cnt++; 471 | goto fail_fronts; 472 | } 473 | -------------------------------------------------------------------------------- /network.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "network.h" 20 | #include "ring.h" 21 | #include "framebuffer.h" 22 | #include "llist.h" 23 | #include "util.h" 24 | 25 | #define CONNECTION_QUEUE_SIZE 16 26 | #define THREAD_NAME_MAX 16 27 | #define SCRATCH_STR_MAX 32 28 | #define WHITESPACE_SEARCH_GARBAGE_THRESHOLD 32 29 | 30 | #if DEBUG > 1 31 | #define debug_printf(...) printf(__VA_ARGS__) 32 | #define debug_fprintf(s, ...) fprintf(s, __VA_ARGS__) 33 | #else 34 | #define debug_printf(...) 35 | #define debug_fprintf(...) 36 | #endif 37 | 38 | /* Theory Of Operation 39 | * =================== 40 | * 41 | * Using net_alloc the caller grabs a net struct. After that 42 | * he uses net_listen with the desired number of threads used 43 | * for accepting connections. 44 | * 45 | * Each of the threads used for accepting connections starts 46 | * another thread for each connection it accepted. 47 | * 48 | * The newly created thread then sets up a ring buffer to avoid 49 | * memmoves while parsing and starts reading data to it. 50 | * After receiving any number of bytes the thread tries to read 51 | * a valid command verb from the current read position in the 52 | * ring buffer. If that fails the thread tries to skip to the 53 | * next whitespace-separated token and tries to parse it as a 54 | * command verb. One a valid command verb is detected the 55 | * parser tries to fetch all arguments required to form a 56 | * complete command. 57 | * If there are any required parts missing from a command the 58 | * parser will assume that it has simply not been received yet 59 | * and go back to reading from the socket. 60 | */ 61 | static int one = 1; 62 | 63 | int net_alloc(struct net** network, struct fb* fb, struct llist* fb_list, struct fb_size* fb_size, size_t ring_size) { 64 | int err = 0; 65 | struct net* net = calloc(1, sizeof(struct net)); 66 | if(!net) { 67 | err = -ENOMEM; 68 | goto fail; 69 | } 70 | 71 | net->state = NET_STATE_IDLE; 72 | net->fb = fb; 73 | net->fb_list = fb_list; 74 | net->fb_size = fb_size; 75 | pthread_mutex_init(&net->fb_lock, NULL); 76 | net->ring_size = ring_size; 77 | 78 | *network = net; 79 | 80 | return 0; 81 | 82 | fail: 83 | return err; 84 | } 85 | 86 | void net_free(struct net* net) { 87 | assert(net->state == NET_STATE_EXIT || net->state == NET_STATE_IDLE); 88 | if(net->threads) { 89 | free(net->threads); 90 | } 91 | free(net); 92 | } 93 | 94 | 95 | static void net_kill_threads(struct net* net) { 96 | int i = net->num_threads; 97 | struct net_thread* thread; 98 | net->state = NET_STATE_SHUTDOWN; 99 | while(i-- > 0) { 100 | thread = &net->threads[i]; 101 | pthread_cancel(thread->thread); 102 | pthread_join(thread->thread, NULL); 103 | } 104 | } 105 | 106 | void net_shutdown(struct net* net) { 107 | net_kill_threads(net); 108 | shutdown(net->socket, SHUT_RDWR); 109 | close(net->socket); 110 | net->state = NET_STATE_EXIT; 111 | } 112 | 113 | static inline int net_is_newline(char c) { 114 | return c == '\r' || c == '\n'; 115 | } 116 | 117 | static inline int net_is_whitespace(char c) { 118 | switch(c) { 119 | case ' ': 120 | case '\n': 121 | case '\r': 122 | case '\t': 123 | return 1; 124 | } 125 | return 0; 126 | } 127 | 128 | static int net_skip_whitespace(struct ring* ring) { 129 | int cnt = 0; 130 | char c; 131 | while(ring_any_available(ring)) { 132 | c = ring_peek_one(ring); 133 | if(!net_is_whitespace(c)) { 134 | goto done; 135 | } 136 | ring_inc_read(ring); 137 | cnt++; 138 | } 139 | done: 140 | return cnt ? cnt : -1; 141 | } 142 | 143 | static off_t net_next_whitespace(struct ring* ring) { 144 | off_t offset = 0; 145 | char c, *read_before = ring->ptr_read; 146 | int err; 147 | while(ring_any_available(ring)) { 148 | c = ring_read_one(ring); 149 | if(net_is_whitespace(c)) { 150 | goto done; 151 | } 152 | if(offset++ >= WHITESPACE_SEARCH_GARBAGE_THRESHOLD) { 153 | err = -EINVAL; 154 | goto fail; 155 | } 156 | } 157 | err = -1; // No next whitespace found 158 | goto fail; 159 | 160 | done: 161 | ring->ptr_read = read_before; 162 | return offset; 163 | fail: 164 | ring->ptr_read = read_before; 165 | return err; 166 | } 167 | 168 | static uint32_t net_str_to_uint32_10(struct ring* ring, ssize_t len) { 169 | uint32_t val = 0; 170 | int radix; 171 | char c; 172 | for(radix = 0; radix < len; radix++) { 173 | c = ring_read_one(ring); 174 | val = val * 10 + (c - '0'); 175 | } 176 | return val; 177 | } 178 | 179 | // Separate implementation to keep performance high 180 | static uint32_t net_str_to_uint32_16(struct ring* ring, ssize_t len) { 181 | uint32_t val = 0; 182 | char c; 183 | int radix, lower; 184 | for(radix = 0; radix < len; radix++) { 185 | // Could be replaced by a left shift 186 | val *= 16; 187 | c = ring_read_one(ring); 188 | lower = c | 0x20; 189 | if(lower >= 'a') { 190 | val += lower - 'a' + 10; 191 | } else { 192 | val += lower - '0'; 193 | } 194 | } 195 | return val; 196 | } 197 | 198 | static ssize_t net_sock_printf(int socket, char* scratch_str, size_t scratch_len, char* fmt, ...) { 199 | ssize_t ret; 200 | size_t len; 201 | off_t write_cnt = 0; 202 | 203 | va_list vargs; 204 | va_start(vargs, fmt); 205 | ret = vsnprintf(scratch_str, scratch_len, fmt, vargs); 206 | va_end(vargs); 207 | 208 | if(ret >= scratch_len) { 209 | ret = -ENOBUFS; 210 | } 211 | 212 | if(ret < 0) { 213 | goto out; 214 | } 215 | 216 | len = ret; 217 | 218 | while(write_cnt < len) { 219 | ssize_t write_len; 220 | if((write_len = write(socket, scratch_str + write_cnt, len - write_cnt)) < 0) { 221 | ret = -errno; 222 | goto out; 223 | } 224 | write_cnt += write_len; 225 | } 226 | out: 227 | return ret; 228 | } 229 | 230 | static void net_connection_thread_cleanup_ring(void* args) { 231 | struct net_connection_thread* thread = args; 232 | ring_free(thread->ring); 233 | } 234 | 235 | static void net_connection_thread_cleanup_socket(void* args) { 236 | struct net_connection_thread* thread = args; 237 | shutdown(thread->threadargs.socket, SHUT_RDWR); 238 | close(thread->threadargs.socket); 239 | } 240 | 241 | static void net_connection_thread_cleanup_self(void* args) { 242 | struct net_connection_thread* thread = args; 243 | pthread_mutex_lock(&thread->threadargs.net_thread->list_lock); 244 | llist_remove(&thread->list); 245 | pthread_mutex_unlock(&thread->threadargs.net_thread->list_lock); 246 | free(thread); 247 | } 248 | 249 | static void* net_connection_thread(void* args) { 250 | struct net_connection_threadargs* threadargs = args; 251 | int err, socket = threadargs->socket; 252 | struct net* net = threadargs->net; 253 | struct net_connection_thread* thread = 254 | container_of(threadargs, struct net_connection_thread, threadargs); 255 | 256 | unsigned numa_node = get_numa_node(); 257 | struct fb* fb; 258 | struct fb_size* fbsize; 259 | union fb_pixel pixel; 260 | unsigned int x, y; 261 | 262 | off_t offset; 263 | ssize_t read_len; 264 | char* last_cmd; 265 | 266 | /* 267 | A small ring buffer (64kB * 64k connections = ~4GB at max) to prevent memmoves. 268 | Using a ring buffer prevents us from having to memmove but we do have to handle 269 | Wrap arounds. This means that we can not use functions like strncmp safely without 270 | checking for wraparounds 271 | */ 272 | struct ring* ring; 273 | 274 | char scratch_str[SCRATCH_STR_MAX]; 275 | 276 | #ifndef FEATURE_BROKEN_PTHREAD 277 | cpu_set_t nodemask; 278 | int cpuid = sched_getcpu(); 279 | if(cpuid < 0) { 280 | fprintf(stderr, "Failed to get cpuid of network thread, continuing without affinity setting\n"); 281 | } else { 282 | CPU_ZERO(&nodemask); 283 | CPU_SET(cpuid, &nodemask); 284 | if((err = pthread_setaffinity_np(pthread_self(), sizeof(nodemask), &nodemask))) { 285 | fprintf(stderr, "Failed to set cpu affinity, continuing without affinity setting: %s (%d)\n", strerror(err), err); 286 | } 287 | } 288 | #endif 289 | 290 | pthread_mutex_lock(&net->fb_lock); 291 | fb = fb_get_fb_on_node(net->fb_list, numa_node); 292 | if(!fb) { 293 | printf("Failed to find fb on NUMA node %u, creating new fb\n", numa_node); 294 | if(fb_alloc(&fb, net->fb_size->width, net->fb_size->height)) { 295 | fprintf(stderr, "Failed to allocate fb on node\n"); 296 | goto fail; 297 | } 298 | printf("Allocated fb on NUMA node %u\n", fb->numa_node); 299 | llist_append(net->fb_list, &fb->list); 300 | } 301 | pthread_mutex_unlock(&net->fb_lock); 302 | 303 | fbsize = fb_get_size(fb); 304 | 305 | pthread_cleanup_push(net_connection_thread_cleanup_self, thread); 306 | pthread_cleanup_push(net_connection_thread_cleanup_socket, thread); 307 | 308 | 309 | if((err = ring_alloc(&ring, net->ring_size))) { 310 | fprintf(stderr, "Failed to allocate ring buffer, %s\n", strerror(-err)); 311 | goto fail_socket; 312 | } 313 | thread->ring = ring; 314 | 315 | pthread_cleanup_push(net_connection_thread_cleanup_ring, thread); 316 | recv: 317 | while(net->state != NET_STATE_SHUTDOWN) { 318 | read_len = read(socket, ring->ptr_write, ring_free_space_contig(ring)); 319 | if(read_len <= 0) { 320 | if(read_len < 0) { 321 | err = -errno; 322 | fprintf(stderr, "Client socket failed %d => %s\n", errno, strerror(errno)); 323 | } 324 | goto fail_ring; 325 | } 326 | #ifdef FEATURE_STATISTICS 327 | thread->byte_count += read_len; 328 | #endif 329 | debug_printf("Read %zd bytes\n", read_len); 330 | ring_advance_write(ring, read_len); 331 | 332 | while(ring_any_available(ring)) { 333 | last_cmd = ring->ptr_read; 334 | 335 | if(!ring_memcmp(ring, "PX", strlen("PX"), NULL)) { 336 | if((err = net_skip_whitespace(ring)) < 0) { 337 | debug_fprintf(stderr, "No whitespace after PX cmd\n"); 338 | goto recv_more; 339 | } 340 | if((offset = net_next_whitespace(ring)) < 0) { 341 | debug_fprintf(stderr, "No more whitespace found, missing X\n"); 342 | goto recv_more; 343 | } 344 | x = net_str_to_uint32_10(ring, offset); 345 | if((err = net_skip_whitespace(ring)) < 0) { 346 | debug_fprintf(stderr, "No whitespace after X coordinate\n"); 347 | goto recv_more; 348 | } 349 | if((offset = net_next_whitespace(ring)) < 0) { 350 | debug_fprintf(stderr, "No more whitespace found, missing Y\n"); 351 | goto recv_more; 352 | } 353 | y = net_str_to_uint32_10(ring, offset); 354 | if((err = net_skip_whitespace(ring)) < 0) { 355 | debug_fprintf(stderr, "No whitespace after Y coordinate\n"); 356 | goto recv_more; 357 | } 358 | x += thread->offset.x; 359 | y += thread->offset.y; 360 | if(unlikely(net_is_newline(ring_peek_prev(ring)))) { 361 | // Get pixel 362 | if(x < fbsize->width && y < fbsize->height) { 363 | if((err = net_sock_printf(socket, scratch_str, sizeof(scratch_str), "PX %u %u %06x\n", 364 | x, y, fb_get_pixel(net->fb, x, y).abgr >> 8)) < 0) { 365 | fprintf(stderr, "Failed to write out pixel value: %d => %s\n", err, strerror(-err)); 366 | goto fail_ring; 367 | } 368 | } 369 | } else { 370 | // Set pixel 371 | if((offset = net_next_whitespace(ring)) < 0) { 372 | debug_fprintf(stderr, "No more whitespace found, missing color\n"); 373 | goto recv_more; 374 | } 375 | if(offset > 6) { 376 | pixel.abgr = net_str_to_uint32_16(ring, offset); 377 | } else { 378 | pixel.abgr = net_str_to_uint32_16(ring, offset) << 8; 379 | pixel.color.alpha = 0xFF; 380 | } 381 | 382 | debug_printf("Got pixel command: PX %u %u %02x%02x%02x%02x\n", x, y, 383 | pixel.color.color_bgr.red, pixel.color.color_bgr.green, 384 | pixel.color.color_bgr.blue, pixel.color.alpha); 385 | if(x < fbsize->width && y < fbsize->height) { 386 | #ifdef FEATURE_STATISTICS 387 | #ifdef FEATURE_PIXEL_COUNT 388 | fb->pixel_count++; 389 | #endif 390 | #endif 391 | #ifdef FEATURE_ALPHA_BLENDING 392 | if (pixel.color.alpha != 0xFF) { 393 | union fb_pixel old_pixel = fb_get_pixel(fb, x, y); 394 | FB_ALPHA_BLEND_PIXEL(pixel, pixel, old_pixel); 395 | } 396 | #endif 397 | fb_set_pixel(fb, x, y, &pixel); 398 | } else { 399 | debug_printf("Got pixel outside screen area: %u, %u outside %u, %u\n", x, y, fbsize->width, fbsize->height); 400 | } 401 | } 402 | } 403 | #ifdef FEATURE_SIZE 404 | else if(!ring_memcmp(ring, "SIZE", strlen("SIZE"), NULL)) { 405 | if((err = net_sock_printf(socket, scratch_str, sizeof(scratch_str), "SIZE %u %u\n", fbsize->width, fbsize->height)) < 0) { 406 | fprintf(stderr, "Failed to write out size: %d => %s\n", err, strerror(-err)); 407 | goto fail_ring; 408 | } 409 | } 410 | #endif 411 | #ifdef FEATURE_OFFSET 412 | else if(!ring_memcmp(ring, "OFFSET", strlen("OFFSET"), NULL)) { 413 | if((err = net_skip_whitespace(ring)) < 0) { 414 | goto recv_more; 415 | } 416 | if((offset = net_next_whitespace(ring)) < 0) { 417 | goto recv_more; 418 | } 419 | x = net_str_to_uint32_10(ring, offset); 420 | if((err = net_skip_whitespace(ring)) < 0) { 421 | goto recv_more; 422 | } 423 | if((offset = net_next_whitespace(ring)) < 0) { 424 | goto recv_more; 425 | } 426 | y = net_str_to_uint32_10(ring, offset); 427 | thread->offset.x = x; 428 | thread->offset.y = y; 429 | } 430 | #endif 431 | else { 432 | if((offset = net_next_whitespace(ring)) >= 0) { 433 | debug_printf("Encountered unknown command\n"); 434 | ring_advance_read(ring, offset); 435 | } else { 436 | if(offset == -EINVAL) { 437 | // We have a missbehaving client 438 | goto fail_ring; 439 | } 440 | goto recv; 441 | } 442 | } 443 | 444 | net_skip_whitespace(ring); 445 | } 446 | } 447 | 448 | fail_ring: 449 | pthread_cleanup_pop(true); 450 | fail_socket: 451 | pthread_cleanup_pop(true); 452 | pthread_cleanup_pop(true); 453 | fail: 454 | pthread_detach(pthread_self()); 455 | return NULL; 456 | 457 | recv_more: 458 | ring->ptr_read = last_cmd; 459 | goto recv; 460 | } 461 | 462 | static void net_listen_thread_cleanup_threadlist(void* args) { 463 | struct net_thread* thread = args; 464 | struct llist* threadlist = thread->threadlist; 465 | struct net_connection_thread* conn_thread; 466 | 467 | llist_lock(threadlist); 468 | while(threadlist->head) { 469 | conn_thread = llist_entry_get_value(threadlist->head, struct net_connection_thread, list); 470 | pthread_cancel(conn_thread->thread); 471 | llist_unlock(threadlist) 472 | pthread_join(conn_thread->thread, NULL); 473 | llist_lock(threadlist); 474 | } 475 | llist_unlock(threadlist); 476 | llist_free(threadlist); 477 | } 478 | 479 | 480 | static void* net_listen_thread(void* args) { 481 | int err, socket; 482 | struct net_threadargs* threadargs = args; 483 | struct net* net = threadargs->net; 484 | struct net_thread* thread = container_of(args, struct net_thread, threadargs); 485 | 486 | struct llist* threadlist; 487 | struct net_connection_thread* conn_thread; 488 | pthread_mutex_init(&thread->list_lock, NULL); 489 | 490 | if((err = llist_alloc(&threadlist))) { 491 | fprintf(stderr, "Failed to allocate thread list\n"); 492 | goto fail; 493 | } 494 | thread->threadlist = threadlist; 495 | thread->initialized = true; 496 | 497 | pthread_cleanup_push(net_listen_thread_cleanup_threadlist, thread); 498 | 499 | while(net->state != NET_STATE_SHUTDOWN) { 500 | socket = accept(net->socket, NULL, NULL); 501 | if(socket < 0) { 502 | err = -errno; 503 | fprintf(stderr, "Got error %d => %s, shutting down\n", errno, strerror(errno)); 504 | goto fail_threadlist; 505 | } 506 | printf("Got a new connection\n"); 507 | 508 | conn_thread = calloc(1, sizeof(struct net_connection_thread)); 509 | if(!conn_thread) { 510 | fprintf(stderr, "Failed to allocate memory for connection thread\n"); 511 | goto fail_connection; 512 | } 513 | llist_entry_init(&conn_thread->list); 514 | conn_thread->threadargs.socket = socket; 515 | conn_thread->threadargs.net = net; 516 | conn_thread->threadargs.net_thread = thread; 517 | 518 | pthread_mutex_lock(&thread->list_lock); 519 | if((err = -pthread_create(&conn_thread->thread, NULL, net_connection_thread, &conn_thread->threadargs))) { 520 | fprintf(stderr, "Failed to create thread: %d => %s\n", err, strerror(-err)); 521 | pthread_mutex_unlock(&thread->list_lock); 522 | goto fail_thread_entry; 523 | } 524 | 525 | llist_append(threadlist, &conn_thread->list); 526 | pthread_mutex_unlock(&thread->list_lock); 527 | 528 | continue; 529 | 530 | fail_thread_entry: 531 | free(conn_thread); 532 | fail_connection: 533 | shutdown(socket, SHUT_RDWR); 534 | close(socket); 535 | } 536 | fail_threadlist: 537 | pthread_cleanup_pop(true); 538 | fail: 539 | return NULL; 540 | 541 | } 542 | 543 | int net_listen(struct net* net, unsigned int num_threads, struct sockaddr_storage* addr, size_t addr_len) { 544 | int err = 0, i; 545 | char host_tmp[NI_MAXHOST], port_tmp[NI_MAXSERV]; 546 | 547 | assert(num_threads > 0); 548 | 549 | assert(net->state == NET_STATE_IDLE); 550 | net->state = NET_STATE_LISTEN; 551 | 552 | // Create socket 553 | net->socket = socket(addr->ss_family, SOCK_STREAM, 0); 554 | if(net->socket < 0) { 555 | fprintf(stderr, "Failed to create socket\n"); 556 | err = -errno; 557 | goto fail; 558 | } 559 | setsockopt(net->socket, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)); 560 | 561 | assert(!getnameinfo((struct sockaddr*)addr, addr_len, host_tmp, NI_MAXHOST, port_tmp, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV)); 562 | 563 | // Start listening 564 | if(bind(net->socket, (struct sockaddr*)addr, addr_len) < 0) { 565 | fprintf(stderr, "Failed to bind to %s:%s\n", host_tmp, port_tmp); 566 | err = -errno; 567 | goto fail_socket; 568 | } 569 | 570 | if(listen(net->socket, CONNECTION_QUEUE_SIZE)) { 571 | fprintf(stderr, "Failed to start listening: %d => %s\n", errno, strerror(errno)); 572 | err = -errno; 573 | goto fail_socket; 574 | } 575 | 576 | if (addr->ss_family == AF_INET6) { 577 | printf("Listening on [%s]:%s\n", host_tmp, port_tmp); 578 | } else { 579 | printf("Listening on %s:%s\n", host_tmp, port_tmp); 580 | } 581 | 582 | // Allocate space for threads 583 | net->threads = calloc(num_threads, sizeof(struct net_thread)); 584 | if(!net->threads) { 585 | err = -ENOMEM; 586 | goto fail_socket; 587 | } 588 | 589 | for(i = 0; i < num_threads; i++) { 590 | net->threads[i].threadargs.net = net; 591 | } 592 | 593 | // Setup pthreads (using net->num_threads as a counter might come back to bite me) 594 | for(net->num_threads = 0; net->num_threads < num_threads; net->num_threads++) { 595 | #ifndef FEATURE_BROKEN_PTHREAD 596 | char threadname[THREAD_NAME_MAX]; 597 | #endif 598 | err = -pthread_create(&net->threads[net->num_threads].thread, NULL, net_listen_thread, &net->threads[net->num_threads].threadargs); 599 | if(err) { 600 | fprintf(stderr, "Failed to create pthread %d\n", net->num_threads); 601 | goto fail_pthread_create; 602 | } 603 | #ifndef FEATURE_BROKEN_PTHREAD 604 | snprintf(threadname, THREAD_NAME_MAX, "network %d", net->num_threads); 605 | pthread_setname_np(net->threads[net->num_threads].thread, threadname); 606 | #endif 607 | } 608 | 609 | return 0; 610 | 611 | fail_pthread_create: 612 | net_kill_threads(net); 613 | //fail_threads_alloc: 614 | free(net->threads); 615 | fail_socket: 616 | close(net->socket); 617 | shutdown(net->socket, SHUT_RDWR); 618 | fail: 619 | net->state = NET_STATE_IDLE; 620 | return err; 621 | } 622 | --------------------------------------------------------------------------------