-/* starwars, Copyright (c) 1998-2008 Jamie Zawinski <jwz@jwz.org> and
+/* starwars, Copyright (c) 1998-2014 Jamie Zawinski <jwz@jwz.org> and
* Claudio Matsuoka <claudio@helllabs.org>
*
* Permission to use, copy, modify, distribute, and sell this software and its
*
* For the fanboys:
*
- * starwars -program 'cat starwars.txt' -columns 25 -no-wrap -texture
+ * starwars -program 'cat starwars.txt' -columns 25 -no-wrap
*/
#ifdef HAVE_CONFIG_H
# include <unistd.h>
#endif
-#ifdef HAVE_UNAME
-# include <sys/utsname.h>
-#endif /* HAVE_UNAME */
-
-#ifndef HAVE_COCOA
-# include <X11/Intrinsic.h>
-#endif
-
-
+#include "starwars.h"
#define DEFAULTS "*delay: 40000 \n" \
"*showFPS: False \n" \
"*fpsTop: True \n" \
- "*font: " DEF_FONT "\n"
+ "*usePty: False \n" \
+ "*texFontCacheSize: 300\n" \
+ "*font: " DEF_FONT "\n" \
+ "*textLiteral: " DEF_TEXT "\n" \
+ "*program: xscreensaver-text --cols 0" /* don't wrap */
# define refresh_sws 0
# define sws_handle_event 0
#define countof(x) (sizeof((x))/sizeof((*x)))
#include "xlockmore.h"
+#include "textclient.h"
+#include "utf8wc.h"
#ifdef USE_GL /* whole file */
# endif
-#define DEF_PROGRAM "xscreensaver-text --cols 0" /* don't wrap */
#define DEF_LINES "125"
#define DEF_STEPS "35"
#define DEF_SPIN "0.03"
#define DEF_TEXTURES "True"
#define DEF_DEBUG "False"
-/* Utopia 800 needs 64 512x512 textures (4096x4096 bitmap).
- Utopia 720 needs 16 512x512 textures (2048x2048 bitmap).
- Utopia 480 needs 16 512x512 textures (2048x2048 bitmap).
- Utopia 400 needs 4 512x512 textures (1024x1024 bitmap).
- Utopia 180 needs 1 512x512 texture.
- Times 240 needs 1 512x512 texture.
- */
-#define DEF_FONT "-*-utopia-bold-r-normal-*-*-720-*-*-*-*-iso8859-1"
+#define DEF_FONT "-*-utopia-bold-r-normal-*-*-360-*-*-*-*-*-*"
#define TAB_WIDTH 8
#define MAX_THICK_LINES 25
#define FONT_WEIGHT 14
-#define KEEP_ASPECT
+
+#ifndef USE_IPHONE
+# define KEEP_ASPECT /* Letterboxing looks dumb on iPhone. */
+#endif
#include "texfont.h"
#include "glutstroke.h"
GLuint text_list, star_list;
texture_font_data *texfont;
- int polygon_count;
-
- FILE *pipe;
- XtInputId pipe_id;
- XtIntervalId pipe_timer;
- Time subproc_relaunch_delay;
+ text_data *tc;
char *buf;
int buf_size;
static sws_configuration *scs = NULL;
-static char *program;
static int max_lines;
static int scroll_steps;
static float star_spin;
static int alignment;
static XrmOptionDescRec opts[] = {
- {"-program", ".program", XrmoptionSepArg, 0 },
{"-lines", ".lines", XrmoptionSepArg, 0 },
{"-steps", ".steps", XrmoptionSepArg, 0 },
{"-spin", ".spin", XrmoptionSepArg, 0 },
};
static argtype vars[] = {
- {&program, "program", "Program", DEF_PROGRAM, t_String},
{&max_lines, "lines", "Integer", DEF_LINES, t_Int},
{&scroll_steps, "steps", "Integer", DEF_STEPS, t_Int},
{&star_spin, "spin", "Float", DEF_SPIN, t_Float},
}
-/* The GLUT font only has ASCII characters in them, so do what we can to
- convert Latin1 characters to the nearest ASCII equivalent...
- */
-static void
-latin1_to_ascii (char *s)
-{
- unsigned char *us = (unsigned char *) s;
- const unsigned char ascii[95] = {
- '!', 'C', '#', '#', 'Y', '|', 'S', '_', 'C', '?', '<', '=', '-', 'R', '_',
- '?', '?', '2', '3', '\'','u', 'P', '.', ',', '1', 'o', '>', '?', '?', '?',
- '?', 'A', 'A', 'A', 'A', 'A', 'A', 'E', 'C', 'E', 'E', 'E', 'E', 'I', 'I',
- 'I', 'I', 'D', 'N', 'O', 'O', 'O', 'O', 'O', 'x', '0', 'U', 'U', 'U', 'U',
- 'Y', 'p', 'S', 'a', 'a', 'a', 'a', 'a', 'a', 'e', 'c', 'e', 'e', 'e', 'e',
- 'i', 'i', 'i', 'i', 'o', 'n', 'o', 'o', 'o', 'o', 'o', '/', 'o', 'u', 'u',
- 'u', 'u', 'y', 'p', 'y' };
- while (*us)
- {
- if (*us >= 161)
- *us = ascii[*us - 161];
- else if (*us > 127)
- *us = '?';
- us++;
- }
-}
-
-\f
-/* Subprocess.
- (This bit mostly cribbed from phosphor.c)
- */
-
-static void drain_input (sws_configuration *sc);
-
-static void
-subproc_cb (XtPointer closure, int *source, XtInputId *id)
-{
- sws_configuration *sc = (sws_configuration *) closure;
- drain_input (sc);
-}
-
-
-static void
-launch_text_generator (sws_configuration *sc)
-{
- XtAppContext app = XtDisplayToApplicationContext (sc->dpy);
- char *oprogram = get_string_resource (sc->dpy, "program", "Program");
- char *program = (char *) malloc (strlen (oprogram) + 10);
- strcpy (program, "( ");
- strcat (program, oprogram);
- strcat (program, " ) 2>&1");
-
- if ((sc->pipe = popen (program, "r")))
- {
- sc->pipe_id =
- XtAppAddInput (app, fileno (sc->pipe),
- (XtPointer) (XtInputReadMask | XtInputExceptMask),
- subproc_cb, (XtPointer) sc);
- }
- else
- {
- perror (program);
- }
-}
-
-
-static void
-relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
-{
- sws_configuration *sc = (sws_configuration *) closure;
- if (!sc->pipe_timer) abort();
- sc->pipe_timer = 0;
- launch_text_generator (sc);
-}
-
-
-/* When the subprocess has generated some output, this reads as much as it
- can into sc->buf at sc->buf_tail.
- */
-static void
-drain_input (sws_configuration *sc)
-{
- XtAppContext app = XtDisplayToApplicationContext (sc->dpy);
- if (sc->buf_tail < sc->buf_size - 2)
- {
- int target = sc->buf_size - sc->buf_tail - 2;
- int n = (sc->pipe
- ? read (fileno (sc->pipe),
- (void *) (sc->buf + sc->buf_tail),
- target)
- : 0);
- if (n > 0)
- {
- sc->buf_tail += n;
- sc->buf[sc->buf_tail] = 0;
- }
- else
- {
- if (sc->pipe)
- {
- XtRemoveInput (sc->pipe_id);
- sc->pipe_id = 0;
- pclose (sc->pipe);
- sc->pipe = 0;
- }
-
- /* If the process didn't print a terminating newline, add one. */
- if (sc->buf_tail > 1 &&
- sc->buf[sc->buf_tail-1] != '\n')
- {
- sc->buf[sc->buf_tail++] = '\n';
- sc->buf[sc->buf_tail] = 0;
- }
-
- /* Then add one more, just for giggles. */
- sc->buf[sc->buf_tail++] = '\n';
- sc->buf[sc->buf_tail] = 0;
-
- /* Set up a timer to re-launch the subproc in a bit. */
- sc->pipe_timer = XtAppAddTimeOut (app, sc->subproc_relaunch_delay,
- relaunch_generator_timer,
- (XtPointer) sc);
- }
- }
-}
-
-
static int
-string_width (sws_configuration *sc, const char *s)
+sw_string_width (sws_configuration *sc, const char *s)
{
if (textures_p)
return texture_string_width (sc->texfont, s, 0);
char s[2];
s[0] = c;
s[1] = 0;
- return string_width (sc, s);
+ return sw_string_width (sc, s);
}
-/* Populates the sc->lines list with as many lines as are currently in
- sc->buf (which was filled by drain_input().
+/* Populates the sc->lines list with as many lines as possible.
*/
static void
get_more_lines (sws_configuration *sc)
int col_pix = 0;
char *s = sc->buf;
+
+ int target = sc->buf_size - sc->buf_tail - 2;
+
+ /* Fill as much as we can into sc->buf.
+ */
+ while (target > 0)
+ {
+ int c = textclient_getc (sc->tc);
+ if (c <= 0)
+ break;
+ sc->buf[sc->buf_tail++] = (char) c;
+ sc->buf[sc->buf_tail] = 0;
+ target--;
+ }
+
while (sc->total_lines < max_lines)
{
int cw;
sc->lines[sc->total_lines][L] = 0;
if (!textures_p)
- latin1_to_ascii (sc->lines[sc->total_lines]);
+ {
+ /* The GLUT font only has ASCII characters. */
+ char *s1 = utf8_to_latin1 (sc->lines[sc->total_lines], True);
+ free (sc->lines[sc->total_lines]);
+ sc->lines[sc->total_lines] = s1;
+ }
{
char *t = sc->lines[sc->total_lines];
if (debug_p)
{
- GLfloat w;
GLfloat h = sc->line_height / sc->font_scale;
- char c[2];
- c[1]=0;
- s = os;
+ char **chars = utf8_split (os, 0);
+ int i;
+
if (textures_p) glDisable (GL_TEXTURE_2D);
glLineWidth (1);
glColor3f (0.4, 0.4, 0.4);
glPushMatrix ();
glTranslatef (x, y, 0);
- while (*s)
+ for (i = 0; chars[i]; i++)
{
- *c = *s++;
- w = string_width (sc, c);
+ GLfloat w = sw_string_width (sc, chars[i]);
+ free (chars[i]);
glBegin (GL_LINE_LOOP);
glVertex3f (0, 0, 0);
glVertex3f (w, 0, 0);
glEnd();
glTranslatef (w, 0, 0);
}
+ free (chars);
glPopMatrix ();
if (textures_p) glEnable (GL_TEXTURE_2D);
}
int w = mi->xgwa.width;
int h = mi->xgwa.height;
int yoff = 0;
+ GLfloat rot = current_device_rotation();
#ifdef KEEP_ASPECT
{
gluLookAt (0.0, 0.0, 4600.0,
0.0, 0.0, 0.0,
0.0, 1.0, 0.0);
+
+ glRotatef(rot, 0, 0, 1);
+
+ /* Horrible kludge to prevent the text from materializing already
+ on screen on iPhone in landscape mode.
+ */
+ if ((rot > 45 && rot < 135) ||
+ (rot < -45 && rot > -135))
+ {
+ GLfloat s = 1.1;
+ glScalef (s, s, s);
+ }
+
glRotatef (-60.0, 1.0, 0.0, 0.0);
#if 0
{
GLdouble mm[17], pm[17];
GLint vp[5];
- GLfloat x = 0.5, y1 = 0, z = 0;
- GLfloat y2 = sc->line_height;
+ GLdouble x = 0.5, y1 = 0, z = 0;
+ GLdouble y2 = sc->line_height;
GLdouble wx=-1, wy1=-1, wy2=-1, wz=-1;
glGetDoublev (GL_MODELVIEW_MATRIX, mm);
{
sws_configuration *sc = &scs[MI_SCREEN(mi)];
- program = get_string_resource (mi->dpy, "program", "Program");
-
glDisable (GL_LIGHTING);
glDisable (GL_DEPTH_TEST);
if ((sc->glx_context = init_GL(mi)) != NULL) {
gl_init(mi);
reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+ clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
+
init_stars (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
}
sc->line_height = font_height * sc->font_scale;
- /* Buffer only two lines of text.
+ /* Buffer only a few lines of text.
If the buffer is too big, there's a significant delay between
when the program launches and when the text appears, which can be
irritating for time-sensitive output (clock, current music, etc.)
+
+ I'd like to buffer only 2 lines, but we need to assume that we
+ could get a whole line of N-byte Unicrud, and if we fill the buffer
+ before hitting the end of the line, we stall.
*/
- sc->buf_size = target_columns * 2;
+ sc->buf_size = target_columns * 2 * 4;
if (sc->buf_size < 80) sc->buf_size = 80;
sc->buf = (char *) calloc (1, sc->buf_size);
- sc->subproc_relaunch_delay = 2 * 1000; /* 2 seconds */
sc->total_lines = max_lines-1;
if (random() & 1)
exit (1);
}
- launch_text_generator (sc);
+ sc->tc = textclient_open (sc->dpy);
/* one more reshape, after line_height has been computed */
reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
-100.0, 100.0);
glRotatef (sc->star_theta, 0.0, 0.0, 1.0);
if (textures_p) glDisable (GL_TEXTURE_2D);
+
+ /* Keep the stars pointing in the same direction after rotation */
+ glRotatef(current_device_rotation(), 0, 0, 1);
+
glCallList (sc->star_list);
if (textures_p) glEnable (GL_TEXTURE_2D);
}
if (!sc->glx_context)
return;
-#if 0
- if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
- XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
-#endif
-
glDrawBuffer (GL_BACK);
glXMakeCurrent (dpy, window, *(sc->glx_context));
glMatrixMode (GL_MODELVIEW);
glPushMatrix ();
+# ifdef USE_IPHONE
+ /* Need to do this every time to get device rotation right */
+ reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+# endif
+
if (debug_p)
{
int i;
glPushMatrix ();
if (textures_p) glDisable (GL_TEXTURE_2D);
glLineWidth (1);
- glColor3f (0.4, 0.4, 0.4);
glTranslatef (0,-1, 0);
+
+ glColor3f(1, 0, 0); /* Red line is where text appears */
+ glPushMatrix();
+ glTranslatef(0, -0.028, 0);
+ glLineWidth (4);
+ glBegin(GL_LINES);
+ glVertex3f(-0.5, 1, 0);
+ glVertex3f( 0.5, 1, 0);
+ glVertex3f(-0.5, -1, 0);
+ glVertex3f( 0.5, -1, 0);
+ glEnd();
+ glLineWidth (1);
+ glPopMatrix();
+
+ glColor3f (0.4, 0.4, 0.4);
for (i = 0; i < 16; i++)
{
box (1, 1, 1);
}
if (textures_p) glEnable (GL_TEXTURE_2D);
glPopMatrix ();
+ check_gl_error ("debug render");
}
/* Scroll to current position */
glTranslatef (0.0, sc->intra_line_scroll, 0.0);
glColor3f (1.0, 1.0, 0.4);
- glCallList (sc->text_list);
- mi->polygon_count = sc->polygon_count;
+
+ mi->polygon_count = 0;
+
+ glPushMatrix ();
+ glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
+ for (i = 0; i < sc->total_lines; i++)
+ {
+ double fade = (fade_p ? 1.0 * i / sc->total_lines : 1.0);
+ int offscreen_lines = 2;
+
+ double x = -0.5;
+ double y = ((sc->total_lines - (i + offscreen_lines) - 1)
+ * sc->line_height);
+ double xoff = 0;
+ char *line = sc->lines[i];
+
+ if (debug_p)
+ {
+ double xx = x * 1.4; /* a little more to the left */
+ char n[20];
+ sprintf(n, "%d:", i);
+ draw_string (sc, xx / sc->font_scale, y / sc->font_scale, n);
+ }
+
+ if (!line || !*line)
+ continue;
+
+ if (sc->line_thickness != 1 && !textures_p)
+ {
+ int max_thick_lines = MAX_THICK_LINES;
+ GLfloat thinnest_line = 1.0;
+ GLfloat thickest_line = sc->line_thickness;
+ GLfloat range = thickest_line - thinnest_line;
+ GLfloat thickness;
+
+ int j = sc->total_lines - i - 1;
+
+ if (j > max_thick_lines)
+ thickness = thinnest_line;
+ else
+ thickness = (thinnest_line +
+ (range * ((max_thick_lines - j) /
+ (GLfloat) max_thick_lines)));
+
+ glLineWidth (thickness);
+ }
+
+ if (alignment >= 0)
+ {
+ int n = sw_string_width (sc, line);
+ xoff = 1.0 - (n * sc->font_scale);
+ }
+
+ if (alignment == 0)
+ xoff /= 2;
+
+ glColor3f (fade, fade, 0.5 * fade);
+ draw_string (sc, (x + xoff) / sc->font_scale, y / sc->font_scale,
+ line);
+ if (textures_p)
+ mi->polygon_count += strlen (line);
+ }
+ glPopMatrix ();
+
+
sc->intra_line_scroll += sc->line_height / scroll_steps;
here so that new text still pulls in from the bottom of
the screen, isntead of just appearing. */
sc->total_lines = max_lines;
-
- glDeleteLists (sc->text_list, 1);
- sc->text_list = glGenLists (1);
- glNewList (sc->text_list, GL_COMPILE);
- sc->polygon_count = 0;
- glPushMatrix ();
- glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
- for (i = 0; i < sc->total_lines; i++)
- {
- double fade = (fade_p ? 1.0 * i / sc->total_lines : 1.0);
- int offscreen_lines = 2;
-
- double x = -0.5;
- double y = ((sc->total_lines - (i + offscreen_lines) - 1)
- * sc->line_height);
- double xoff = 0;
- char *line = sc->lines[i];
-
- if (debug_p)
- {
- double xx = x * 1.4; /* a little more to the left */
- char n[20];
- sprintf(n, "%d:", i);
- draw_string (sc, xx / sc->font_scale, y / sc->font_scale, n);
- }
-
- if (!line || !*line)
- continue;
-
- if (sc->line_thickness != 1 && !textures_p)
- {
- int max_thick_lines = MAX_THICK_LINES;
- GLfloat thinnest_line = 1.0;
- GLfloat thickest_line = sc->line_thickness;
- GLfloat range = thickest_line - thinnest_line;
- GLfloat thickness;
-
- int j = sc->total_lines - i - 1;
-
- if (j > max_thick_lines)
- thickness = thinnest_line;
- else
- thickness = (thinnest_line +
- (range * ((max_thick_lines - j) /
- (GLfloat) max_thick_lines)));
-
- glLineWidth (thickness);
- }
-
- if (alignment >= 0)
- {
- int n = string_width (sc, line);
- xoff = 1.0 - (n * sc->font_scale);
- }
-
- if (alignment == 0)
- xoff /= 2;
-
- glColor3f (fade, fade, 0.5 * fade);
- draw_string (sc, (x + xoff) / sc->font_scale, y / sc->font_scale,
- line);
- if (textures_p)
- sc->polygon_count += strlen (line);
- }
- glPopMatrix ();
- glEndList ();
}
glPopMatrix ();
int screen;
for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
sws_configuration *sc = &scs[screen];
- if (sc->pipe_id)
- XtRemoveInput (sc->pipe_id);
- if (sc->pipe)
- pclose (sc->pipe);
- if (sc->pipe_timer)
- XtRemoveTimeOut (sc->pipe_timer);
+ if (sc->tc)
+ textclient_close (sc->tc);
/* #### there's more to free here */
}
}
+#ifdef __GNUC__
+ __extension__ /* don't warn about "string length is greater than the length
+ ISO C89 compilers are required to support" when including
+ "starwars.txt" in the defaults... */
+#endif
+
XSCREENSAVER_MODULE_2 ("StarWars", starwars, sws)
#endif /* USE_GL */