blob: 80237f5bc750745e5d820d9adb282870bef596e2 [file] [log] [blame]
/*
* Mesa 3-D graphics library
* Version: 6.5
* Copyright (C) 1995-2006 Brian Paul
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* Library for glut using mesa fbdev driver
*
* Written by Sean D'Epagnier (c) 2006
*
* To improve on this library, maybe support subwindows or overlays,
* I (sean at depagnier dot com) will do my best to help.
*/
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/kd.h>
#include <linux/fb.h>
#include <linux/vt.h>
#include <GL/gl.h>
#include <GL/glut.h>
#include "internal.h"
#define FBMODES "/etc/fb.modes"
struct fb_fix_screeninfo FixedInfo;
struct fb_var_screeninfo VarInfo;
static struct fb_var_screeninfo OrigVarInfo;
static int DesiredDepth = 0;
int FrameBufferFD = -1;
unsigned char *FrameBuffer;
unsigned char *BackBuffer = NULL;
int DisplayMode;
struct GlutTimer *GlutTimers = NULL;
struct timeval StartTime;
/* per window data */
GLFBDevContextPtr Context;
GLFBDevBufferPtr Buffer;
GLFBDevVisualPtr Visual;
int Redisplay;
int Visible;
int VisibleSwitch;
int Active;
static int Resized;
/* we have to poll to see if we are visible
on a framebuffer that is not active */
int VisiblePoll;
int Swapping, VTSwitch;
static int FramebufferIndex;
static int Initialized;
char exiterror[256];
/* test if the active console is attached to the same framebuffer */
void TestVisible(void) {
struct fb_con2fbmap confb;
struct vt_stat st;
int ret;
ioctl(ConsoleFD, VT_GETSTATE, &st);
confb.console = st.v_active;
ret = ioctl(FrameBufferFD, FBIOGET_CON2FBMAP, &confb);
if(ret == -1 || confb.framebuffer == FramebufferIndex) {
VisibleSwitch = 1;
Visible = 0;
VisiblePoll = 0;
}
}
static void Cleanup(void)
{
/* do not handle this signal when cleaning up */
signal(SIGWINCH, SIG_IGN);
if(GameMode)
glutLeaveGameMode();
if(ConsoleFD != -1)
RestoreVT();
/* close mouse */
CloseMouse();
if(Visual)
glutDestroyWindow(1);
/* restore original variable screen info */
if(FrameBufferFD != -1) {
OrigVarInfo.xoffset = 0;
OrigVarInfo.yoffset = 0;
if (ioctl(FrameBufferFD, FBIOPUT_VSCREENINFO, &OrigVarInfo))
fprintf(stderr, "ioctl(FBIOPUT_VSCREENINFO failed): %s\n",
strerror(errno));
if(FrameBuffer)
munmap(FrameBuffer, FixedInfo.smem_len);
close(FrameBufferFD);
}
/* free allocated back buffer */
if(DisplayMode & GLUT_DOUBLE)
free(BackBuffer);
/* free menu items */
FreeMenus();
if(exiterror[0])
fprintf(stderr, "[glfbdev glut] %s", exiterror);
}
static void CrashHandler(int sig)
{
sprintf(exiterror, "Caught signal %d, cleaning up\n", sig);
exit(0);
}
static void removeArgs(int *argcp, char **argv, int num)
{
int i;
for (i = 0; argv[i+num]; i++)
argv[i] = argv[i+num];
argv[i] = NULL;
*argcp -= num;
}
#define REQPARAM(PARAM) \
if (i >= *argcp - 1) { \
fprintf(stderr, PARAM" requires a parameter\n"); \
exit(0); \
}
void glutInit (int *argcp, char **argv)
{
int i, nomouse = 0, nokeyboard = 0, usestdin = 0;
int RequiredWidth = 0, RequiredHeight;
char *fbdev;
stack_t stack;
struct sigaction sa;
/* parse out args */
for (i = 1; i < *argcp;) {
if (!strcmp(argv[i], "-geometry")) {
REQPARAM("geometry");
if(sscanf(argv[i+1], "%dx%d", &RequiredWidth,
&RequiredHeight) != 2) {
fprintf(stderr,"Please specify geometry as widthxheight\n");
exit(0);
}
removeArgs(argcp, &argv[i], 2);
} else
if (!strcmp(argv[i], "-bpp")) {
REQPARAM("bpp");
if(sscanf(argv[i+1], "%d", &DesiredDepth) != 1) {
fprintf(stderr, "Please specify a parameter for bpp\n");
exit(0);
}
removeArgs(argcp, &argv[i], 2);
} else
if (!strcmp(argv[i], "-vt")) {
REQPARAM("vt");
if(sscanf(argv[i+1], "%d", &CurrentVT) != 1) {
fprintf(stderr, "Please specify a parameter for vt\n");
exit(0);
}
removeArgs(argcp, &argv[i], 2);
} else
if (!strcmp(argv[i], "-mousespeed")) {
REQPARAM("mousespeed");
if(sscanf(argv[i+1], "%lf", &MouseSpeed) != 1) {
fprintf(stderr, "Please specify a mouse speed, eg: 2.5\n");
exit(0);
}
removeArgs(argcp, &argv[i], 2);
} else
if (!strcmp(argv[i], "-nomouse")) {
nomouse = 1;
removeArgs(argcp, &argv[i], 1);
} else
if (!strcmp(argv[i], "-nokeyboard")) {
nokeyboard = 1;
removeArgs(argcp, &argv[i], 1);
} else
if (!strcmp(argv[i], "-stdin")) {
usestdin = 1;
removeArgs(argcp, &argv[i], 1);
} else
if (!strcmp(argv[i], "-gpmmouse")) {
#ifdef HAVE_GPM
GpmMouse = 1;
#else
fprintf(stderr, "gpm support not compiled\n");
exit(0);
#endif
removeArgs(argcp, &argv[i], 1);
} else
if (!strcmp(argv[i], "--")) {
removeArgs(argcp, &argv[i], 1);
break;
} else
i++;
}
gettimeofday(&StartTime, 0);
atexit(Cleanup);
/* set up SIGSEGV to use alternate stack */
stack.ss_flags = 0;
stack.ss_size = SIGSTKSZ;
if(!(stack.ss_sp = malloc(SIGSTKSZ)))
sprintf(exiterror, "Failed to allocate alternate stack for SIGSEGV!\n");
sigaltstack(&stack, NULL);
sa.sa_handler = CrashHandler;
sa.sa_flags = SA_ONSTACK;
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, NULL);
signal(SIGINT, CrashHandler);
signal(SIGTERM, CrashHandler);
signal(SIGABRT, CrashHandler);
if(nomouse == 0)
InitializeMouse();
if(nokeyboard == 0)
InitializeVT(usestdin);
fbdev = getenv("FRAMEBUFFER");
if(fbdev) {
#ifdef MULTIHEAD
if(!sscanf(fbdev, "/dev/fb%d", &FramebufferIndex))
if(!sscanf(fbdev, "/dev/fb/%d", &FramebufferIndex))
sprintf(exiterror, "Could not determine Framebuffer index!\n");
#endif
} else {
static char fb[128];
struct fb_con2fbmap confb;
int fd = open("/dev/fb0", O_RDWR);
FramebufferIndex = 0;
confb.console = CurrentVT;
if(ioctl(fd, FBIOGET_CON2FBMAP, &confb) != -1)
FramebufferIndex = confb.framebuffer;
sprintf(fb, "/dev/fb%d", FramebufferIndex);
fbdev = fb;
close(fd);
}
/* open the framebuffer device */
FrameBufferFD = open(fbdev, O_RDWR);
if (FrameBufferFD < 0) {
sprintf(exiterror, "Error opening %s: %s\n", fbdev, strerror(errno));
exit(0);
}
/* get the fixed screen info */
if (ioctl(FrameBufferFD, FBIOGET_FSCREENINFO, &FixedInfo)) {
sprintf(exiterror, "error: ioctl(FBIOGET_FSCREENINFO) failed: %s\n",
strerror(errno));
exit(0);
}
/* get the variable screen info */
if (ioctl(FrameBufferFD, FBIOGET_VSCREENINFO, &OrigVarInfo)) {
sprintf(exiterror, "error: ioctl(FBIOGET_VSCREENINFO) failed: %s\n",
strerror(errno));
exit(0);
}
/* operate on a copy */
VarInfo = OrigVarInfo;
/* set the depth, resolution, etc */
if(RequiredWidth)
if(!ParseFBModes(RequiredWidth, RequiredWidth, RequiredHeight,
RequiredHeight, 0, MAX_VSYNC)) {
sprintf(exiterror, "No mode (%dx%d) found in "FBMODES"\n",
RequiredWidth, RequiredHeight);
exit(0);
}
Initialized = 1;
}
void glutInitDisplayMode (unsigned int mode)
{
DisplayMode = mode;
}
static const char *GetStrVal(const char *p, int *set, int min, int max)
{
char *endptr;
int comp = *p, val;
if(p[1] == '=')
p++;
if(*p == '\0')
return p;
val = strtol(p+1, &endptr, 10);
if(endptr == p+1)
return p;
switch(comp) {
case '!':
if(val == min)
val = max;
else
val = min;
break;
case '<':
val = min;
break;
case '>':
val = max;
break;
}
if(val < min || val > max) {
sprintf(exiterror, "display string value out of range\n");
exit(0);
}
*set = val;
return endptr;
}
static void SetAttrib(int val, int attr)
{
if(val)
DisplayMode |= attr;
else
DisplayMode &= ~attr;
}
void glutInitDisplayString(const char *string)
{
const char *p = string;
int val;
while(*p) {
if(*p == ' ')
p++;
else
if(memcmp(p, "acca", 4) == 0) {
p = GetStrVal(p+4, &AccumSize, 1, 32);
SetAttrib(AccumSize, GLUT_ACCUM);
} else
if(memcmp(p, "acc", 3) == 0) {
p = GetStrVal(p+3, &AccumSize, 1, 32);
SetAttrib(AccumSize, GLUT_ACCUM);
} else
if(memcmp(p, "depth", 5) == 0) {
p = GetStrVal(p+5, &DepthSize, 12, 32);
SetAttrib(DepthSize, GLUT_DEPTH);
} else
if(memcmp(p, "double", 6) == 0) {
val = 1;
p = GetStrVal(p+6, &val, 0, 1);
SetAttrib(val, GLUT_DOUBLE);
} else
if(memcmp(p, "index", 5) == 0) {
val = 1;
p = GetStrVal(p+5, &val, 0, 1);
SetAttrib(val, GLUT_INDEX);
} else
if(memcmp(p, "stencil", 7) == 0) {
p = GetStrVal(p+7, &StencilSize, 0, 1);
SetAttrib(StencilSize, GLUT_STENCIL);
} else
if(memcmp(p, "samples", 7) == 0) {
NumSamples = 1;
p = GetStrVal(p+7, &NumSamples, 0, 16);
SetAttrib(NumSamples, GLUT_MULTISAMPLE);
} else
if(p = strchr(p, ' '))
p++;
else
break;
}
}
void glutInitWindowPosition (int x, int y)
{
}
void glutInitWindowSize (int width, int height)
{
}
static void ProcessTimers(void)
{
while(GlutTimers && GlutTimers->time <= glutGet(GLUT_ELAPSED_TIME)) {
struct GlutTimer *timer = GlutTimers;
GlutTimers = timer->next;
timer->func(timer->value);
free(timer);
}
}
void glutMainLoop(void)
{
int idleiters;
if(ReshapeFunc)
ReshapeFunc(VarInfo.xres, VarInfo.yres);
if(!DisplayFunc) {
sprintf(exiterror, "Fatal Error: No Display Function registered\n");
exit(0);
}
for(;;) {
ProcessTimers();
if(Active)
ReceiveInput();
else
if(VisiblePoll)
TestVisible();
if(IdleFunc)
IdleFunc();
if(VisibleSwitch) {
VisibleSwitch = 0;
if(VisibilityFunc)
VisibilityFunc(Visible ? GLUT_VISIBLE : GLUT_NOT_VISIBLE);
}
if(Resized) {
SetVideoMode();
CreateBuffer();
if(!glFBDevMakeCurrent( Context, Buffer, Buffer )) {
sprintf(exiterror, "Failure to Make Current\n");
exit(0);
}
InitializeMenus();
if(ReshapeFunc)
ReshapeFunc(VarInfo.xres, VarInfo.yres);
Redisplay = 1;
Resized = 0;
}
if(Visible && Redisplay) {
Redisplay = 0;
EraseCursor();
DisplayFunc();
if(!(DisplayMode & GLUT_DOUBLE)) {
if(ActiveMenu)
DrawMenus();
DrawCursor();
}
idleiters = 0;
} else {
/* we sleep if not receiving redisplays, and
the main loop is running faster than 2khz */
static int lasttime;
int time = glutGet(GLUT_ELAPSED_TIME);
if(time > lasttime) {
if(idleiters >= 2)
usleep(100);
idleiters = 0;
lasttime = time;
}
idleiters++;
}
}
}
int ParseFBModes(int minw, int maxw, int minh, int maxh, int minf, int maxf)
{
char buf[1024];
struct fb_var_screeninfo vi = VarInfo;
FILE *fbmodes = fopen(FBMODES, "r");
if(!fbmodes) {
sprintf(exiterror, "Warning: could not open "FBMODES"\n");
return 0;
}
while(fgets(buf, sizeof buf, fbmodes)) {
char *c;
int v, bpp, freq;
if(!(c = strstr(buf, "geometry")))
continue;
v = sscanf(c, "geometry %d %d %d %d %d", &vi.xres, &vi.yres,
&vi.xres_virtual, &vi.yres_virtual, &bpp);
if(v != 5)
continue;
if(maxw < minw) {
if(maxw < vi.xres && minw > vi.xres)
continue;
} else
if(maxw < vi.xres || minw > vi.xres)
continue;
if(maxh < minh) {
if(maxh < vi.yres && minh > vi.yres)
continue;
} else
if(maxh < vi.yres || minh > vi.yres)
continue;
fgets(buf, sizeof buf, fbmodes);
if(!(c = strstr(buf, "timings")))
continue;
v = sscanf(c, "timings %d %d %d %d %d %d %d", &vi.pixclock,
&vi.left_margin, &vi.right_margin, &vi.upper_margin,
&vi.lower_margin, &vi.hsync_len, &vi.vsync_len);
if(v != 7)
continue;
freq = 1E12/vi.pixclock
/(vi.left_margin + vi.xres + vi.right_margin + vi.hsync_len)
/(vi.upper_margin + vi.yres + vi.lower_margin + vi.vsync_len);
if(maxf < minf) {
if(maxf < freq && minf > freq)
continue;
} else
if(maxf < freq || minf > freq)
continue;
VarInfo = vi;
fclose(fbmodes);
return 1;
}
fclose(fbmodes);
return 0;
}
void SetVideoMode(void)
{
/* set new variable screen info */
if (ioctl(FrameBufferFD, FBIOPUT_VSCREENINFO, &VarInfo)) {
sprintf(exiterror, "FBIOPUT_VSCREENINFO failed: %s\n", strerror(errno));
strcat(exiterror, "Perhaps the device does not support the selected mode\n");
exit(0);
}
/* reload the screen info to update rgb bits */
if (ioctl(FrameBufferFD, FBIOGET_VSCREENINFO, &VarInfo)) {
sprintf(exiterror, "error: ioctl(FBIOGET_VSCREENINFO) failed: %s\n",
strerror(errno));
exit(0);
}
/* reload the fixed info to update color mode */
if (ioctl(FrameBufferFD, FBIOGET_FSCREENINFO, &FixedInfo)) {
sprintf(exiterror, "error: ioctl(FBIOGET_FSCREENINFO) failed: %s\n",
strerror(errno));
exit(0);
}
if (DesiredDepth && DesiredDepth != VarInfo.bits_per_pixel) {
sprintf(exiterror, "error: Could not set set %d bpp\n", DesiredDepth);
exit(0);
}
if(DisplayMode & GLUT_INDEX && FixedInfo.visual == FB_VISUAL_DIRECTCOLOR) {
sprintf(exiterror, "error: Could not set 8 bit color mode\n");
exit(0);
}
/* initialize colormap */
LoadColorMap();
}
void CreateBuffer(void)
{
int size = VarInfo.xres_virtual * VarInfo.yres_virtual
* VarInfo.bits_per_pixel / 8;
/* mmap the framebuffer into our address space */
if(FrameBuffer)
munmap(FrameBuffer, FixedInfo.smem_len);
FrameBuffer = mmap(0, FixedInfo.smem_len, PROT_READ | PROT_WRITE,
MAP_SHARED, FrameBufferFD, 0);
if (FrameBuffer == MAP_FAILED) {
sprintf(exiterror, "error: unable to mmap framebuffer: %s\n",
strerror(errno));
exit(0);
}
if(DisplayMode & GLUT_DOUBLE) {
free(BackBuffer);
if(!(BackBuffer = malloc(size))) {
sprintf(exiterror, "Failed to allocate double buffer\n");
exit(0);
}
} else
BackBuffer = FrameBuffer;
if(Buffer)
glFBDevDestroyBuffer(Buffer);
if(!(Buffer = glFBDevCreateBuffer( &FixedInfo, &VarInfo, Visual,
FrameBuffer, BackBuffer, size))) {
sprintf(exiterror, "Failure to create Buffer\n");
exit(0);
}
}
void CreateVisual(void)
{
int i, mask = DisplayMode;
int attribs[20];
for(i=0; i<sizeof(attribs)/sizeof(*attribs) && mask; i++) {
if(mask & GLUT_DOUBLE) {
attribs[i] = GLFBDEV_DOUBLE_BUFFER;
mask &= ~GLUT_DOUBLE;
continue;
}
if(mask & GLUT_INDEX) {
attribs[i] = GLFBDEV_COLOR_INDEX;
mask &= ~GLUT_INDEX;
continue;
}
if(mask & GLUT_DEPTH) {
attribs[i] = GLFBDEV_DEPTH_SIZE;
attribs[++i] = DepthSize;
mask &= ~GLUT_DEPTH;
continue;
}
if(mask & GLUT_STENCIL) {
attribs[i] = GLFBDEV_STENCIL_SIZE;
attribs[++i] = StencilSize;
mask &= ~GLUT_STENCIL;
continue;
}
if(mask & GLUT_ACCUM) {
attribs[i] = GLFBDEV_ACCUM_SIZE;
attribs[++i] = AccumSize;
mask &= ~GLUT_ACCUM;
continue;
}
if(mask & GLUT_ALPHA)
if(!(DisplayMode & GLUT_INDEX)) {
mask &= ~GLUT_ALPHA;
i--;
continue;
}
if(mask & GLUT_MULTISAMPLE) {
attribs[i] = GLFBDEV_MULTISAMPLE;
attribs[++i] = NumSamples;
mask &= ~GLUT_MULTISAMPLE;
continue;
}
sprintf(exiterror, "Invalid mode from glutInitDisplayMode\n");
exit(0);
}
attribs[i] = GLFBDEV_NONE;
if(!(Visual = glFBDevCreateVisual( &FixedInfo, &VarInfo, attribs ))) {
sprintf(exiterror, "Failure to create Visual\n");
exit(0);
}
}
static void SignalWinch(int arg)
{
/* we can't change bitdepth without destroying the visual */
int bits_per_pixel = VarInfo.bits_per_pixel;
struct fb_bitfield red = VarInfo.red, green = VarInfo.green,
blue = VarInfo.blue, transp = VarInfo.transp;
/* get the variable screen info */
if (ioctl(FrameBufferFD, FBIOGET_VSCREENINFO, &VarInfo)) {
sprintf(exiterror, "error: ioctl(FBIOGET_VSCREENINFO) failed: %s\n",
strerror(errno));
exit(0);
}
/* restore bitdepth and color masks only */
VarInfo.bits_per_pixel = bits_per_pixel;
VarInfo.red = red;
VarInfo.green = green;
VarInfo.blue = blue;
VarInfo.transp = transp;
Resized = 1;
}
int glutCreateWindow (const char *title)
{
if(Initialized == 0) {
int argc = 0;
char *argv[] = {NULL};
glutInit(&argc, argv);
}
if(Context)
return 0;
if(DisplayMode & GLUT_INDEX)
VarInfo.bits_per_pixel = 8;
else
if(VarInfo.bits_per_pixel == 8)
VarInfo.bits_per_pixel = 32;
if (DesiredDepth)
VarInfo.bits_per_pixel = DesiredDepth;
VarInfo.xoffset = 0;
VarInfo.yoffset = 0;
VarInfo.nonstd = 0;
VarInfo.vmode &= ~FB_VMODE_YWRAP; /* turn off scrolling */
SetVideoMode();
CreateVisual();
CreateBuffer();
if(!(Context = glFBDevCreateContext(Visual, NULL))) {
sprintf(exiterror, "Failure to create Context\n");
exit(0);
}
if(!glFBDevMakeCurrent( Context, Buffer, Buffer )) {
sprintf(exiterror, "Failure to Make Current\n");
exit(0);
}
InitializeCursor();
InitializeMenus();
glutSetWindowTitle(title);
signal(SIGWINCH, SignalWinch);
Visible = 1;
VisibleSwitch = 1;
Redisplay = 1;
return 1;
}
int glutCreateSubWindow(int win, int x, int y, int width, int height)
{
return 0;
}
void glutSetWindow(int win)
{
}
int glutGetWindow(void)
{
return 1;
}
void glutDestroyWindow(int win)
{
glFBDevMakeCurrent( NULL, NULL, NULL);
glFBDevDestroyContext(Context);
glFBDevDestroyBuffer(Buffer);
glFBDevDestroyVisual(Visual);
Visual = NULL;
}
void glutPostRedisplay(void)
{
Redisplay = 1;
}
void glutPostWindowRedisplay(int win)
{
Redisplay = 1;
}
void glutSwapBuffers(void)
{
glFlush();
if(!(DisplayMode & GLUT_DOUBLE))
return;
if(ActiveMenu)
DrawMenus();
DrawCursor();
if(Visible) {
Swapping = 1;
glFBDevSwapBuffers(Buffer);
Swapping = 0;
}
/* if there was a vt switch while swapping, switch now */
if(VTSwitch) {
if(ioctl(ConsoleFD, VT_ACTIVATE, VTSwitch) < 0)
sprintf(exiterror, "Error switching console\n");
VTSwitch = 0;
}
}
void glutPositionWindow(int x, int y)
{
}
void glutReshapeWindow(int width, int height)
{
if(GameMode)
return;
if(!ParseFBModes(width, width, height, height, 0, MAX_VSYNC))
return;
signal(SIGWINCH, SIG_IGN);
SetVideoMode();
signal(SIGWINCH, SignalWinch);
Resized = 1;
}
void glutFullScreen(void)
{
}
void glutPopWindow(void)
{
}
void glutPushWindow(void)
{
}
void glutShowWindow(void)
{
Visible = 1;
}
void glutHideWindow(void)
{
Visible = 0;
}
static void UnIconifyWindow(int sig)
{
if(ConsoleFD == 0)
InitializeVT(1);
else
if(ConsoleFD > 0)
InitializeVT(0);
if (ioctl(FrameBufferFD, FBIOPUT_VSCREENINFO, &VarInfo)) {
sprintf(exiterror, "ioctl(FBIOPUT_VSCREENINFO failed): %s\n",
strerror(errno));
exit(0);
}
RestoreColorMap();
Redisplay = 1;
VisibleSwitch = 1;
Visible = 1;
}
void glutIconifyWindow(void)
{
RestoreVT();
signal(SIGCONT, UnIconifyWindow);
if (ioctl(FrameBufferFD, FBIOPUT_VSCREENINFO, &OrigVarInfo))
fprintf(stderr, "ioctl(FBIOPUT_VSCREENINFO failed): %s\n",
strerror(errno));
raise(SIGSTOP);
}
void glutSetWindowTitle(const char *name)
{
/* escape code to set title in screen */
if(getenv("TERM") && memcmp(getenv("TERM"), "screen", 6) == 0)
printf("\033k%s\033\\", name);
}
void glutSetIconTitle(const char *name)
{
}