/* * render.c * This file is part of LCDd, the lcdproc server. * * This file is released under the GNU General Public License. Refer to the * COPYING file distributed with this package. * * Copyright (c) 1999, William Ferrell, Scott Scriven * 2001, Joris Robijn * * * Draws screens on the LCD. * * This needs to be greatly expanded and redone for greater flexibility. * For example, it should support multiple screen sizes, more flexible * widgets, and multiple simultaneous screens. * * This will probably take a while to do. :( * * THIS FILE IS MESSY! Anyone care to rewrite it nicely? Please?? :) * * NOTE: (from David Douthitt) Multiple screen sizes? Multiple simultaneous * screens? Horrors of horrors... next thing you know it'll be making coffee... * Better believe it'll take a while to do... * */ #include #include #include #include "shared/report.h" #include "shared/LL.h" #include "drivers.h" #include "screen.h" #include "screenlist.h" #include "widget.h" #include "render.h" int heartbeat = HEARTBEAT_OPEN; static int heartbeat_fallback = HEARTBEAT_ON; /* If no heartbeat setting has been set at all */ int backlight = BACKLIGHT_OPEN; static int backlight_fallback = BACKLIGHT_ON; /* If no backlight setting has been set at all */ int output_state = 0; char * server_msg_text; int server_msg_expire = 0; static int reset; #define BUFSIZE 1024 static int render_frame (LinkedList * list, char fscroll, int left, int top, int right, int bottom, int fwid, int fhgt, int fspeed, long int timer); int render_screen (Screen * s, long int timer) { static Screen * old_s = NULL; int tmp_state = 0; debug(RPT_DEBUG, "%s( screen=[%.40s], timer=%d ) ==== START RENDERING ====", __FUNCTION__, s->id, timer ); reset = 1; if (!s) return -1; if (s == old_s) reset = 0; old_s = s; /* Clear the LCD screen... */ drivers_clear (); /* FIXME drivers_backlight -- * * If the screen's backlight isn't set (default) then we * inherit the backlight state from the parent client. This allows * the client to override it's childrens settings. * The server can also override the clients and screens settings. */ if (backlight != BACKLIGHT_OPEN) { tmp_state = backlight; } else if (s->client && s->client->backlight != BACKLIGHT_OPEN) { tmp_state = s->client->backlight; } else if (s->backlight != BACKLIGHT_OPEN) { tmp_state = s->backlight; } else { tmp_state = backlight_fallback; } /* Set up backlight to the correct state... */ /* NOTE: dirty stripping of other options... */ /* Backlight flash: check timer and flip backlight as appropriate */ if (tmp_state & BACKLIGHT_FLASH) { drivers_backlight ( ( (tmp_state & BACKLIGHT_ON) ^ ((timer & 7) == 7) ) ? BACKLIGHT_ON : BACKLIGHT_OFF ); /* Backlight blink: check timer and flip backlight as appropriate */ } else if (tmp_state & BACKLIGHT_BLINK) { drivers_backlight ( ( (tmp_state & BACKLIGHT_ON) ^ ((timer & 14) == 14) ) ? BACKLIGHT_ON : BACKLIGHT_OFF ); } else { /* Simple: Only send lowest bit then...*/ drivers_backlight (tmp_state & BACKLIGHT_ON); } /* Output ports from LCD - outputs depend on the current screen */ drivers_output (output_state); /* Draw a frame... */ render_frame (s->widgetlist, 'v', 0, 0, display_props->width, display_props->height, s->width, s->height, (((s->duration / s->height) < 1) ? 1 : (s->duration / s->height)), timer); /* Set the cursor */ drivers_cursor (s->cursor_x, s->cursor_y, s->cursor); if (heartbeat != HEARTBEAT_OPEN) { tmp_state = heartbeat; } else if (s->client && s->client->heartbeat != HEARTBEAT_OPEN) { tmp_state = s->client->heartbeat; } else if (s->heartbeat != HEARTBEAT_OPEN) { tmp_state = s->heartbeat; } else { tmp_state = heartbeat_fallback; } drivers_heartbeat(tmp_state); /* If there is an server message that is not expired, display it */ if( server_msg_expire > 0 ) { drivers_string( display_props->width - strlen(server_msg_text) + 1, display_props->height, server_msg_text ); server_msg_expire --; if( server_msg_expire == 0 ) { free( server_msg_text ); } } /* flush display out, frame and all... */ drivers_flush (); debug(RPT_DEBUG, "==== END RENDERING ====" ); return 0; } /* The following function is positively ghastly (as was mentioned above!) */ /* Best thing to do is to remove support for frames... but anyway... */ /* */ static int render_frame (LinkedList * list, char fscroll, /* direction of scrolling */ int left, /* left edge of frame */ int top, /* top edge of frame */ int right, /* right edge of frame */ int bottom, /* bottom edge of frame */ int fwid, /* frame width? */ int fhgt, /* frame height? */ int fspeed, /* speed of scrolling... */ long int timer) /* ? */ { #define VerticalScrolling (fscroll == 'v') #define HorizontalScrolling (fscroll == 'h') int vis_width = right - left; /* width of visible frame area */ int vis_height = bottom - top; /* height of visible frame area */ int x, y; int /*fx = 0,*/ fy = 0; /* Scrolling offset for the frame... */ int length, speed; int str_length = BUFSIZE-1; int reset = 1; debug( RPT_DEBUG, "%s( list=%p, fscroll='%c', left=%d, top=%d, " "right=%d, bottom=%d, fwid=%d, fhgt=%d, fspeed=%d, timer=%d )", __FUNCTION__, list, fscroll, left,top, right, bottom, fwid, fhgt, fspeed, timer ); /* return on no data or illegal height */ if (!list || (fhgt <= 0)) return -1; if (VerticalScrolling) { // FIXME: timer may be negative (this should be changed generally) // only set offset !=0 when fspeed is != 0 and there is something to scroll if (fspeed && (fhgt > vis_height)) { int fy_max = fhgt - vis_height + 1; fy = (fspeed > 0) ? (timer / fspeed) % fy_max : (-fspeed * timer) % fy_max; fy = max(fy, 0); // safeguard against negative values } } else if (HorizontalScrolling) { /* TODO: Frames don't scroll horizontally yet! */ } /* reset widget list */ LL_Rewind (list); /* loop over all widgets */ do { char str[BUFSIZE]; /* scratch buffer */ Widget *w = (Widget *) LL_Get (list); if (!w) return -1; /* TODO: Make this cleaner and more flexible!*/ switch (w->type) { case WID_STRING: if ((w->x > 0) && (w->y > 0) && (w->text) && (w->y <= vis_height + fy) && (w->y > fy)) { w->x = min(w->x, vis_width); str_length = min(vis_width - w->x + 1, BUFSIZE - 1); strncpy (str, w->text, str_length); str[str_length] = 0; drivers_string (w->x + left, w->y + top - fy, str); } break; case WID_HBAR: if (reset) { //drivers_init_hbar (); reset = 0; } if ((w->x > 0) && (w->y > 0) && (w->y <= vis_height + fy) && (w->y > fy)) { if (w->length > 0) { if ((w->length / display_props->cellwidth) < vis_width - w->x + 1) { /*was: drivers_hbar (w->x + left, w->y + top - fy, w->length); */ /* improvised len and promille */ int full_len = display_props->width - w->x - left + 1; int promille = (long) 1000 * w->length / ( display_props->cellwidth * full_len ); drivers_hbar (w->x + left, w->y + top - fy, full_len, promille, BAR_PATTERN_FILLED); } else { /*was: drivers_hbar (w->x + left, w->y + top - fy, wid * display_props->cellwidth); */ /* Improvised len and promille while we have the old widget language */ int full_len = ( display_props->width - w->x - left + 1 ); drivers_hbar (w->x + left, w->y + top - fy, full_len, 1000, BAR_PATTERN_FILLED); } } else if (w->length < 0) { /* TODO: Rearrange stuff to get left-extending * hbars to draw correctly... * .. er, this'll require driver modifications, * so I'll leave it out for now. */ } } break; case WID_VBAR: /* FIXME: Vbars don't work in frames!*/ if (reset) { //drivers_init_vbar (); reset = 0; } if ((w->x > 0) && (w->y > 0)) { if (w->length > 0) { /* Improvised len and promille while we have the old widget language */ int full_len = display_props->height; int promille = (long) 1000 * w->length / display_props->cellheight / full_len; drivers_vbar (w->x, display_props->height, full_len, promille, BAR_PATTERN_FILLED); } else if (w->length < 0) { /* TODO: Rearrange stuff to get down-extending * vbars to draw correctly... * .. er, this'll require driver modifications, * so I'll leave it out for now. */ } } break; case WID_ICON: drivers_icon (w->x, w->y, w->length); break; case WID_TITLE: /* FIXME: Doesn't work quite right in frames...*/ if (!w->text) break; if (vis_width < 8) break; drivers_icon (w->x + left, w->y + top, ICON_BLOCK_FILLED); drivers_icon (w->x + left + 1, w->y + top, ICON_BLOCK_FILLED); length = strlen (w->text); length = min(length, BUFSIZE - 1); if (length <= vis_width - 6) { strncpy (str, w->text, length); str[length] = 0; x = length + 5; } else /* Scroll the title, if it doesn't fit...*/ { speed = 1; x = timer / speed; y = x / length; x %= length; x = max(x, 0); if (x > length - (vis_width - 6)) x = length - (vis_width - 6); if (y & 1) /* Scrolling backwards...*/ x = (length - (vis_width - 6)) - x; str_length = abs(vis_width - 6); str_length = min(str_length, BUFSIZE -1); strncpy (str, w->text + x, str_length); str[str_length] = 0; x = vis_width - 1; } drivers_string (w->x + 3 + left, w->y + top, str); for (; x<=vis_width; x++) { drivers_icon (w->x + x - 1 + left, w->y + top, ICON_BLOCK_FILLED); } break; case WID_SCROLLER: /* FIXME: doesn't work in frames...*/ { int offset; int screen_width; if (!w->text) break; if (w->right < w->left) break; /*debug(RPT_DEBUG, "%s: %s %d",__FUNCTION__,w->text,timer);*/ screen_width = abs(w->right - w->left + 1); screen_width = min(screen_width, BUFSIZE -1); switch (w->length) { /* actually, direction...*/ /* FIXED: Horz scrollers don't show the * last letter in the string... (1-off error?) */ case 'm': // Marquee length = strlen (w->text); if (length <= screen_width) { /* it fits within the box, just render it */ drivers_string (w->left, w->top, w->text); } else { int necessaryTimeUnits = 0; if (w->speed > 0) { necessaryTimeUnits = length * w->speed; offset = (timer % (length * w->speed)) / w->speed; } else if (w->speed < 0) { necessaryTimeUnits = length / (w->speed * -1); offset = (timer % (length / (w->speed * -1))) * w->speed * -1; } else { offset = 0; } if (offset <= length) { int room = screen_width - (length - offset); strncpy (str, &w->text[offset], screen_width); // if there's more room, restart at the beginning if (room > 0) { strncat (str, w->text, room); } str[screen_width] = '\0'; /*debug(RPT_DEBUG, "scroller %s : %d", str, length-offset);*/ } else { str[0] = '\0'; } drivers_string (w->left, w->top, str); } break; case 'h': length = strlen (w->text) + 1; if (length <= screen_width) { /* it fits within the box, just render it */ drivers_string (w->left, w->top, w->text); } else { int effLength = length - screen_width; int necessaryTimeUnits = 0; if (w->speed > 0) { necessaryTimeUnits = effLength * w->speed; if (((timer / (effLength * w->speed)) % 2) == 0) { /*wiggle one way*/ offset = (timer % (effLength * w->speed)) / w->speed; } else { /*wiggle the other*/ offset = (((timer % (effLength * w->speed)) - (effLength * w->speed) + 1) / w->speed) * -1; } } else if (w->speed < 0) { necessaryTimeUnits = effLength / (w->speed * -1); if (((timer / (effLength / (w->speed * -1))) % 2) == 0) { offset = (timer % (effLength / (w->speed * -1))) * w->speed * -1; } else { offset = (((timer % (effLength / (w->speed * -1))) * w->speed * -1) - effLength + 1) * -1; } } else { offset = 0; } if (offset <= length) { strncpy (str, &((w->text)[offset]), screen_width); str[screen_width] = '\0'; /*debug(RPT_DEBUG, "scroller %s : %d", str, length-offset); */ } else { str[0] = '\0'; } drivers_string (w->left, w->top, str); } break; /* FIXME: Vert scrollers don't always seem to scroll */ /* back up after hitting the bottom. They jump back to */ /* the top instead... (nevermind?) */ case 'v': { int i = 0; length = strlen (w->text); if (length <= screen_width) { /* no scrolling required... */ drivers_string (w->left, w->top, w->text); } else { int lines_required = (length / screen_width) + (length % screen_width ? 1 : 0); int available_lines = (w->bottom - w->top + 1); if (lines_required <= available_lines) { /* easy...*/ for (i = 0; i < lines_required; i++) { strncpy (str, &((w->text)[i * screen_width]), screen_width); str[screen_width] = '\0'; drivers_string (w->left, w->top + i, str); } } else { int necessaryTimeUnits = 0; int effLines = lines_required - available_lines + 1; int begin = 0; /*debug(RPT_DEBUG, "length: %d sw: %d lines req: %d avail lines: %d effLines: %d ",length,screen_width,lines_required,available_lines,effLines);*/ if (w->speed > 0) { necessaryTimeUnits = effLines * w->speed; if (((timer / (effLines * w->speed)) % 2) == 0) { /*debug(RPT_DEBUG, "up ");*/ begin = (timer % (effLines * w->speed)) / w->speed; } else { /*debug(RPT_DEBUG, "down ");*/ begin = (((timer % (effLines * w->speed)) - (effLines * w->speed) + 1) / w->speed) * -1; } } else if (w->speed < 0) { necessaryTimeUnits = effLines / (w->speed * -1); if (((timer / (effLines / (w->speed * -1))) % 2) == 0) { begin = (timer % (effLines / (w->speed * -1))) * w->speed * -1; } else { begin = (((timer % (effLines / (w->speed * -1))) * w->speed * -1) - effLines + 1) * -1; } } else { begin = 0; } /*debug(RPT_DEBUG, "rendering begin: %d timer: %d effLines: %d",begin,timer,effLines); */ for (i = begin; i < begin + available_lines; i++) { strncpy (str, &((w->text)[i * (screen_width)]), screen_width); str[screen_width] = '\0'; /*debug(RPT_DEBUG, "rendering: '%s' of %s", */ /*str,w->text); */ drivers_string (w->left, w->top + (i - begin), str); } } } break; } } break; } case WID_FRAME: { /* FIXME: doesn't handle nested frames quite right! * doesn't handle scrolling in nested frames at all... */ int new_left = left + w->left - 1; int new_top = top + w->top - 1; int new_right = min(left + w->right, right); int new_bottom = min(top + w->bottom, bottom); if ((new_left < right) && (new_top < bottom)) /* Render only if it's visible...*/ render_frame (w->frame_screen->widgetlist, w->length, new_left, new_top, new_right, new_bottom, w->width, w->height, w->speed, timer); } break; case WID_NUM: /* FIXME: doesn't work in frames...*/ /* NOTE: y=10 means COLON (:)*/ if ((w->x > 0) && (w->y >= 0) && (w->y <= 10)) { if (reset) { //drivers_init_num (); reset = 0; } drivers_num (w->x + left, w->y); } break; case WID_NONE: default: break; } } while (LL_Next (list) == 0); return 0; } int server_msg( const char * text, int expire ) { debug( RPT_DEBUG, "%s( text=\"%.40s\", expire=%d )", __FUNCTION__, text, expire ); if( strlen(text) > 15 || expire <= 0 ) { return -1; } /* Still a message active ? */ if( server_msg_expire > 0 ) { free( server_msg_text ); } /* Store new message */ server_msg_text = malloc( strlen(text) + 3 ); strcpy( server_msg_text, "| " ); strcat( server_msg_text, text ); server_msg_expire = expire; return 0; }