├── .gitignore ├── src ├── xfds.h ├── colorspec.c ├── xfds.c ├── legacy │ ├── xhello.c │ ├── xft_debug.c │ └── xecho_old.c ├── xecho.h ├── strings.c ├── xecho.c ├── arguments.c ├── logic.c └── x11.c ├── TODO ├── LICENSE.txt ├── Makefile ├── README.txt └── man └── man1 └── xecho.1 /.gitignore: -------------------------------------------------------------------------------- 1 | xecho 2 | xecho.1.gz 3 | -------------------------------------------------------------------------------- /src/xfds.h: -------------------------------------------------------------------------------- 1 | typedef struct /*XFD_AGGREG*/ { 2 | int* fds; 3 | unsigned size; 4 | } X_FDS; -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | run maximizer in separate thread 2 | handle static size fonts 3 | use x resource manager 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This program is free software. It comes without any warranty, to 2 | the extent permitted by applicable law. You can redistribute it 3 | and/or modify it under the terms of the Do What The Fuck You Want 4 | To Public License, Version 2, as published by Sam Hocevar and 5 | reproduced below. 6 | 7 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 8 | Version 2, December 2004 9 | 10 | Copyright (C) 2004 Sam Hocevar 11 | 12 | Everyone is permitted to copy and distribute verbatim or modified 13 | copies of this license document, and changing it is allowed as long 14 | as the name is changed. 15 | 16 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 17 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 18 | 19 | 0. You just DO WHAT THE FUCK YOU WANT TO. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export PREFIX ?= /usr 2 | export DOCDIR ?= $(DESTDIR)$(PREFIX)/share/man/man1 3 | 4 | .PHONY: all clean 5 | PKG_CONFIG ?= pkg-config 6 | CFLAGS ?= -g -Wall 7 | CFLAGS += $(shell $(PKG_CONFIG) --cflags freetype2) 8 | LDLIBS += $(shell $(PKG_CONFIG) --libs freetype2) -lXft -lm -lXext -lX11 9 | SRC = src/xecho.c 10 | 11 | all: xecho xecho.1.gz 12 | 13 | install: 14 | install -d "$(DESTDIR)$(PREFIX)/bin" 15 | install -m 0755 xecho "$(DESTDIR)$(PREFIX)/bin" 16 | install -d "$(DOCDIR)" 17 | install -g 0 -o 0 -m 0644 xecho.1.gz "$(DOCDIR)" 18 | 19 | xecho: $(SRC) 20 | $(CC) $(CFLAGS) $^ -o $@ $(LDLIBS) 21 | 22 | xecho.1.gz: 23 | gzip -c < man/man1/xecho.1 > $@ 24 | 25 | clean: 26 | $(RM) xecho xecho.1.gz 27 | 28 | displaytest: 29 | valgrind -v --leak-check=full --track-origins=yes --show-reachable=yes ./xecho -vv qmt 30 | 31 | updatetest: 32 | valgrind --track-origins=yes --leak-check=full ./xecho -stdin -padding 10 -size 20 -align nw 33 | -------------------------------------------------------------------------------- /src/colorspec.c: -------------------------------------------------------------------------------- 1 | unsigned short colorspec_read_byte(char* cs){ 2 | char* hexmap = "0123456789abcdef"; 3 | unsigned short rv = 0; 4 | int i; 5 | if(*cs != 0 && cs[1] != 0){ 6 | for(i = 0; hexmap[i] != 0 && hexmap[i] != cs[0]; i++){ 7 | } 8 | rv |= (i << 12); 9 | for(i = 0; hexmap[i] != 0 && hexmap[i] != cs[1]; i++){ 10 | } 11 | rv |= (i << 8); 12 | } 13 | return rv; 14 | } 15 | 16 | XftColor colorspec_parse(char* cs, Display* display, int screen){ 17 | XftColor rv = {}; 18 | XRenderColor xrender_color = {0,0,0,0xffff}; 19 | int i; 20 | 21 | if(*cs == '#'){ 22 | if(strlen(cs) != 7){ 23 | fprintf(stderr, "Invalid colorspec length\n"); 24 | } 25 | 26 | for(i = 1; i < strlen(cs); i++){ 27 | if(!isxdigit(cs[i])){ 28 | fprintf(stderr, "Invalid digit in colorspec: %c\n", cs[i]); 29 | return rv; 30 | } 31 | } 32 | 33 | xrender_color.red = colorspec_read_byte(cs + 1); 34 | xrender_color.green = colorspec_read_byte(cs + 3); 35 | xrender_color.blue = colorspec_read_byte(cs + 5); 36 | 37 | fprintf(stderr, "Read colorspec %s as r:%04x g:%04x b:%04x\n", cs, xrender_color.red, xrender_color.green, xrender_color.blue); 38 | 39 | if(!XftColorAllocValue(display, DefaultVisual(display, screen), DefaultColormap(display, screen), &xrender_color, &rv)){ 40 | fprintf(stderr, "Failed to allocate color\n"); 41 | } 42 | } 43 | else{ 44 | if(!XftColorAllocName(display, DefaultVisual(display, screen), DefaultColormap(display, screen), cs, &rv)){ 45 | fprintf(stderr, "Failed to get color by name\n"); 46 | } 47 | } 48 | return rv; 49 | } 50 | -------------------------------------------------------------------------------- /src/xfds.c: -------------------------------------------------------------------------------- 1 | bool xfd_add(X_FDS* set, int fd){ 2 | unsigned i; 3 | 4 | if(!set->fds){ 5 | set->fds = malloc(sizeof(int)); 6 | if(!set->fds){ 7 | fprintf(stderr, "xfd_add: Initial alloc failed\n"); 8 | return false; 9 | } 10 | set->size = 1; 11 | set->fds[0] = fd; 12 | return true; 13 | } 14 | 15 | for(i = 0; i < set->size; i++){ 16 | if(set->fds[i] == fd){ 17 | fprintf(stderr, "xfd_add: Not pushing duplicate entry\n"); 18 | return false; 19 | } 20 | } 21 | 22 | set->fds = realloc(set->fds, (set->size + 1) * sizeof(int)); 23 | if(!set->fds){ 24 | fprintf(stderr, "xfd_add: Failed to realloc fd set\n"); 25 | return false; 26 | } 27 | 28 | set->fds[set->size] = fd; 29 | set->size++; 30 | 31 | return true; 32 | } 33 | 34 | bool xfd_remove(X_FDS* set, int fd){ 35 | unsigned i, c; 36 | 37 | for(i = 0; i < set->size; i++){ 38 | if(set->fds[i] == fd){ 39 | for(c = i; c < set->size - 1; c++){ 40 | set->fds[c] = set->fds[c + 1]; 41 | } 42 | 43 | set->size--; 44 | set->fds = realloc(set->fds, set->size * sizeof(int)); 45 | if(!set->fds && set->size > 0){ 46 | fprintf(stderr, "xfd_remove: Failed to realloc\n"); 47 | return false; 48 | } 49 | return true; 50 | } 51 | } 52 | 53 | fprintf(stderr, "xfd_remove: FD not in set\n"); 54 | return false; 55 | } 56 | 57 | void xfd_free(X_FDS* set){ 58 | if(set->fds){ 59 | free(set->fds); 60 | set->fds = NULL; 61 | } 62 | set->size = 0; 63 | } 64 | 65 | void xconn_watch(Display* dpy, XPointer client_data, int fd, Bool opening, XPointer* watch_data){ 66 | if(opening){ 67 | fprintf(stderr, "xconn_watch: Internal connection registered\n"); 68 | xfd_add((X_FDS*)client_data, fd); 69 | } 70 | else{ 71 | fprintf(stderr, "xconn_watch: Internal connection closed\n"); 72 | xfd_remove((X_FDS*)client_data, fd); 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /src/legacy/xhello.c: -------------------------------------------------------------------------------- 1 | /* 2 | Pretty minimal XWindow test program 3 | 4 | 5 | This program is free software. It comes without any warranty, to 6 | the extent permitted by applicable law. You can redistribute it 7 | and/or modify it under the terms of the Do What The Fuck You Want 8 | To Public License, Version 2, as published by Sam Hocevar and 9 | reproduced below. 10 | 11 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 12 | Version 2, December 2004 13 | 14 | Copyright (C) 2004 Sam Hocevar 15 | 16 | Everyone is permitted to copy and distribute verbatim or modified 17 | copies of this license document, and changing it is allowed as long 18 | as the name is changed. 19 | 20 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 21 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 22 | 23 | 0. You just DO WHAT THE FUCK YOU WANT TO. 24 | */ 25 | #include 26 | #include 27 | #include 28 | 29 | int main(int argc, char** argc){ 30 | printf("Xlib Hello World"); 31 | 32 | Display* d; 33 | XEvent e; 34 | Window w,r; 35 | int screen; 36 | char* dpyName=getenv("DISPLAY"); 37 | if(!dpyName){ 38 | dpyName=":0"; 39 | } 40 | 41 | d=XOpenDisplay(dpyName); 42 | if(!d){ 43 | printf("No display"); 44 | exit(1); 45 | } 46 | 47 | screen=DefaultScreen(d); 48 | r=RootWindow(d,screen); 49 | w=XCreateSimpleWindow(d, r, 0, 0, 100, 100, 2, BlackPixel(d, screen), WhitePixel(d, screen)); 50 | 51 | XSelectInput(d, w, ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask); 52 | 53 | XMapWindow(d,w); 54 | 55 | while(1){ 56 | XNextEvent(d,&e); 57 | switch(e.type){ 58 | case Expose: 59 | printf("Expose!\n"); 60 | break; 61 | 62 | case ConfigureNotify: 63 | printf("Configure! (%dx%d)\n", e.xconfigure.width, e.xconfigure.height); 64 | break; 65 | 66 | case KeyPress: 67 | printf("KeyPress!\n"); 68 | break; 69 | 70 | case ButtonPress: 71 | XCloseDisplay(d); 72 | exit(0); 73 | break; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/xecho.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "xfds.h" 15 | 16 | typedef enum /*_ALIGNMENT*/ { 17 | ALIGN_CENTER, 18 | ALIGN_NORTH, 19 | ALIGN_EAST, 20 | ALIGN_SOUTH, 21 | ALIGN_WEST, 22 | ALIGN_NORTHEAST, 23 | ALIGN_SOUTHEAST, 24 | ALIGN_SOUTHWEST, 25 | ALIGN_NORTHWEST 26 | } TEXT_ALIGN; 27 | 28 | typedef struct /*_CFG_ARGS*/ { 29 | unsigned verbosity; 30 | unsigned padding; 31 | unsigned line_spacing; 32 | unsigned max_size; 33 | TEXT_ALIGN alignment; 34 | bool independent_resize; 35 | bool handle_stdin; 36 | bool debug_boxes; 37 | bool disable_text; 38 | bool double_buffer; 39 | bool windowed; 40 | bool print_usage; 41 | double force_size; 42 | char* text_color; 43 | char* bg_color; 44 | char* debug_color; 45 | char* font_name; 46 | char* window_name; 47 | } CFG; 48 | 49 | typedef struct /*_XDATA*/ { 50 | int screen; 51 | Display* display; 52 | Window main; 53 | XdbeBackBuffer back_buffer; 54 | XftDraw* drawable; 55 | XftColor text_color; 56 | XftColor bg_color; 57 | XftColor debug_color; 58 | Atom wm_delete; 59 | X_FDS xfds; 60 | } XRESOURCES; 61 | 62 | typedef struct /*_TEXT_BLOCK*/ { 63 | unsigned layout_x; 64 | unsigned layout_y; 65 | double size; 66 | char* text; 67 | bool active; 68 | bool calculated; 69 | XGlyphInfo extents; 70 | } TEXTBLOCK; 71 | 72 | #define DEFAULT_FONT "verdana" 73 | #define DEFAULT_TEXTCOLOR "black" 74 | #define DEFAULT_WINCOLOR "white" 75 | #define DEFAULT_DEBUGCOLOR "red" 76 | #define DEFAULT_WINDOWNAME "xecho" 77 | #define STDIN_DATA_CHUNK 512 78 | 79 | #define LOG_DEBUG 3 80 | #define LOG_INFO 2 81 | void errlog(CFG* config, unsigned level, char* fmt, ...); 82 | 83 | #include "xfds.c" 84 | #include "colorspec.c" 85 | #include "arguments.c" 86 | #include "strings.c" 87 | #include "x11.c" 88 | #include "logic.c" 89 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | xecho is an easy way to display text via an X Window. 2 | It takes input from the commandline or via stdin and 3 | tries to render it at the largest font size the window 4 | permits. 5 | 6 | Additional options allow customizing the display 7 | for special cases, such as signage or status displays. 8 | 9 | xecho aims to have minimal dependencies and bloat, 10 | directly using Xlib and Xft for it's functionality. 11 | Double buffering is done by using the X Double 12 | Buffering Extension (XDBE) and can be disabled. 13 | 14 | This repository contains the latest development version 15 | of xecho. Release tarballs are available at the tag page 16 | and are archived at 17 | https://dev.cbcdn.com/xecho/ 18 | 19 | Should this repository ever move, the new location will be 20 | announced there. 21 | 22 | Options: 23 | -- Stop argument parsing 24 | -font Font to be used 25 | -bc Background color 26 | -fc Text color 27 | -dc Debug color 28 | -title Set window title 29 | -size <n> Set static font size 30 | -maxsize <n> Set maximum size for scaling 31 | -align <alignspec> Align text 32 | -padding <n> Pad entire text 33 | -linespacing <n> Pad between lines 34 | 35 | Flags: 36 | -stdin Deprecated / No-op 37 | -no-stdin Disable text content update via stdin 38 | -windowed Do not force window to fullscreen 39 | -independent-lines Scale lines independently 40 | -debugboxes Draw debug boxes 41 | -disable-text Do not draw text 42 | -disable-doublebuffer What it says on the tin 43 | -h | -help | --help Display usage information 44 | -v[v[v[v]]] Increase verbosity 45 | 46 | Where <colorspec> is either an X Color name (blue, red, 47 | yellow etc) or an HTML-style RGB value (#rrggbb), 48 | <fontspec> is a freetype font name (e.g. verdana, monospace) 49 | and <alignspec> is one of n|ne|e|se|s|sw|w|nw 50 | 51 | Options must be given before a text argument starts. 52 | Command line option parsing can be stopped with --, 53 | eg.: ./xecho -bc blue -fc yellow -- -help shows usage information 54 | 55 | Text passed via the command line is scanned once for 56 | control character encodings (\n and \\), which 57 | are replaced by their ASCII codepoints. 58 | 59 | By default, xecho reads text from stdin and appends it to the window 60 | content. Control characters on stdin are handled as follows 61 | \n Starts new line 62 | \f Clears display 63 | \r Clears current line 64 | \b Backspace 65 | 66 | Usage examples: 67 | 68 | while :; do printf "\f%s" "`date`" \ 69 | && sleep 1; done | ./xecho 70 | 71 | Displays the current date updated by every second. 72 | The output of `date` is handled by printf to avoid 73 | a race condition where the pipe is flushed after 74 | the form feed, but before date has printed its 75 | output, thus leading to flicker. 76 | 77 | Build prerequisites: 78 | - libxft-dev 79 | - libx11-dev 80 | - libxext-dev 81 | - pkg-config 82 | - A C compiler (tcc does the trick) 83 | 84 | To compile, simply run make. 85 | 86 | -------------------------------------------------------------------------------- /src/legacy/xft_debug.c: -------------------------------------------------------------------------------- 1 | /* 2 | This file contains verbatim code taken from the Xft source, 3 | modified to display more debug info. 4 | 5 | Copyright © 2001,2003 Keith Packard 6 | 7 | Permission to use, copy, modify, distribute, and sell this software and its 8 | documentation for any purpose is hereby granted without fee, provided that 9 | the above copyright notice appear in all copies and that both that 10 | copyright notice and this permission notice appear in supporting 11 | documentation, and that the name of Keith Packard not be used in 12 | advertising or publicity pertaining to distribution of the software without 13 | specific, written prior permission. Keith Packard makes no 14 | representations about the suitability of this software for any purpose. It 15 | is provided "as is" without express or implied warranty. 16 | 17 | KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, 18 | INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO 19 | EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR 20 | CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, 21 | DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 22 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 23 | PERFORMANCE OF THIS SOFTWARE. 24 | 25 | 26 | */ 27 | static short maskbase (unsigned long m){ 28 | short i; 29 | if (!m) 30 | return 0; 31 | i = 0; 32 | while (!(m&1)){ 33 | m>>=1; 34 | i++; 35 | } 36 | return i; 37 | } 38 | 39 | static short masklen (unsigned long m){ 40 | unsigned long y; 41 | y = (m >> 1) &033333333333; 42 | y = m - y - ((y >>1) & 033333333333); 43 | return (short) (((y + (y >> 3)) & 030707070707) % 077); 44 | } 45 | 46 | 47 | Bool XftColorAllocValueDebug (Display *dpy, Visual *visual, Colormap cmap, XRenderColor *color, XftColor *result){ 48 | if (visual->class == TrueColor){ 49 | printf("xft_coloralloc: truecolor mode\n"); 50 | int red_shift, red_len; 51 | int green_shift, green_len; 52 | int blue_shift, blue_len; 53 | 54 | red_shift = maskbase (visual->red_mask); 55 | red_len = masklen (visual->red_mask); 56 | green_shift = maskbase (visual->green_mask); 57 | green_len = masklen (visual->green_mask); 58 | blue_shift = maskbase (visual->blue_mask); 59 | blue_len = masklen (visual->blue_mask); 60 | 61 | printf("Red Length: %d Shift: %d, Value %d\n", red_len, red_shift, color->red); 62 | printf("Green Length: %d Shift: %d, Value %d\n", green_len, green_shift, color->green); 63 | printf("Blue Length: %d Shift: %d, Value %d\n", blue_len, blue_shift, color->blue); 64 | 65 | int red_value=((color->red >> (16 - red_len)) << red_shift); 66 | int green_value= ((color->green >> (16 - green_len)) << green_shift) ; 67 | int blue_value=((color->blue >> (16 - blue_len)) << blue_shift); 68 | 69 | result->pixel = ( red_value|green_value|blue_value ); 70 | printf("Pixel value: %d (%x=%x|%x|%x)\n", result->pixel, result->pixel, red_value, green_value, blue_value); 71 | } 72 | else{ 73 | printf("xft_coloralloc: non-truecolor mode\n"); 74 | XColor xcolor; 75 | 76 | xcolor.red = color->red; 77 | xcolor.green = color->green; 78 | xcolor.blue = color->blue; 79 | if (!XAllocColor (dpy, cmap, &xcolor)) 80 | return False; 81 | result->pixel = xcolor.pixel; 82 | } 83 | result->color.red = color->red; 84 | result->color.green = color->green; 85 | result->color.blue = color->blue; 86 | result->color.alpha = color->alpha; 87 | return True; 88 | } 89 | 90 | -------------------------------------------------------------------------------- /src/strings.c: -------------------------------------------------------------------------------- 1 | bool string_preprocess(char* input, bool handle_escapes){ 2 | unsigned i; 3 | int c, text_pos = 0; 4 | 5 | if(!input){ 6 | return false; 7 | } 8 | 9 | for(i = 0; input[i]; i++){ 10 | input[text_pos] = input[i]; 11 | 12 | //fprintf(stderr, "Handling input character %c at %d\n", input[i], i); 13 | 14 | if(handle_escapes && input[i] == '\\'){ 15 | //fprintf(stderr, "Handling escape sequence at %d\n", i); 16 | switch(input[i + 1]){ 17 | case '\\': 18 | i += 1; 19 | break; 20 | case 'n': 21 | input[text_pos] = '\n'; 22 | i += 1; 23 | break; 24 | } 25 | } 26 | 27 | switch(input[i]){ 28 | //handle tab? 29 | case '\r': 30 | //skip back to last newline 31 | //FIXME handle \r\n 32 | for(c = text_pos - 1; c >= 0 && input[c]; c--){ 33 | if(input[c] == '\n'){ 34 | break; 35 | } 36 | } 37 | text_pos = c; 38 | break; 39 | case '\n': 40 | //is handled by blockify 41 | break; 42 | case '\f': 43 | //reset text buffer (if data is present) 44 | if(input[i + 1]){ 45 | text_pos = -1; //is increased directly below 46 | } 47 | else{ 48 | //trailing \f, handle in blockify 49 | //in order to be roughly compatible 50 | //to sm 51 | } 52 | break; 53 | case '\b': 54 | //delete one character back, but not over newline 55 | if(i > 0 && input[i - 1] != '\n'){ 56 | text_pos -= 2; 57 | } 58 | break; 59 | } 60 | 61 | text_pos++; 62 | } 63 | input[text_pos] = 0; 64 | 65 | return true; 66 | } 67 | 68 | bool string_block_store(TEXTBLOCK* block, char* stream, unsigned length){ 69 | block->text = realloc(block->text, (length + 1) * sizeof(char)); 70 | if(!(block->text)){ 71 | fprintf(stderr, "Failed to allocate memory\n"); 72 | return false; 73 | } 74 | 75 | strncpy(block->text, stream, length); 76 | (block->text)[length] = 0; 77 | 78 | block->active = true; 79 | return true; 80 | } 81 | 82 | unsigned string_block_longest(TEXTBLOCK** blocks){ 83 | unsigned i; 84 | unsigned longest_length = 0, longest_index = 0; 85 | for(i = 0; blocks[i] && blocks[i]->active; i++){ 86 | if(!(blocks[i]->calculated)){ 87 | if(strlen(blocks[i]->text) > longest_length){ 88 | longest_index = i; 89 | longest_length = strlen(blocks[i]->text); 90 | } 91 | } 92 | } 93 | return longest_index; 94 | } 95 | 96 | bool string_blockify(TEXTBLOCK*** blocks, char* input){ 97 | unsigned i, num_blocks = 0, blocks_needed = 1, input_offset = 0, current_block = 0; 98 | 99 | for(i = 0; input[i]; i++){ 100 | if(input[i] == '\n' || input[i] == '\f'){ 101 | blocks_needed++; 102 | } 103 | } 104 | 105 | if(!(*blocks)){ 106 | //fprintf(stderr, "Initially allocating block container for %d pointers\n", blocks_needed+1); 107 | //allocate array structure 108 | (*blocks) = calloc(blocks_needed + 1, sizeof(TEXTBLOCK*)); 109 | if(!(*blocks)){ 110 | fprintf(stderr, "Failed to allocate memory\n"); 111 | return false; 112 | } 113 | num_blocks = blocks_needed; 114 | } 115 | else{ 116 | for(; (*blocks)[num_blocks]; num_blocks++){ 117 | } 118 | 119 | //fprintf(stderr, "%d blocks currently initialized in set, need %d\n", num_blocks, blocks_needed); 120 | if(num_blocks < blocks_needed){ 121 | //reallocate for more slots 122 | (*blocks) = realloc((*blocks), (blocks_needed + 1) * sizeof(TEXTBLOCK*)); 123 | if(!(*blocks)){ 124 | fprintf(stderr, "Failed to allocate memory\n"); 125 | return false; 126 | } 127 | 128 | for(; num_blocks <= blocks_needed; num_blocks++){ 129 | (*blocks)[num_blocks] = NULL; 130 | } 131 | 132 | //fprintf(stderr, "After alloc session, now at %d slots\n", num_blocks); 133 | num_blocks--; 134 | } 135 | else{ 136 | //fprintf(stderr, "Not touching block set, is big enough\n"); 137 | } 138 | } 139 | 140 | //fill empty pointers (else the counting structure is borken) 141 | for(i = 0; i < num_blocks; i++){ 142 | if(!(*blocks)[i]){ 143 | (*blocks)[i] = calloc(1, sizeof(TEXTBLOCK)); 144 | } 145 | (*blocks)[i]->active = false; 146 | } 147 | 148 | for(i = 0; input[i]; i++){ 149 | if(input[i] == '\n' || input[i] == '\f'){ 150 | if(!string_block_store((*blocks)[current_block++], input + input_offset, i - input_offset)){ 151 | return false; 152 | } 153 | input_offset = i + 1; 154 | } 155 | } 156 | if(!string_block_store((*blocks)[current_block], input + input_offset, i - input_offset)){ 157 | return false; 158 | } 159 | 160 | if(((*blocks)[current_block]->text)[0] == 0){ 161 | //fprintf(stderr, "Disabling last block, was empty\n"); 162 | (*blocks)[current_block]->active = false; 163 | } 164 | 165 | return true; 166 | } 167 | -------------------------------------------------------------------------------- /src/xecho.c: -------------------------------------------------------------------------------- 1 | #include "xecho.h" 2 | 3 | int usage(char* fn){ 4 | printf("xecho - Render text to X\n\n"); 5 | printf("Usage: %s <arguments> <text>\n", fn); 6 | printf("Recognized options:\n"); 7 | printf("\t--\t\t\t\tStop argument parsing,\n\t\t\t\t\ttreat all following arguments as content text\n\n"); 8 | printf("\t-font <fontspec>\t\tSelect font by FontConfig name\n\n"); 9 | printf("\t-fc <colorspec>\t\t\tSet text color by name or HTML code\n\n"); 10 | printf("\t-bc <colorspec>\t\t\tSet background color by name or code\n\n"); 11 | printf("\t-dc <colorspec>\t\t\tSet debug color by name or code\n\n"); 12 | printf("\t-title <title>\t\t\tSet window title\n\n"); 13 | printf("\t-size <n>\t\t\tRender at font size n\n\n"); 14 | printf("\t-maxsize <n>\t\t\tLimit font size to n at max\n\n"); 15 | printf("\t-align [n|ne|e|se|s|sw|w|nw]\tAlign text\n\n"); 16 | printf("\t-padding <n>\t\t\tPad text by n pixels\n\n"); 17 | printf("\t-linespacing <n>\t\tPad lines by n pixels\n\n"); 18 | printf("Recognized flags:\n"); 19 | printf("\t-no-stdin\t\t\tDisable content updates from stdin\n\n"); 20 | printf("\t-windowed\t\t\tDo not try to force a fullscreen window\n\n"); 21 | printf("\t-independent-lines\t\tResize every line individually\n\n"); 22 | printf("\t-debugboxes\t\t\tDraw debug boxes\n\n"); 23 | printf("\t-disable-text\t\t\tDo not render text at all.\n\t\t\t\t\tMight be useful for playing tetris.\n\n"); 24 | printf("\t-disable-doublebuffer\t\tDo not use XDBE\n\n"); 25 | printf("\t-h | -help | --help\t\tPrint this usage information\n\n"); 26 | printf("\t-v[v[v]]\t\t\tIncrease output verbosity\n\n"); 27 | printf("stdin content update protocol:\n"); 28 | printf("\t\\f (Form feed) clears text,\n"); 29 | printf("\t\\r (Carriage return) clears current line\n\n"); 30 | return 1; 31 | } 32 | 33 | void errlog(CFG* config, unsigned level, char* fmt, ...){ 34 | va_list args; 35 | va_start(args, fmt); 36 | if(config->verbosity >= level){ 37 | vfprintf(stderr, fmt, args); 38 | } 39 | va_end(args); 40 | } 41 | 42 | int main(int argc, char** argv){ 43 | CFG config = { 44 | .verbosity = 0, 45 | .padding = 0, 46 | .line_spacing = 0, 47 | .max_size = 0, 48 | .alignment = ALIGN_CENTER, 49 | .independent_resize = false, 50 | .handle_stdin = true, 51 | .debug_boxes = false, 52 | .disable_text = false, 53 | .double_buffer = true, 54 | .windowed = false, 55 | .print_usage = false, 56 | .force_size = 0, 57 | .text_color = NULL, 58 | .bg_color = NULL, 59 | .debug_color = NULL, 60 | .font_name = NULL, 61 | .window_name = NULL 62 | }; 63 | 64 | XRESOURCES xres = { 65 | .screen = 0, 66 | .display = NULL, 67 | .main = 0, 68 | .back_buffer = 0, 69 | .drawable = NULL, 70 | .text_color = {}, 71 | .bg_color = {}, 72 | .debug_color = {}, 73 | .wm_delete = 0, 74 | .xfds = {NULL, 0} 75 | }; 76 | 77 | int args_end; 78 | unsigned text_length, i; 79 | char* args_text = NULL; 80 | long flags; 81 | 82 | //parse command line arguments 83 | args_end = args_parse(&config, argc - 1, argv + 1); 84 | if(argc - args_end < 1 && !config.handle_stdin){ 85 | return usage(argv[0]); 86 | } 87 | 88 | //config sanity check 89 | if(!args_sane(&config)){ 90 | args_cleanup(&config); 91 | return usage(argv[0]); 92 | } 93 | 94 | //set up x11 display 95 | if(!x11_init(&xres, &config)){ 96 | x11_cleanup(&xres, &config); 97 | args_cleanup(&config); 98 | return usage(argv[0]); 99 | } 100 | 101 | //preprocess display text if given 102 | if(argc - args_end > 0){ 103 | //copy arguments into buffer 104 | text_length = 0; 105 | for(i = args_end; i < argc; i++){ 106 | text_length += strlen(argv[i]) + 1; 107 | } 108 | 109 | args_text = calloc(text_length + 1, sizeof(char)); 110 | 111 | text_length = 0; 112 | for(i = args_end; i < argc; i++){ 113 | strncpy(args_text + text_length, argv[i], strlen(argv[i])); 114 | text_length += strlen(argv[i]); 115 | args_text[text_length++] = ' '; 116 | } 117 | args_text[((text_length > 0) ? text_length:1) - 1] = 0; 118 | 119 | errlog(&config, LOG_INFO, "Input text:\n\"%s\"\n", args_text); 120 | 121 | //preprocess 122 | if(!string_preprocess(args_text, true)){ 123 | fprintf(stderr, "Failed to preprocess input text\n"); 124 | x11_cleanup(&xres, &config); 125 | args_cleanup(&config); 126 | return usage(argv[0]); 127 | } 128 | 129 | errlog(&config, LOG_DEBUG, "Printing text:\n\"%s\"\n", args_text); 130 | } 131 | 132 | //prepare stdin 133 | if(config.handle_stdin){ 134 | errlog(&config, LOG_INFO, "Marking stdin as nonblocking\n"); 135 | flags = fcntl(0, F_GETFL, 0); 136 | flags |= O_NONBLOCK; 137 | fcntl(0, F_SETFL, flags); 138 | } 139 | 140 | //enter main loop 141 | xecho(&config, &xres, args_text); 142 | 143 | //clear data 144 | x11_cleanup(&xres, &config); 145 | args_cleanup(&config); 146 | 147 | if(args_text){ 148 | free(args_text); 149 | } 150 | 151 | errlog(&config, LOG_INFO, "xecho shutdown ok\n"); 152 | 153 | return 0; 154 | } 155 | -------------------------------------------------------------------------------- /man/man1/xecho.1: -------------------------------------------------------------------------------- 1 | .TH XECHO 1 "August 2016" "v1.1" 2 | 3 | .SH NAME 4 | xecho \- Render text to simple X Windows 5 | 6 | .SH SYNOPSIS 7 | .BI "xecho [-h | -help | --help] [-font " fontspec "] [-title " title "] [-bc " colorspec "] [-fc " colorspec "] " 8 | .BI "[-dc " fontspec "] [-size " size "] [-maxsize " size "] [-align " alignspec "] " 9 | .BI "[-padding " n "] [-linespacing " n "] [-no-stdin] [-windowed] [-independent-lines] [-debugboxes] " 10 | .BI "[-disable-text] [-disable-doublebuffer] [-v[v[v[v]]]] " text 11 | 12 | .SH DESCRIPTION 13 | .BR xecho " takes text from the command line or from the standard" 14 | input and displays it at the largest possible font size in an X11 window. 15 | 16 | .SH OPTIONS 17 | 18 | .TP 19 | .B -- 20 | Stop parsing arguments and treat all following arguments as input text. 21 | .RS 22 | .B Example: 23 | $ xecho -font monospace -- -font monospace looks like this 24 | .RE 25 | 26 | .TP 27 | .BI "-font " fontspec 28 | .RI "Set the font to be used for rendering. " font " may be any freetype font name or specification." 29 | .RS 30 | .B Example: 31 | $ xecho -font monospace Yay, monospace! 32 | .RE 33 | 34 | .TP 35 | .BI "-title " title 36 | set the X window title. This will be displayed somewhere by most window managers. 37 | .RS 38 | .B Example: 39 | $ xecho -title foo Bar 40 | .RE 41 | 42 | .TP 43 | .BI "-bc | -fc | -dc " colorspec 44 | .RI "Set background/text/debug color. " colorspec " may be any X11 color name such as " 45 | .BR red " or " blue ", as well as any HTML-style hex-encoded RGB value, for example" 46 | .BR #ff00ff " or " #f00f00 "." 47 | .RS 48 | .B Example: 49 | $ xecho -bc red -fc #00ff00 Looks great! 50 | .RE 51 | 52 | .TP 53 | .BI "-size " n 54 | Force font size to render at. 55 | .RS 56 | .B Example: 57 | $ xecho -size 10 Tiny text 58 | .RE 59 | 60 | .TP 61 | .BI "-maxsize " n 62 | Limit the maximum size text is rendered at. 63 | .RS 64 | .B Example: 65 | $ xecho -maxsize 100 Size limited text 66 | .RE 67 | 68 | .TP 69 | .BI "-align " alignspec 70 | Align all text according to a compass direction. 71 | .IR alignspec " must be one of 72 | .BR "n, ne, e, se, s, sw, w, nw." 73 | .RS 74 | .B Example: 75 | $ xecho -align s This text will be at the bottom of the window 76 | .RE 77 | 78 | .TP 79 | .BI "-padding " n 80 | .RI "Pad the window display area by " n " pixels." 81 | .RS 82 | .B Example: 83 | $ xecho -padding 50 This text will be padded by 50 pixels 84 | .RE 85 | 86 | .TP 87 | .BI "-linespacing " n 88 | .RI "Pad " n " pixels between lines." 89 | .RS 90 | .B Example: 91 | $ xecho -linespacing 50 "There are 50 pixels\enBetween these lines" 92 | .RE 93 | 94 | .SH FLAGS 95 | 96 | .TP 97 | .B -h | -help | --help 98 | Print usage information. 99 | 100 | .TP 101 | .B -stdin 102 | Deprecated / No-op. Reading date from stdin has since become the standard and 103 | this argument is only provided for compatability reasons. 104 | 105 | .TP 106 | .B -no-stdin 107 | Disable text content updates from stdin (see 108 | .B STDIN UPDATE PROTOCOL 109 | below for more information) 110 | .RS 111 | .B Example: 112 | $ xecho -no-stdin This text can not be updated 113 | .RE 114 | 115 | .TP 116 | .B -independent-lines 117 | Maximize every line for itself. 118 | .RS 119 | .B Example: 120 | $ xecho -independent-lines "This line will be big\enThis line will be comparatively small" 121 | .RE 122 | 123 | .TP 124 | .B -debugboxes 125 | Draw debug boxes to indicate text bounding boxes. 126 | .RS 127 | .B Example: 128 | $ xecho -debugboxes foo 129 | .RE 130 | 131 | .TP 132 | .B -disable-text 133 | Do not print text at all. Might be useful for playing tetris. 134 | .RS 135 | .B Example: 136 | $ xecho -disable-text There goes nothing 137 | .RE 138 | 139 | .TP 140 | .B -disable-doublebuffer 141 | Disable double buffering via the XDBE extension. 142 | .RS 143 | .B Example: 144 | $ xecho -disable-doublebuffer Resizing this window might flicker 145 | .RE 146 | 147 | .TP 148 | .B -windowed 149 | Do not try to force full screen display. 150 | .RS 151 | .B Example: 152 | $ xecho -windowed This window can be resized 153 | .RE 154 | 155 | .TP 156 | .B -v[v[v[v]]] 157 | Increase log output verbosity. 158 | .RS 159 | .B Example: 160 | $ xecho -vvvv Tell me more! 161 | .RE 162 | 163 | .SH STDIN UPDATE PROTOCOL 164 | By default, xecho reads text from stdin and appends it to the window content. 165 | Some control characters are assigned special functions to allow for advanced usage. 166 | .RS 167 | .BR "\en" " Start new line" 168 | .RE 169 | .RS 170 | .BR "\ef" " Clear display" 171 | .RE 172 | .RS 173 | .BR "\er" " Clear current line" 174 | .RE 175 | .RS 176 | .BR "\eb" " Backspace" 177 | .RE 178 | .RS 179 | .B Example: 180 | $ while :; do printf "\ef%s" "$(date)" && sleep 1; done | xecho 181 | .RE 182 | The stdin update facility can be disabled with the 183 | .B -no-stdin 184 | argument. 185 | 186 | 187 | .SH BUGS 188 | Font size calculation might be slow on old systems. 189 | 190 | .SH AUTHOR 191 | cbdev <cb at cbcdn dot com> 192 | 193 | .SH SEE ALSO 194 | .BR xecho " was written as a lightweight replacement for " sm "(6), which does basically the same thing," 195 | but with an integrated editor and more dependencies. 196 | -------------------------------------------------------------------------------- /src/arguments.c: -------------------------------------------------------------------------------- 1 | bool arg_copy(char** dest, char* src){ 2 | (*dest) = calloc(strlen(src) + 1, sizeof(char)); 3 | if(!(*dest)){ 4 | fprintf(stderr, "Failed to allocate memory\n"); 5 | return false; 6 | } 7 | strncpy((*dest), src, strlen(src)); 8 | return true; 9 | } 10 | 11 | int args_parse(CFG* config, int argc, char** argv){ 12 | unsigned i, c; 13 | 14 | for(i = 0; i < argc ; i++){ 15 | if(!strcmp(argv[i], "-padding")){ 16 | if(++i < argc){ 17 | config->padding = strtoul(argv[i], NULL, 10); 18 | } 19 | else{ 20 | fprintf(stderr, "No parameter for padding\n"); 21 | return -1; 22 | } 23 | } 24 | else if(!strcmp(argv[i], "-linespacing")){ 25 | if(++i < argc){ 26 | config->line_spacing = strtoul(argv[i], NULL, 10); 27 | } 28 | else{ 29 | fprintf(stderr, "No parameter for line spacing\n"); 30 | return -1; 31 | } 32 | } 33 | else if(!strcmp(argv[i], "-maxsize")){ 34 | if(++i < argc){ 35 | config->max_size = strtoul(argv[i], NULL, 10); 36 | } 37 | else{ 38 | fprintf(stderr, "No parameter for max size\n"); 39 | return -1; 40 | } 41 | } 42 | else if(!strcmp(argv[i], "-size")){ 43 | if(++i < argc){ 44 | config->force_size = (double)strtoul(argv[i], NULL, 10); 45 | } 46 | else{ 47 | fprintf(stderr, "No parameter for size\n"); 48 | return -1; 49 | } 50 | } 51 | else if(!strcmp(argv[i], "-independent-lines")){ 52 | config->independent_resize = true; 53 | } 54 | else if(!strcmp(argv[i], "-disable-text")){ 55 | config->disable_text = true; 56 | } 57 | else if(!strcmp(argv[i], "-disable-doublebuffer")){ 58 | config->double_buffer = false; 59 | } 60 | else if(!strcmp(argv[i], "-windowed")){ 61 | config->windowed = true; 62 | } 63 | //this parameter is now a noop and deprecated, but still recognized for compatability reasons 64 | else if(!strcmp(argv[i], "-stdin")){ 65 | config->handle_stdin = true; 66 | } 67 | else if(!strcmp(argv[i], "-no-stdin")){ 68 | config->handle_stdin = false; 69 | } 70 | else if(!strcmp(argv[i], "-debugboxes")){ 71 | config->debug_boxes = true; 72 | } 73 | else if(!strcmp(argv[i], "-help") || !strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")){ 74 | config->print_usage = true; 75 | } 76 | else if(!strcmp(argv[i], "-fc")){ 77 | if(++i < argc && !(config->text_color)){ 78 | if(!arg_copy(&(config->text_color), argv[i])){ 79 | return false; 80 | } 81 | } 82 | else{ 83 | fprintf(stderr, "No parameter for text color or already defined\n"); 84 | return -1; 85 | } 86 | } 87 | else if(!strcmp(argv[i], "-bc")){ 88 | if(++i < argc && !(config->bg_color)){ 89 | if(!arg_copy(&(config->bg_color), argv[i])){ 90 | return false; 91 | } 92 | } 93 | else{ 94 | fprintf(stderr, "No parameter for window color or already defined\n"); 95 | return -1; 96 | } 97 | } 98 | else if(!strcmp(argv[i], "-dc")){ 99 | if(++i < argc && !(config->debug_color)){ 100 | if(!arg_copy(&(config->debug_color), argv[i])){ 101 | return false; 102 | } 103 | } 104 | else{ 105 | fprintf(stderr, "No parameter for debug color or already defined\n"); 106 | return -1; 107 | } 108 | } 109 | else if(!strcmp(argv[i], "-font")){ 110 | if(++i < argc && !(config->font_name)){ 111 | if(!arg_copy(&(config->font_name), argv[i])){ 112 | return false; 113 | } 114 | } 115 | else{ 116 | fprintf(stderr, "No parameter for font or already defined\n"); 117 | return -1; 118 | } 119 | } 120 | else if(!strcmp(argv[i], "-title")){ 121 | if(++i < argc && !(config->window_name)){ 122 | if(!arg_copy(&(config->window_name), argv[i])){ 123 | return false; 124 | } 125 | } 126 | else{ 127 | fprintf(stderr, "No parameter for window name or already defined\n"); 128 | return -1; 129 | } 130 | } 131 | else if(!strcmp(argv[i], "-align")){ 132 | if(++i < argc){ 133 | switch(argv[i][0]){ 134 | case 'n': 135 | case 'N': 136 | switch(argv[i][1]){ 137 | case 'e': 138 | case 'E': 139 | config->alignment = ALIGN_NORTHEAST; 140 | break; 141 | case 'w': 142 | case 'W': 143 | config->alignment = ALIGN_NORTHWEST; 144 | break; 145 | default: 146 | config->alignment = ALIGN_NORTH; 147 | } 148 | break; 149 | case 'e': 150 | case 'E': 151 | config->alignment = ALIGN_EAST; 152 | break; 153 | case 's': 154 | case 'S': 155 | switch(argv[i][1]){ 156 | case 'e': 157 | case 'E': 158 | config->alignment = ALIGN_SOUTHEAST; 159 | break; 160 | case 'w': 161 | case 'W': 162 | config->alignment = ALIGN_SOUTHWEST; 163 | break; 164 | default: 165 | config->alignment = ALIGN_SOUTH; 166 | } 167 | break; 168 | case 'w': 169 | case 'W': 170 | config->alignment = ALIGN_WEST; 171 | break; 172 | default: 173 | fprintf(stderr, "Invalid alignment specifier\n"); 174 | return -1; 175 | } 176 | } 177 | else{ 178 | fprintf(stderr, "No parameter for alignment\n"); 179 | return -1; 180 | } 181 | } 182 | else if(!strncmp(argv[i], "-v", 2)){ 183 | for(c = 1; argv[i][c] == 'v'; c++){ 184 | } 185 | config->verbosity = c - 1; 186 | } 187 | else if(!strncmp(argv[i], "--", 2)){ 188 | i++; 189 | break; 190 | } 191 | else{ 192 | break; 193 | } 194 | } 195 | 196 | return i + 1; 197 | } 198 | 199 | bool args_sane(CFG* config){ 200 | //string memory is allocated in order to be able to 201 | //simply free() them later 202 | 203 | if(!(config->font_name)){ 204 | errlog(config, LOG_INFO, "No font name specified, using default.\n"); 205 | if(!arg_copy(&(config->font_name), DEFAULT_FONT)){ 206 | return false; 207 | } 208 | } 209 | 210 | if(!(config->window_name)){ 211 | errlog(config, LOG_INFO, "No window name specified, using default.\n"); 212 | if(!arg_copy(&(config->window_name), DEFAULT_WINDOWNAME)){ 213 | return false; 214 | } 215 | } 216 | 217 | if(!(config->bg_color)){ 218 | errlog(config, LOG_INFO, "No window color specified, using default\n"); 219 | if(!arg_copy(&(config->bg_color), DEFAULT_WINCOLOR)){ 220 | return false; 221 | } 222 | } 223 | 224 | if(!(config->text_color)){ 225 | errlog(config, LOG_INFO, "No text color specified, using default\n"); 226 | if(!arg_copy(&(config->text_color), DEFAULT_TEXTCOLOR)){ 227 | return false; 228 | } 229 | } 230 | 231 | if(!(config->debug_color)){ 232 | errlog(config, LOG_INFO, "No debug color specified, using default\n"); 233 | if(!arg_copy(&(config->debug_color), DEFAULT_DEBUGCOLOR)){ 234 | return false; 235 | } 236 | } 237 | 238 | if(config->verbosity > 1){ 239 | fprintf(stderr, "Config summary\n"); 240 | fprintf(stderr, "Verbosity level: %d\n", config->verbosity); 241 | fprintf(stderr, "Text padding: %d\n", config->padding); 242 | fprintf(stderr, "Line spacing: %d\n", config->line_spacing); 243 | fprintf(stderr, "Maximum size: %d\n", config->max_size); 244 | fprintf(stderr, "Text alignment: %d\n", config->alignment); 245 | fprintf(stderr, "Resize lines independently: %s\n", config->independent_resize ? "true":"false"); 246 | fprintf(stderr, "Handle stdin: %s\n", config->handle_stdin ? "true":"false"); 247 | fprintf(stderr, "Draw debug boxes: %s\n", config->debug_boxes ? "true":"false"); 248 | fprintf(stderr, "Disable text draw: %s\n", config->disable_text ? "true":"false"); 249 | fprintf(stderr, "Forced text size: %d\n", (int)config->force_size); 250 | fprintf(stderr, "Text colorspec: %s\n", config->text_color); 251 | fprintf(stderr, "Window colorspec: %s\n", config->bg_color); 252 | fprintf(stderr, "Debug colorspec: %s\n", config->debug_color); 253 | fprintf(stderr, "Font name: %s\n", config->font_name); 254 | } 255 | 256 | if(config->print_usage){ 257 | return false; 258 | } 259 | 260 | return true; 261 | } 262 | 263 | void args_cleanup(CFG* config){ 264 | free(config->text_color); 265 | free(config->bg_color); 266 | free(config->debug_color); 267 | free(config->font_name); 268 | free(config->window_name); 269 | } 270 | -------------------------------------------------------------------------------- /src/logic.c: -------------------------------------------------------------------------------- 1 | int xecho(CFG* config, XRESOURCES* xres, char* initial_text){ 2 | fd_set readfds; 3 | struct timeval tv; 4 | int maxfd, error; 5 | unsigned i; 6 | int abort=0; 7 | XEvent event; 8 | XdbeSwapInfo swap_info; 9 | 10 | bool reconfigured = false, exposed = false; 11 | 12 | unsigned window_width, window_height; 13 | unsigned display_buffer_length = 0, display_buffer_offset; 14 | 15 | TEXTBLOCK** blocks = NULL; 16 | char* display_buffer = NULL; 17 | char pressed_key; 18 | 19 | //get initial sizes 20 | window_width = DisplayWidth(xres->display, xres->screen); 21 | window_height = DisplayHeight(xres->display, xres->screen); 22 | 23 | //prepare initial block buffer 24 | if(initial_text){ 25 | if(!string_blockify(&blocks, initial_text)){ 26 | fprintf(stderr, "Failed to blockify initial input text\n"); 27 | return -1; 28 | } 29 | 30 | //recalculate blocks 31 | if(!x11_recalculate_blocks(config, xres, blocks, window_width, window_height)){ 32 | fprintf(stderr, "Block calculation failed\n"); 33 | return -1; 34 | } 35 | } 36 | 37 | //copy initial text to stdin buffer 38 | if(config->handle_stdin){ 39 | display_buffer_length = STDIN_DATA_CHUNK + (initial_text ? strlen(initial_text):0) + 1; 40 | display_buffer = calloc(display_buffer_length, sizeof(char)); 41 | if(!display_buffer){ 42 | fprintf(stderr, "Failed to allocate memory\n"); 43 | return -1; 44 | } 45 | if(initial_text){ 46 | strncpy(display_buffer, initial_text, strlen(initial_text)); 47 | } 48 | } 49 | 50 | while(!abort){ 51 | //indicators for aggregating events 52 | reconfigured = false; 53 | exposed = false; 54 | 55 | //handle events 56 | while(XPending(xres->display)){ 57 | XNextEvent(xres->display, &event); 58 | //handle events 59 | switch(event.type){ 60 | case ConfigureNotify: 61 | //trigger block recalculation 62 | reconfigured = true; 63 | errlog(config, LOG_INFO, "Window configured to %dx%d\n", event.xconfigure.width, event.xconfigure.height); 64 | if(window_width != event.xconfigure.width || window_height != event.xconfigure.height){ 65 | window_width = event.xconfigure.width; 66 | window_height = event.xconfigure.height; 67 | } 68 | else{ 69 | errlog(config, LOG_DEBUG, "Configuration not changed, ignoring\n"); 70 | } 71 | break; 72 | 73 | case Expose: 74 | //trigger frame redraw 75 | exposed = true; 76 | break; 77 | 78 | case KeyPress: 79 | //translate key event into a character, respecting keyboard layout 80 | if(XLookupString(&event.xkey, &pressed_key, 1, NULL, NULL) != 1){ 81 | //disregard combined characters / bound strings 82 | break; 83 | } 84 | switch(pressed_key){ 85 | case 'q': 86 | abort = -1; 87 | break; 88 | case 'r': 89 | errlog(config, LOG_INFO, "Redrawing on request\n"); 90 | if(!x11_recalculate_blocks(config, xres, blocks, window_width, window_height)){ 91 | fprintf(stderr, "Block calculation failed\n"); 92 | abort = -1; 93 | } 94 | event.type = Expose; 95 | XSendEvent(xres->display, xres->main, False, 0, &event); 96 | break; 97 | default: 98 | errlog(config, LOG_DEBUG, "KeyPress %d (%c)\n", event.xkey.keycode, pressed_key); 99 | break; 100 | } 101 | break; 102 | 103 | case ClientMessage: 104 | if(event.xclient.data.l[0] == xres->wm_delete){ 105 | errlog(config, LOG_INFO, "Closing down window\n"); 106 | abort = 1; 107 | } 108 | else{ 109 | errlog(config, LOG_INFO, "Client message\n"); 110 | } 111 | break; 112 | 113 | default: 114 | errlog(config, LOG_INFO, "Unhandled X event\n"); 115 | break; 116 | } 117 | } 118 | 119 | if(reconfigured){ 120 | errlog(config, LOG_DEBUG, "Recalculating blocks\n"); 121 | 122 | //recalculate size 123 | if(!x11_recalculate_blocks(config, xres, blocks, window_width, window_height)){ 124 | fprintf(stderr, "Block calculation failed\n"); 125 | abort = -1; 126 | } 127 | 128 | if(config->double_buffer){ 129 | //update drawable 130 | XftDrawChange(xres->drawable, xres->back_buffer); 131 | } 132 | } 133 | 134 | if(reconfigured || exposed){ 135 | //draw here 136 | errlog(config, LOG_INFO, "Window exposed or reconfigured, initiating redraw\n"); 137 | if(!config->double_buffer){ 138 | errlog(config, LOG_DEBUG, "Clearing window\n"); 139 | XClearWindow(xres->display, xres->main); 140 | } 141 | if(!x11_draw_blocks(config, xres, blocks)){ 142 | fprintf(stderr, "Failed to draw blocks\n"); 143 | abort = -1; 144 | } 145 | if(config->double_buffer){ 146 | errlog(config, LOG_DEBUG, "Swapping buffers\n"); 147 | swap_info.swap_window = xres->main; 148 | swap_info.swap_action = XdbeBackground; 149 | XdbeSwapBuffers(xres->display, &swap_info, 1); 150 | } 151 | } 152 | 153 | XFlush(xres->display); 154 | 155 | if(abort){ 156 | break; 157 | } 158 | 159 | //prepare select data 160 | FD_ZERO(&readfds); 161 | maxfd = -1; 162 | tv.tv_sec = 1; 163 | tv.tv_usec = 0; 164 | 165 | for(i = 0; i < xres->xfds.size; i++){ 166 | FD_SET(xres->xfds.fds[i], &readfds); 167 | if(maxfd < xres->xfds.fds[i]){ 168 | maxfd = xres->xfds.fds[i]; 169 | } 170 | } 171 | 172 | if(config->handle_stdin){ 173 | FD_SET(fileno(stdin), &readfds); 174 | if(maxfd < fileno(stdin)){ 175 | maxfd = fileno(stdin); 176 | } 177 | } 178 | 179 | error = select(maxfd + 1, &readfds, NULL, NULL, &tv); 180 | if(error > 0){ 181 | if(FD_ISSET(fileno(stdin), &readfds)){ 182 | //handle stdin input 183 | errlog(config, LOG_INFO, "Data on stdin\n"); 184 | 185 | do{ 186 | display_buffer_offset = strlen(display_buffer); 187 | errlog(config, LOG_DEBUG, "Display buffer is %d long, offset is %d\n", display_buffer_length, display_buffer_offset); 188 | if(display_buffer_length - display_buffer_offset < STDIN_DATA_CHUNK){ 189 | //reallocate 190 | display_buffer_length += STDIN_DATA_CHUNK; 191 | display_buffer = realloc(display_buffer, display_buffer_length * sizeof(char)); 192 | if(!display_buffer){ 193 | fprintf(stderr, "Failed to reallocate display data buffer\n"); 194 | abort = -1; 195 | } 196 | errlog(config, LOG_DEBUG, "Reallocated display buffer to %d bytes\n", display_buffer_length); 197 | } 198 | 199 | //read data 200 | error = read(fileno(stdin), 201 | display_buffer + display_buffer_offset, 202 | display_buffer_length - display_buffer_offset - 1 203 | ); 204 | 205 | errlog(config, LOG_DEBUG, "Read %d bytes from stdin\n", error); 206 | 207 | //terminate string 208 | if(error>0){ 209 | display_buffer[display_buffer_offset + error] = 0; 210 | } 211 | 212 | }while(error > 0); 213 | 214 | //check if stdin was closed 215 | if(error == 0){ 216 | abort = 1; 217 | } 218 | else{ 219 | switch(errno){ 220 | case EAGAIN: 221 | //would block, so done reading 222 | //preprocess input data to filter control codes 223 | if(!string_preprocess(display_buffer, false)){ 224 | fprintf(stderr, "Failed to preprocess input text\n"); 225 | abort = -1; 226 | } 227 | errlog(config, LOG_INFO, "Updated display text to\n\"%s\"\n", display_buffer); 228 | 229 | //blockify 230 | if(!string_blockify(&blocks, display_buffer)){ 231 | fprintf(stderr, "Failed to blockify updated input\n"); 232 | abort = -1; 233 | } 234 | 235 | //recalculate 236 | if(!x11_recalculate_blocks(config, xres, blocks, window_width, window_height)){ 237 | fprintf(stderr, "Block calculation failed\n"); 238 | abort = -1; 239 | } 240 | 241 | //update display 242 | event.type = Expose; 243 | XSendEvent(xres->display, xres->main, False, 0, &event); 244 | break; 245 | default: 246 | fprintf(stderr, "Failed to read stdin\n"); 247 | abort = -1; 248 | } 249 | } 250 | } 251 | } 252 | else if(error < 0){ 253 | perror("select"); 254 | abort = -1; 255 | } 256 | } 257 | 258 | //free data 259 | if(display_buffer){ 260 | free(display_buffer); 261 | } 262 | 263 | if(blocks){ 264 | //free blocks structure 265 | for(i = 0; blocks[i]; i++){ 266 | if(blocks[i]->text){ 267 | free(blocks[i]->text); 268 | } 269 | free(blocks[i]); 270 | } 271 | free(blocks); 272 | } 273 | 274 | return abort; 275 | } 276 | -------------------------------------------------------------------------------- /src/legacy/xecho_old.c: -------------------------------------------------------------------------------- 1 | /* 2 | This program is free software. It comes without any warranty, to 3 | the extent permitted by applicable law. You can redistribute it 4 | and/or modify it under the terms of the Do What The Fuck You Want 5 | To Public License, Version 2, as published by Sam Hocevar and 6 | reproduced below. 7 | 8 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 9 | Version 2, December 2004 10 | 11 | Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> 12 | 13 | Everyone is permitted to copy and distribute verbatim or modified 14 | copies of this license document, and changing it is allowed as long 15 | as the name is changed. 16 | 17 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 18 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 19 | 20 | 0. You just DO WHAT THE FUCK YOU WANT TO. 21 | */ 22 | 23 | #include <stdio.h> 24 | #include <stdbool.h> 25 | #include <X11/Xlib.h> 26 | #include <X11/Xatom.h> 27 | #include <X11/Xft/Xft.h> 28 | #include <ctype.h> 29 | 30 | //#include "xft_debug.c" 31 | 32 | volatile bool xecho_shutdown=false; 33 | 34 | /** 35 | *TODO: more debug stuff 36 | * 37 | */ 38 | 39 | struct { 40 | char* font_name; 41 | char* cs_back; 42 | char* cs_text; 43 | } OPTIONS; 44 | 45 | struct { 46 | XftDraw* xft_drawable; 47 | XftColor xft_text_color; 48 | XftColor xft_background_color; 49 | Display* display; 50 | Window main_window; 51 | } RESOURCES; 52 | 53 | void draw_string(char* text, char* font_name, int width, int height, int border, Display* display, XftDraw* drawable, XftColor c){ 54 | XGlyphInfo extents={0,0,0,0,0,0}; 55 | XftFont* font=NULL; 56 | double search_max=1, search_min=1, search_test; 57 | 58 | printf("Doing size calculation for window parameters %dx%d\n", width, height); 59 | 60 | do{ 61 | search_min=search_max; 62 | search_max*=2; 63 | 64 | printf("Trying with size %f\n",search_max); 65 | //open font at size 66 | font=XftFontOpen(display, DefaultScreen(display), XFT_FAMILY, XftTypeString, font_name, XFT_SIZE, XftTypeDouble, search_max, NULL); 67 | 68 | if(!font){ 69 | printf("Could not open font %s at size %f\n", font_name, search_max); 70 | } 71 | 72 | XftTextExtentsUtf8(display, font, text, strlen(text), &extents); 73 | XftFontClose(display, font); 74 | 75 | //reset size 76 | printf("Size %f spans %dx%d\n", search_max, extents.width, extents.height); 77 | }while(extents.width!=0&&extents.height!=0&&extents.width<width-border&&extents.height<height-border); 78 | 79 | if(extents.height==0||extents.width==0){ 80 | printf("Failed to get extents\n"); 81 | return; 82 | } 83 | 84 | printf("Doing binary search between sizes %f and %f\n", search_max, search_min); 85 | 86 | font=NULL; 87 | 88 | while(search_max>search_min){ 89 | search_test=search_max-((search_max-search_min)/2); 90 | printf("Testing size %f\n", search_test); 91 | 92 | if(search_test-search_min<1){ 93 | printf("Size found\n"); 94 | break; 95 | } 96 | 97 | if(font){ 98 | XftFontClose(display, font); 99 | } 100 | 101 | //open font at size 102 | font=XftFontOpen(display, DefaultScreen(display), XFT_FAMILY, XftTypeString, font_name, XFT_SIZE, XftTypeDouble, search_test, NULL); 103 | if(!font){ 104 | printf("Could not open font %s at size %f\n", font_name, search_max); 105 | } 106 | 107 | XftTextExtents8(display, font, text, strlen(text), &extents); 108 | 109 | if(extents.width==0||extents.height==0){ 110 | printf("Failed to get extents in binary search\n"); 111 | return; 112 | } 113 | 114 | printf("Size %f spans %dx%d\n", search_test, extents.width, extents.height); 115 | 116 | if(extents.width<width-border&&extents.height<height-border){ 117 | printf("Smaller than window\n"); 118 | search_min=search_test; 119 | } 120 | else{ 121 | printf("Bigger than window\n"); 122 | search_max=search_test; 123 | } 124 | 125 | } 126 | 127 | printf("Window width: %d, Text width: %d, Space: %d, Align Delta: %d\n", width, extents.width, width-extents.width, (width-extents.width)/2); 128 | printf("Window height: %d, Text height: %d, Space: %d, Align Delta: %d\n", height, extents.height, width-extents.height, (width-extents.height)/2); 129 | printf("Font params: ascent %d, descent %d, height %d, max_advance_width %d\n", font->ascent, font->descent, font->height, font->max_advance_width); 130 | printf("Extents: width: %d, height: %d, x: %d, y: %d, yOff: %d, xOff: %d\n", extents.width, extents.height, extents.x, extents.y, extents.yOff, extents.xOff); 131 | printf("Drawing at (%d|%d)",0, ((height-extents.height)/2)+extents.height); 132 | 133 | XftColor color=c; 134 | XftDrawRect(drawable, &color, 0, /*((height-extents.height)/2)+extents.height*/0, extents.xOff, extents.height); 135 | XftDrawString8(drawable, /*&color*/&(RESOURCES.xft_background_color), font, /*((width-extents.width)/2)*/extents.x, /*((height-extents.height)/2)+extents.height*//*extents.height*/extents.y/*height*/, text, strlen(text)); 136 | XftFontClose(display, font); 137 | } 138 | 139 | unsigned short colorspec_read_byte(char* cs){ 140 | char* hexmap="0123456789abcdef"; 141 | unsigned short rv=0; 142 | int i; 143 | if(*cs!=0&&cs[1]!=0){ 144 | for(i=0;hexmap[i]!=0&&hexmap[i]!=cs[0];i++){ 145 | } 146 | rv|=(i<<12); 147 | for(i=0;hexmap[i]!=0&&hexmap[i]!=cs[1];i++){ 148 | } 149 | rv|=(i<<8); 150 | } 151 | return rv; 152 | } 153 | 154 | XftColor colorspec_parse(char* cs, Display* display, int screen){ 155 | XftColor rv; 156 | XRenderColor xrender_color={0,0,0,0xffff}; 157 | int i; 158 | 159 | if(*cs=='#'){ 160 | if(strlen(cs)!=7){ 161 | printf("Invalid colorspec length\n"); 162 | } 163 | 164 | for(i=1;i<strlen(cs);i++){ 165 | if(!isxdigit(cs[i])){ 166 | printf("Invalid digit in colorspec: %c\n", cs[i]); 167 | return rv; 168 | } 169 | } 170 | 171 | xrender_color.red=colorspec_read_byte(cs+1); 172 | xrender_color.green=colorspec_read_byte(cs+3); 173 | xrender_color.blue=colorspec_read_byte(cs+5); 174 | 175 | printf("Read colorspec %s as r:%04x g:%04x b:%04x\n", cs, xrender_color.red, xrender_color.green, xrender_color.blue); 176 | 177 | if(!XftColorAllocValue(display, DefaultVisual(display, screen), DefaultColormap(display, screen), &xrender_color, &rv)){ 178 | printf("Failed to allocate color\n"); 179 | } 180 | } 181 | else{ 182 | if(!XftColorAllocName(display, DefaultVisual(display, screen), DefaultColormap(display, screen), cs, &rv)){ 183 | printf("Failed to get color by name\n"); 184 | } 185 | } 186 | return rv; 187 | } 188 | 189 | 190 | int main(int argc, char** argv){ 191 | int i, text_size, width, height; 192 | bool read_stdin=false; 193 | int text_argument=1; 194 | char* current_text=NULL; 195 | 196 | int screen_no; 197 | Window root_window; 198 | XSetWindowAttributes window_attributes; 199 | XEvent event; 200 | 201 | OPTIONS.font_name="verdana"; 202 | OPTIONS.cs_back="black"; 203 | OPTIONS.cs_text="white"; 204 | 205 | Atom wm_state_fullscreen; 206 | 207 | for(i=1;i<argc;i++){ 208 | if(!strcmp(argv[i], "--stdin")){ 209 | read_stdin=true; 210 | } 211 | else if(!strcmp(argv[i], "-font")){ 212 | if(i+1<argc){ 213 | OPTIONS.font_name=argv[i+1]; 214 | i++; 215 | text_argument=i+1; 216 | } 217 | else{ 218 | printf("No font specified\n"); 219 | } 220 | } 221 | else if(!strcmp(argv[i], "-bc")){ 222 | if(i+1<argc){ 223 | OPTIONS.cs_back=argv[i+1]; 224 | i++; 225 | text_argument=i+1; 226 | } 227 | else{ 228 | printf("No background color argument\n"); 229 | } 230 | } 231 | else if(!strcmp(argv[i], "-tc")){ 232 | if(i+1<argc){ 233 | OPTIONS.cs_text=argv[i+1]; 234 | i++; 235 | text_argument=i+1; 236 | } 237 | else{ 238 | printf("No text color argument\n"); 239 | } 240 | } 241 | else if(!strcmp(argv[i], "--")){ 242 | text_argument=i+1; 243 | break; 244 | } 245 | /* 246 | * -align text-align 247 | */ 248 | } 249 | 250 | if(read_stdin){ 251 | printf("Reading text from stdin\n"); 252 | } 253 | else{ 254 | text_size=0; 255 | for(i=text_argument;i<argc;i++){ 256 | text_size+=strlen(argv[i])+1; 257 | } 258 | 259 | current_text=realloc(current_text, text_size*sizeof(char)); 260 | 261 | text_size=0; 262 | for(i=text_argument;i<argc;i++){ 263 | strncpy(current_text+text_size, argv[i], strlen(argv[i])); 264 | text_size+=strlen(argv[i])+1; 265 | current_text[text_size-1]=' '; 266 | } 267 | current_text[text_size-1]=0; 268 | 269 | printf("Text to print: \"%s\"\n", current_text); 270 | } 271 | 272 | RESOURCES.display=XOpenDisplay(NULL); 273 | 274 | if(!RESOURCES.display){ 275 | printf("Failed to open display\n"); 276 | return -1; 277 | } 278 | 279 | screen_no=DefaultScreen(RESOURCES.display); 280 | root_window=RootWindow(RESOURCES.display, screen_no); 281 | 282 | printf("X Server connected\n"); 283 | 284 | //set up xft 285 | XftInit(NULL); 286 | 287 | printf("Parsing colorspecs\n"); 288 | RESOURCES.xft_text_color=colorspec_parse(OPTIONS.cs_text, RESOURCES.display, screen_no); 289 | RESOURCES.xft_background_color=colorspec_parse(OPTIONS.cs_back, RESOURCES.display, screen_no); 290 | printf("Done parsing\n"); 291 | 292 | //create window 293 | window_attributes.background_pixel=RESOURCES.xft_background_color.pixel; 294 | //window_attributes.background_pixel=XBlackPixel(display, screen_no); 295 | window_attributes.cursor=None; 296 | window_attributes.event_mask=ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask; 297 | 298 | width=DisplayWidth(RESOURCES.display, screen_no); 299 | height=DisplayHeight(RESOURCES.display, screen_no); 300 | RESOURCES.main_window=XCreateWindow(RESOURCES.display, root_window, 0, 0, width, height, 0, CopyFromParent, InputOutput, CopyFromParent, CWBackPixel | CWCursor | CWEventMask, &window_attributes); 301 | 302 | //set window properties 303 | //XSetWMProperties 304 | XSizeHints* size_hints=XAllocSizeHints(); 305 | XWMHints* wm_hints=XAllocWMHints(); 306 | XClassHint* class_hints=XAllocClassHint(); 307 | 308 | if(!size_hints||!wm_hints||!class_hints){ 309 | //FIXME should probably exit here 310 | printf("Failed to allocate hinting structures\n"); 311 | } 312 | 313 | class_hints->res_name=argv[0]; 314 | class_hints->res_class="xecho"; 315 | 316 | //XSetWMProperties(RESOURCES.display, RESOURCES.main_window, "xecho", NULL, argv, argc, size_hints, wm_hints, class_hints); 317 | 318 | XFree(size_hints); 319 | XFree(wm_hints); 320 | XFree(class_hints); 321 | 322 | //set fullscreen mode 323 | wm_state_fullscreen=XInternAtom(RESOURCES.display, "_NET_WM_STATE_FULLSCREEN", True); 324 | XChangeProperty(RESOURCES.display, RESOURCES.main_window, XInternAtom(RESOURCES.display, "_NET_WM_STATE", True), XA_ATOM, 32, PropModeReplace, (unsigned char*) &wm_state_fullscreen, 1); 325 | 326 | //make xft drawable from window 327 | RESOURCES.xft_drawable=XftDrawCreate(RESOURCES.display, RESOURCES.main_window, DefaultVisual(RESOURCES.display, screen_no), DefaultColormap(RESOURCES.display, screen_no)); //FIXME visuals & colormaps? 328 | if(!RESOURCES.xft_drawable){ 329 | printf("Xft: failed to allocate drawable\n"); 330 | } 331 | 332 | //map window 333 | XMapRaised(RESOURCES.display, RESOURCES.main_window); 334 | 335 | while(!xecho_shutdown){ 336 | //TODO listen to x events as well as stdin 337 | XNextEvent(RESOURCES.display,&event); 338 | switch(event.type){ 339 | case ConfigureNotify: 340 | //TODO redraw 341 | width=event.xconfigure.width; 342 | height=event.xconfigure.height; 343 | XClearWindow(RESOURCES.display, RESOURCES.main_window); 344 | draw_string(current_text, OPTIONS.font_name, width, height, 20, RESOURCES.display, RESOURCES.xft_drawable, RESOURCES.xft_text_color); 345 | printf("Configure! (%dx%d)\n", width, height); 346 | break; 347 | 348 | case Expose: 349 | printf("Expose!\n"); 350 | draw_string(current_text, OPTIONS.font_name, width, height, 20, RESOURCES.display, RESOURCES.xft_drawable, RESOURCES.xft_text_color); 351 | break; 352 | 353 | case KeyPress: 354 | printf("KeyPress!\n"); 355 | break; 356 | 357 | case ButtonPress: 358 | free(current_text); 359 | XftColorFree(RESOURCES.display, DefaultVisual(RESOURCES.display, screen_no), DefaultColormap(RESOURCES.display, screen_no), &(RESOURCES.xft_text_color)); 360 | XftDrawDestroy(RESOURCES.xft_drawable); 361 | XCloseDisplay(RESOURCES.display); 362 | exit(0); 363 | break; 364 | } 365 | } 366 | return 0; 367 | } 368 | -------------------------------------------------------------------------------- /src/x11.c: -------------------------------------------------------------------------------- 1 | bool x11_init(XRESOURCES* res, CFG* config){ 2 | Window root; 3 | XSetWindowAttributes window_attributes; 4 | unsigned width, height; 5 | Atom wm_state_fullscreen; 6 | int xdbe_major, xdbe_minor; 7 | XTextProperty window_name; 8 | pid_t pid = getpid(); 9 | 10 | //allocate some structures 11 | XSizeHints* size_hints = XAllocSizeHints(); 12 | XWMHints* wm_hints = XAllocWMHints(); 13 | XClassHint* class_hints = XAllocClassHint(); 14 | 15 | if(!size_hints || !wm_hints || !class_hints){ 16 | fprintf(stderr, "Failed to allocate X data structures\n"); 17 | return false; 18 | } 19 | 20 | //x data initialization 21 | res->display = XOpenDisplay(NULL); 22 | 23 | if(!(res->display)){ 24 | fprintf(stderr, "Failed to open display\n"); 25 | XFree(size_hints); 26 | XFree(wm_hints); 27 | XFree(class_hints); 28 | return false; 29 | } 30 | 31 | if(config->double_buffer){ 32 | config->double_buffer = (XdbeQueryExtension(res->display, &xdbe_major, &xdbe_minor) != 0); 33 | } 34 | else{ 35 | config->double_buffer = false; 36 | } 37 | errlog(config, LOG_INFO, "Double buffering %s\n", config->double_buffer ? "enabled":"disabled"); 38 | 39 | res->screen = DefaultScreen(res->display); 40 | root = RootWindow(res->display, res->screen); 41 | 42 | //start xft 43 | if(!XftInit(NULL)){ 44 | fprintf(stderr, "Failed to initialize Xft\n"); 45 | XFree(size_hints); 46 | XFree(wm_hints); 47 | XFree(class_hints); 48 | return false; 49 | } 50 | 51 | //set up colors 52 | res->text_color = colorspec_parse(config->text_color, res->display, res->screen); 53 | res->bg_color = colorspec_parse(config->bg_color, res->display, res->screen); 54 | res->debug_color = colorspec_parse(config->debug_color, res->display, res->screen); 55 | 56 | //set up window params 57 | window_attributes.background_pixel = res->bg_color.pixel; 58 | window_attributes.cursor = None; 59 | window_attributes.event_mask = ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask; 60 | width = DisplayWidth(res->display, res->screen); 61 | height = DisplayHeight(res->display, res->screen); 62 | 63 | //create window 64 | res->main = XCreateWindow(res->display, 65 | root, 66 | 0, 67 | 0, 68 | width, 69 | height, 70 | 0, 71 | CopyFromParent, 72 | InputOutput, 73 | CopyFromParent, 74 | CWBackPixel | CWCursor | CWEventMask, 75 | &window_attributes); 76 | 77 | //set window properties 78 | if(XStringListToTextProperty(&(config->window_name), 1, &window_name) == 0){ 79 | fprintf(stderr, "Failed to create string list, aborting\n"); 80 | return false; 81 | } 82 | 83 | wm_hints->flags = 0; 84 | class_hints->res_name = "xecho"; 85 | class_hints->res_class = "xecho"; 86 | 87 | XSetWMProperties(res->display, res->main, &window_name, NULL, NULL, 0, NULL, wm_hints, class_hints); 88 | 89 | XFree(window_name.value); 90 | XFree(size_hints); 91 | XFree(wm_hints); 92 | XFree(class_hints); 93 | 94 | //set fullscreen mode 95 | if(!config->windowed){ 96 | wm_state_fullscreen = XInternAtom(res->display, "_NET_WM_STATE_FULLSCREEN", False); 97 | XChangeProperty(res->display, res->main, XInternAtom(res->display, "_NET_WM_STATE", False), XA_ATOM, 32, PropModeReplace, (unsigned char*) &wm_state_fullscreen, 1); 98 | } 99 | 100 | XChangeProperty(res->display, res->main, XInternAtom(res->display, "_NET_WM_PID", False), XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&pid, 1); 101 | 102 | //allocate back drawing buffer 103 | if(config->double_buffer){ 104 | res->back_buffer = XdbeAllocateBackBufferName(res->display, res->main, XdbeBackground); 105 | } 106 | 107 | //make xft drawable from window 108 | res->drawable = XftDrawCreate(res->display, (config->double_buffer?res->back_buffer:res->main), DefaultVisual(res->display, res->screen), DefaultColormap(res->display, res->screen)); 109 | 110 | if(!res->drawable){ 111 | fprintf(stderr, "Failed to allocate drawable\n"); 112 | return false; 113 | } 114 | 115 | //register for WM_DELETE_WINDOW messages 116 | res->wm_delete = XInternAtom(res->display, "WM_DELETE_WINDOW", False); 117 | XSetWMProtocols(res->display, res->main, &(res->wm_delete), 1); 118 | 119 | //map window 120 | XMapRaised(res->display, res->main); 121 | 122 | //get x socket fds 123 | if(!xfd_add(&(res->xfds), XConnectionNumber(res->display))){ 124 | fprintf(stderr, "Failed to allocate xfd memory\n"); 125 | return false; 126 | } 127 | if(XAddConnectionWatch(res->display, xconn_watch, (void*)(&(res->xfds))) == 0){ 128 | fprintf(stderr, "Failed to register connection watch procedure\n"); 129 | return false; 130 | } 131 | 132 | return true; 133 | } 134 | 135 | void x11_cleanup(XRESOURCES* xres, CFG* config){ 136 | if(!(xres->display)){ 137 | return; 138 | } 139 | 140 | XftColorFree(xres->display, DefaultVisual(xres->display, xres->screen), DefaultColormap(xres->display, xres->screen), &(xres->text_color)); 141 | XftColorFree(xres->display, DefaultVisual(xres->display, xres->screen), DefaultColormap(xres->display, xres->screen), &(xres->bg_color)); 142 | XftColorFree(xres->display, DefaultVisual(xres->display, xres->screen), DefaultColormap(xres->display, xres->screen), &(xres->debug_color)); 143 | if(xres->drawable){ 144 | XftDrawDestroy(xres->drawable); 145 | } 146 | if(config->double_buffer){ 147 | XdbeDeallocateBackBufferName(xres->display, xres->back_buffer); 148 | } 149 | XCloseDisplay(xres->display); 150 | xfd_free(&(xres->xfds)); 151 | } 152 | 153 | bool x11_draw_blocks(CFG* config, XRESOURCES* xres, TEXTBLOCK** blocks){ 154 | unsigned i; 155 | double current_size; 156 | XftFont* font = NULL; 157 | 158 | //early exit 159 | if(!blocks || !blocks[0]){ 160 | return true; 161 | } 162 | 163 | //draw debug blocks if requested 164 | if(config->debug_boxes){ 165 | for(i = 0; blocks[i] && blocks[i]->active; i++){ 166 | XftDrawRect(xres->drawable, &(xres->debug_color), blocks[i]->layout_x, blocks[i]->layout_y, blocks[i]->extents.width, blocks[i]->extents.height); 167 | } 168 | } 169 | 170 | //draw boxes only 171 | if(config->disable_text){ 172 | return true; 173 | } 174 | 175 | //draw all blocks 176 | for(i = 0; blocks[i] && blocks[i]->active; i++){ 177 | //load font 178 | if(!font || (font && current_size != blocks[i]->size)){ 179 | if(font){ 180 | XftFontClose(xres->display, font); 181 | } 182 | font = XftFontOpen(xres->display, xres->screen, 183 | XFT_FAMILY, XftTypeString, config->font_name, 184 | XFT_PIXEL_SIZE, XftTypeDouble, blocks[i]->size, 185 | NULL 186 | ); 187 | current_size = blocks[i]->size; 188 | if(!font){ 189 | fprintf(stderr, "Failed to load block font (%s, %d)\n", config->font_name, (int)current_size); 190 | return false; 191 | } 192 | } 193 | 194 | //draw text 195 | errlog(config, LOG_DEBUG, "Drawing block %d (%s) at layoutcoords %d|%d size %d\n", i, blocks[i]->text, 196 | blocks[i]->layout_x + blocks[i]->extents.x, 197 | blocks[i]->layout_y + blocks[i]->extents.y, 198 | (int)blocks[i]->size); 199 | 200 | XftDrawStringUtf8(xres->drawable, 201 | &(xres->text_color), 202 | font, 203 | blocks[i]->layout_x + blocks[i]->extents.x, 204 | blocks[i]->layout_y + blocks[i]->extents.y, 205 | (FcChar8*)blocks[i]->text, 206 | strlen(blocks[i]->text)); 207 | } 208 | 209 | //clean up the mess 210 | if(font){ 211 | XftFontClose(xres->display, font); 212 | } 213 | 214 | return true; 215 | } 216 | 217 | bool x11_blocks_resize(XRESOURCES* xres, CFG* config, TEXTBLOCK** blocks, XGlyphInfo* bounding_box, double size){ 218 | XftFont* font = NULL; 219 | unsigned bounding_width = 0, bounding_height = 0; 220 | unsigned i; 221 | 222 | //load font with at supplied size 223 | font=XftFontOpen(xres->display, xres->screen, 224 | XFT_FAMILY, XftTypeString, config->font_name, 225 | XFT_PIXEL_SIZE, XftTypeDouble, size, 226 | NULL 227 | ); 228 | 229 | if(!font){ 230 | fprintf(stderr, "Could not load font\n"); 231 | return false; 232 | } 233 | 234 | //fprintf(stderr, "Block \"%s\" extents: width %d, height %d, x %d, y %d, xOff %d, yOff %d\n", 235 | // block->text, block->extents.width, block->extents.height, block->extents.x, block->extents.y, 236 | // block->extents.xOff, block->extents.yOff); 237 | 238 | //bounds calculation 239 | for(i = 0; blocks[i] && blocks[i]->active; i++){ 240 | //update only not yet calculated blocks 241 | if(!(blocks[i]->calculated)){ 242 | XftTextExtentsUtf8(xres->display, font, (FcChar8*)blocks[i]->text, strlen(blocks[i]->text), &(blocks[i]->extents)); 243 | errlog(config, LOG_DEBUG, "Recalculated block %d (%s) extents: %dx%d\n", i, blocks[i]->text, blocks[i]->extents.width, blocks[i]->extents.height); 244 | blocks[i]->size = size; 245 | } 246 | 247 | //calculate bounding box over all 248 | bounding_height += blocks[i]->extents.height; 249 | if(blocks[i]->extents.width > bounding_width){ 250 | bounding_width = blocks[i]->extents.width; 251 | } 252 | } 253 | 254 | if(bounding_box){ 255 | bounding_box->width = bounding_width; 256 | bounding_box->height = bounding_height; 257 | } 258 | 259 | XftFontClose(xres->display, font); 260 | return true; 261 | } 262 | 263 | bool x11_maximize_blocks(XRESOURCES* xres, CFG* config, TEXTBLOCK** blocks, unsigned width, unsigned height){ 264 | unsigned i, num_blocks = 0; 265 | double current_size = 1; 266 | unsigned bound_low, bound_high, bound_delta; 267 | unsigned done_block, longest_block; 268 | XGlyphInfo bbox; 269 | bool break_loop = false; 270 | 271 | int bounds_delta = 4; //initial secondary bound delta 272 | 273 | //count blocks 274 | for(i = 0; blocks[i] && blocks[i]->active; i++){ 275 | if(!blocks[i]->calculated){ 276 | num_blocks++; 277 | } 278 | } 279 | 280 | //no blocks, bail out 281 | if(num_blocks < 1 || width < 1 || height < 1){ 282 | errlog(config, LOG_DEBUG, "Maximizer bailing out, nothing to do\n"); 283 | return true; 284 | } 285 | 286 | errlog(config, LOG_DEBUG, "Maximizer running for %dx%d bounds\n", width, height); 287 | 288 | //guess primary bound 289 | //sizes in sets to be maximized are always the same, 290 | //since any pass modifies all active blocks to the same size 291 | longest_block = string_block_longest(blocks); 292 | if(blocks[longest_block]->size == 0){ 293 | if(config->max_size > 0){ 294 | //use max size as primary bound 295 | current_size = config->max_size; 296 | } 297 | else{ 298 | //educated guess 299 | current_size = roundf(width / ((strlen(blocks[longest_block]->text) > 0) ? strlen(blocks[longest_block]->text):1)); 300 | } 301 | } 302 | else{ 303 | //use last known size as primary bound 304 | current_size = blocks[longest_block]->size; 305 | } 306 | errlog(config, LOG_DEBUG, "Guessing primary bound %d\n", (int)current_size); 307 | 308 | //find secondary bound for binary search 309 | if(!x11_blocks_resize(xres, config, blocks, &bbox, current_size)){ 310 | fprintf(stderr, "Failed to resize blocks to primary bound\n"); 311 | } 312 | 313 | if(bbox.height > height || bbox.width > width){ 314 | //primary bound is upper bound, search down 315 | bounds_delta *= -1; 316 | } 317 | errlog(config, LOG_DEBUG, "Primary bound is %s than bounding box\n", (bounds_delta < 0) ? "bigger":"smaller"); 318 | 319 | do{ 320 | bounds_delta *= 2; 321 | 322 | if(current_size + bounds_delta < 1){ 323 | errlog(config, LOG_DEBUG, "Search went out of permissible range\n"); 324 | bounds_delta = -current_size; //FIXME this might fail when the condition is met with an overflow 325 | break; 326 | } 327 | 328 | if(!x11_blocks_resize(xres, config, blocks, &bbox, current_size + bounds_delta)){ 329 | fprintf(stderr, "Failed to resize blocks to size %d\n", (int)current_size + bounds_delta); 330 | return false; 331 | } 332 | 333 | if(bbox.width < 1 || bbox.height < 1){ 334 | errlog(config, LOG_DEBUG, "Bounding box was empty\n"); 335 | return true; 336 | } 337 | 338 | errlog(config, LOG_DEBUG, "With bounds_delta %d bounding box is %dx%d\n", bounds_delta, bbox.width, bbox.height); 339 | } 340 | //loop until direction needs to be reversed 341 | while( ((bounds_delta < 0) && (bbox.width > width || bbox.height > height)) //searching lower bound, break if within bounds 342 | || ((bounds_delta > 0) && (bbox.width <= width && bbox.height <= height))); //searching upper bound, break if out of bounds 343 | errlog(config, LOG_DEBUG, "Calculated secondary bound %d via offset %d\n", (int)current_size + bounds_delta, bounds_delta); 344 | 345 | //prepare bounds for binary search 346 | if(bounds_delta < 0){ 347 | bound_low = current_size + bounds_delta; 348 | bound_high = current_size; //cant optimize here if starting bound matches exactly 349 | } 350 | else{ 351 | bound_high = current_size + bounds_delta; 352 | bound_low = current_size; //cant optimize here if starting bound matches exactly 353 | } 354 | 355 | if(config->max_size > 0 && bound_high > config->max_size){ 356 | errlog(config, LOG_DEBUG, "Enforcing size constraint\n"); 357 | bound_high = config->max_size; 358 | } 359 | if(config->max_size > 0 && bound_low > config->max_size){ 360 | bound_low = 1; 361 | } 362 | 363 | //binary search for final size 364 | do{ 365 | bound_delta = bound_high - bound_low; 366 | current_size = bound_low + ((double)bound_delta / (double)2); 367 | 368 | //stupid tiebreaker implementation 369 | if(bound_delta / 2 == 0){ 370 | if(break_loop){ 371 | current_size = bound_low; 372 | bound_delta = 0; 373 | } 374 | else{ 375 | break_loop = true; 376 | } 377 | } 378 | 379 | errlog(config, LOG_DEBUG, "Binary search testing size %d, hi %d, lo %d, delta %d\n", (int)current_size, bound_high, bound_low, bound_delta); 380 | 381 | if(!x11_blocks_resize(xres, config, blocks, &bbox, current_size)){ 382 | fprintf(stderr, "Failed to resize blocks to test size %d\n", (int)current_size); 383 | return false; 384 | } 385 | 386 | if(bbox.width < 1 || bbox.height < 1){ 387 | errlog(config, LOG_DEBUG, "Bounding box is 0, bailing out\n"); 388 | break; 389 | } 390 | 391 | if(bbox.width > width || bbox.height > height){ 392 | //out of bounds 393 | bound_high = current_size; 394 | errlog(config, LOG_DEBUG, "-> OOB\n"); 395 | } 396 | else{ 397 | //inside bounds 398 | bound_low = current_size; 399 | errlog(config, LOG_DEBUG, "-> OK\n"); 400 | } 401 | 402 | }while(bound_delta > 0); 403 | errlog(config, LOG_DEBUG, "Final size is %d\n", (int)current_size); 404 | 405 | //set active to false for longest 406 | //FIXME find longest by actual extents 407 | done_block = string_block_longest(blocks); 408 | blocks[done_block]->calculated = true; 409 | errlog(config, LOG_DEBUG, "Marked block %d as done\n", done_block); 410 | 411 | return true; 412 | } 413 | 414 | bool x11_align_blocks(XRESOURCES* xres, CFG* config, TEXTBLOCK** blocks, unsigned width, unsigned height){ 415 | //align blocks within bounding rectangle according to configured alignment 416 | unsigned i, total_height = 0, current_height = 0; 417 | 418 | for(i = 0; blocks[i] && blocks[i]->active; i++){ 419 | total_height += blocks[i]->extents.height; 420 | } 421 | 422 | if(i > 0){ 423 | total_height += config->line_spacing * (i - 1); 424 | } 425 | 426 | //FIXME this might underflow in some cases 427 | for(i = 0; blocks[i] && blocks[i]->active; i++){ 428 | //align x axis 429 | switch(config->alignment){ 430 | case ALIGN_NORTH: 431 | case ALIGN_SOUTH: 432 | case ALIGN_CENTER: 433 | //centered 434 | blocks[i]->layout_x = (width - (blocks[i]->extents.width)) / 2; 435 | break; 436 | case ALIGN_NORTHWEST: 437 | case ALIGN_WEST: 438 | case ALIGN_SOUTHWEST: 439 | //left 440 | blocks[i]->layout_x = config->padding; 441 | break; 442 | case ALIGN_NORTHEAST: 443 | case ALIGN_EAST: 444 | case ALIGN_SOUTHEAST: 445 | //right 446 | blocks[i]->layout_x = width - (blocks[i]->extents.width) - config->padding; 447 | break; 448 | } 449 | 450 | //align y axis 451 | switch(config->alignment){ 452 | case ALIGN_WEST: 453 | case ALIGN_EAST: 454 | case ALIGN_CENTER: 455 | //centered 456 | blocks[i]->layout_y = ((height - total_height) / 2) 457 | + current_height 458 | + ((current_height > 0) ? config->line_spacing:0); 459 | current_height += blocks[i]->extents.height 460 | + ((current_height > 0) ? config->line_spacing:0); 461 | break; 462 | case ALIGN_NORTHWEST: 463 | case ALIGN_NORTH: 464 | case ALIGN_NORTHEAST: 465 | //top 466 | blocks[i]->layout_y = (config->padding) 467 | + current_height 468 | + ((current_height > 0) ? config->line_spacing:0); 469 | current_height += blocks[i]->extents.height 470 | + ((current_height > 0) ? config->line_spacing:0); 471 | break; 472 | case ALIGN_SOUTHWEST: 473 | case ALIGN_SOUTH: 474 | case ALIGN_SOUTHEAST: 475 | //bottom 476 | blocks[i]->layout_y = height - total_height - (config->padding) 477 | + ((current_height > 0) ? config->line_spacing:0); 478 | total_height -= (blocks[i]->extents.height 479 | + ((current_height > 0) ? config->line_spacing:0)); 480 | current_height += blocks[i]->extents.height 481 | + ((current_height > 0) ? config->line_spacing:0); 482 | break; 483 | } 484 | } 485 | 486 | return true; 487 | } 488 | 489 | bool x11_recalculate_blocks(CFG* config, XRESOURCES* xres, TEXTBLOCK** blocks, unsigned width, unsigned height){ 490 | unsigned i, num_blocks = 0; 491 | unsigned layout_width = width, layout_height = height; 492 | 493 | //early exit. 494 | if(!blocks || !blocks[0]){ 495 | return true; 496 | } 497 | 498 | //initialize calculation set 499 | for(i = 0; blocks[i] && blocks[i]->active; i++){ 500 | errlog(config, LOG_INFO, "Block %d: %s\n", i, blocks[i]->text); 501 | if(blocks[i]->text[0]){ 502 | blocks[i]->calculated = false; 503 | } 504 | else{ 505 | //disable obviously empty blocks before running maximizer 506 | errlog(config, LOG_DEBUG, "Disabling empty block %d\n", i); 507 | blocks[i]->calculated = true; 508 | blocks[i]->extents.width = 0; 509 | blocks[i]->extents.height = 0; 510 | blocks[i]->extents.x = 0; 511 | blocks[i]->extents.y = 0; 512 | } 513 | num_blocks++; 514 | } 515 | 516 | //calculate layout volume 517 | if(width > (2 * config->padding)){ 518 | layout_width -= 2 * config->padding; 519 | } 520 | if(height > (2 * config->padding)){ 521 | errlog(config, LOG_DEBUG, "Subtracting %d pixels for height padding\n", config->padding); 522 | layout_height -= 2 * config->padding; 523 | } 524 | if(num_blocks > 1 && (((num_blocks - 1) * (config->line_spacing) < layout_height))){ 525 | errlog(config, LOG_DEBUG, "Subtracting %d pixels for linespacing\n", (num_blocks - 1) * config->line_spacing); 526 | layout_height -= (num_blocks - 1) * config->line_spacing; 527 | } 528 | 529 | errlog(config, LOG_INFO, "Window volume %dx%d, layout volume %dx%d\n", width, height, layout_width, layout_height); 530 | 531 | if(config->force_size == 0){ 532 | //do binary search for match size 533 | i = 0; 534 | do{ 535 | errlog(config, LOG_DEBUG, "Running maximizer for pass %d (%d blocks)\n", i, num_blocks); 536 | if(!x11_maximize_blocks(xres, config, blocks, layout_width, layout_height)){ 537 | return false; 538 | } 539 | i++; 540 | } 541 | //do multiple passes if flag is set 542 | while(config->independent_resize && i < num_blocks); 543 | } 544 | else{ 545 | //render with forced size 546 | if(!x11_blocks_resize(xres, config, blocks, NULL, config->force_size)){ 547 | fprintf(stderr, "Failed to resize blocks\n"); 548 | return false; 549 | } 550 | } 551 | 552 | //do alignment pass 553 | if(!x11_align_blocks(xres, config, blocks, width, height)){ 554 | return false; 555 | } 556 | 557 | return true; 558 | } 559 | --------------------------------------------------------------------------------