mirror of
https://github.com/0intro/wmii
synced 2024-11-29 17:13:11 +03:00
622 lines
15 KiB
C
622 lines
15 KiB
C
/*
|
|
* (C)opyright MMIV-MMV Anselm R. Garbe <garbeam at gmail dot com>
|
|
* See LICENSE file for license details.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <time.h>
|
|
#include <X11/Xatom.h>
|
|
#include <X11/cursorfont.h>
|
|
#include <X11/Xproto.h>
|
|
#include <X11/Xutil.h>
|
|
#include <X11/keysym.h>
|
|
|
|
#include "wmii.h"
|
|
|
|
/* array indexes for file pointers */
|
|
typedef enum {
|
|
M_CTL,
|
|
M_GEOMETRY,
|
|
M_PRE_COMMAND,
|
|
M_COMMAND,
|
|
M_HISTORY,
|
|
M_LOOKUP,
|
|
M_FONT,
|
|
M_SEL_BG_COLOR,
|
|
M_SEL_TEXT_COLOR,
|
|
M_SEL_BORDER_COLOR,
|
|
M_NORM_BG_COLOR,
|
|
M_NORM_TEXT_COLOR,
|
|
M_NORM_BORDER_COLOR,
|
|
M_LAST
|
|
} InputIndexes;
|
|
|
|
enum {
|
|
OFF_NEXT, OFF_PREV, OFF_CURR, OFF_LAST
|
|
};
|
|
|
|
static IXPServer *ixps = 0;
|
|
static Display *dpy;
|
|
static GC gc;
|
|
static Window win;
|
|
static XRectangle rect;
|
|
static XRectangle mrect;
|
|
static int screen_num;
|
|
static char *sockfile = 0;
|
|
static File *files[M_LAST];
|
|
static Container items = {0};
|
|
static Container history = {0};
|
|
static int offset[OFF_LAST];
|
|
static unsigned int cmdw = 0;
|
|
static Pixmap pmap;
|
|
static const int seek = 30; /* 30px */
|
|
static XFontStruct *font;
|
|
static unsigned int sel = 0;
|
|
static Align align = SOUTH;
|
|
|
|
static void check_event(Connection * c);
|
|
static void draw_menu(void);
|
|
static void handle_kpress(XKeyEvent * e);
|
|
static void set_text(char *text);
|
|
static void quit(void *obj, char *arg);
|
|
static void display(void *obj, char *arg);
|
|
static int update_items(char *prefix);
|
|
|
|
static Action acttbl[2] = {
|
|
{"quit", quit},
|
|
{"display", display}
|
|
};
|
|
|
|
static char *version[] = {
|
|
"wmimenu - window manager improved menu - " VERSION "\n"
|
|
" (C)opyright MMIV-MMV Anselm R. Garbe\n", 0
|
|
};
|
|
|
|
static void usage()
|
|
{
|
|
fprintf(stderr, "%s",
|
|
"usage: wmimenu [-s <socket file>] [-r] [-v]\n"
|
|
" -s socket file (default: /tmp/.ixp-$USER/wmimenu-%s-%s)\n"
|
|
" -v version info\n");
|
|
exit(1);
|
|
}
|
|
|
|
static void add_history(char *cmd)
|
|
{
|
|
char buf[MAX_BUF];
|
|
snprintf(buf, MAX_BUF, "/history/%ld", (long) time(0));
|
|
cext_attach_item(&history, wmii_create_ixpfile(ixps, buf, cmd));
|
|
}
|
|
|
|
static void exec_item(char *cmd)
|
|
{
|
|
File *item = cext_list_get_item(&items, sel);
|
|
char *rc = cmd;
|
|
|
|
if (!cmd || cmd[0] == 0)
|
|
return;
|
|
|
|
if (item && item->size)
|
|
rc = cmd = item->content;
|
|
add_history(cmd);
|
|
|
|
if (files[M_PRE_COMMAND]->content) {
|
|
size_t len = strlen(cmd) + files[M_PRE_COMMAND]->size + 2;
|
|
rc = cext_emallocz(len);
|
|
snprintf(rc, len, "%s %s", (char *) files[M_PRE_COMMAND]->content, cmd);
|
|
}
|
|
/* fallback */
|
|
spawn(dpy, rc);
|
|
/* cleanup */
|
|
if (files[M_PRE_COMMAND]->content)
|
|
free(rc);
|
|
}
|
|
|
|
static void quit(void *obj, char *arg)
|
|
{
|
|
ixps->runlevel = SHUTDOWN;
|
|
}
|
|
|
|
static void show()
|
|
{
|
|
set_text(0);
|
|
XMapRaised(dpy, win);
|
|
XSync(dpy, False);
|
|
update_items(files[M_COMMAND]->content);
|
|
draw_menu();
|
|
while (XGrabKeyboard
|
|
(dpy, RootWindow(dpy, screen_num), True, GrabModeAsync,
|
|
GrabModeAsync, CurrentTime) != GrabSuccess)
|
|
usleep(1000);
|
|
}
|
|
|
|
static void hide()
|
|
{
|
|
XUngrabKeyboard(dpy, CurrentTime);
|
|
XUnmapWindow(dpy, win);
|
|
XSync(dpy, False);
|
|
}
|
|
|
|
static void display(void *obj, char *arg)
|
|
{
|
|
if (!arg)
|
|
return;
|
|
if (_strtonum(arg, 0, 1))
|
|
show();
|
|
else
|
|
hide();
|
|
check_event(0);
|
|
}
|
|
|
|
void set_text(char *text)
|
|
{
|
|
if (files[M_COMMAND]->content) {
|
|
free(files[M_COMMAND]->content);
|
|
files[M_COMMAND]->content = 0;
|
|
files[M_COMMAND]->size = 0;
|
|
}
|
|
if (text && strlen(text)) {
|
|
files[M_COMMAND]->content = strdup(text);
|
|
files[M_COMMAND]->size = strlen(text);
|
|
}
|
|
}
|
|
|
|
static void update_offsets()
|
|
{
|
|
File *item;
|
|
unsigned int i, w = cmdw + 2 * seek;
|
|
|
|
if (!cext_stack_get_top_item(&items))
|
|
return;
|
|
|
|
/* calc next offset */
|
|
for (i = offset[OFF_CURR]; (item = cext_list_get_item(&items, i)); i++) {
|
|
w += XTextWidth(font, item->content, strlen(item->content)) + mrect.height;
|
|
if (w > mrect.width)
|
|
break;
|
|
}
|
|
offset[OFF_NEXT] = i;
|
|
|
|
w = cmdw + 2 * seek;
|
|
for (i = offset[OFF_CURR] - 1; (i >= 0) && (item = cext_list_get_item(&items, i)); i--) {
|
|
w += XTextWidth(font, item->content, strlen(item->content)) + mrect.height;
|
|
if (w > mrect.width)
|
|
break;
|
|
}
|
|
offset[OFF_PREV] = i + 1;
|
|
}
|
|
|
|
static int update_items(char *pattern)
|
|
{
|
|
size_t plen = pattern ? strlen(pattern) : 0, len, max = 0, size;
|
|
int matched = pattern ? plen == 0 : 1;
|
|
File *f, *p, *maxitem;
|
|
|
|
if (!files[M_LOOKUP]->content)
|
|
return 0;
|
|
f = ixp_walk(ixps, files[M_LOOKUP]->content);
|
|
if (!f || !is_directory(f))
|
|
return 0;
|
|
|
|
cmdw = 0;
|
|
offset[OFF_CURR] = offset[OFF_PREV] = offset[OFF_NEXT] = 0;
|
|
sel = 0;
|
|
|
|
while ((p = cext_stack_get_top_item(&items)))
|
|
cext_detach_item(&items, p);
|
|
|
|
/* build new items */
|
|
for (p = f->content; p; p = p->next) {
|
|
len = strlen(p->name);
|
|
if (max < len) {
|
|
maxitem = p;
|
|
max = len;
|
|
}
|
|
}
|
|
|
|
if (maxitem)
|
|
cmdw = XTextWidth(font, maxitem->name, max) + mrect.height;
|
|
|
|
for (p = f->content; p; p = p->next) {
|
|
if (matched || !strncmp(pattern, p->name, plen)) {
|
|
cext_attach_item(&items, p);
|
|
p->parent = 0; /* HACK to prevent doubled items */
|
|
}
|
|
}
|
|
|
|
for (p = f->content; p; p = p->next) {
|
|
if (p->parent && strstr(p->name, pattern))
|
|
cext_attach_item(&items, p);
|
|
else
|
|
p->parent = f; /* restore HACK */
|
|
}
|
|
|
|
size = cext_sizeof(&items);
|
|
update_offsets();
|
|
return size;
|
|
}
|
|
|
|
/* creates draw structs for menu mode drawing */
|
|
static void draw_menu()
|
|
{
|
|
Draw d = { 0 };
|
|
unsigned int offx = 0;
|
|
int i = 0;
|
|
File *item = 0, *selitem = cext_list_get_item(&items, sel);
|
|
|
|
d.gc = gc;
|
|
d.font = font;
|
|
d.drawable = pmap;
|
|
d.rect = mrect;
|
|
d.rect.x = 0;
|
|
d.rect.y = 0;
|
|
d.bg = blitz_loadcolor(dpy, screen_num, files[M_NORM_BG_COLOR]->content);
|
|
d.border = blitz_loadcolor(dpy, screen_num, files[M_NORM_BORDER_COLOR]->content);
|
|
blitz_drawlabelnoborder(dpy, &d);
|
|
|
|
/* print command */
|
|
d.align = WEST;
|
|
d.font = font;
|
|
d.fg = blitz_loadcolor(dpy, screen_num, files[M_NORM_TEXT_COLOR]->content);
|
|
d.data = files[M_COMMAND]->content;
|
|
if (cmdw && selitem)
|
|
d.rect.width = cmdw;
|
|
offx += d.rect.width;
|
|
blitz_drawlabelnoborder(dpy, &d);
|
|
|
|
d.align = CENTER;
|
|
if (selitem) {
|
|
d.bg = blitz_loadcolor(dpy, screen_num, files[M_NORM_BG_COLOR]->content);
|
|
d.fg = blitz_loadcolor(dpy, screen_num, files[M_NORM_TEXT_COLOR]->content);
|
|
d.data = offset[OFF_CURR] ? "<" : 0;
|
|
d.rect.x = offx;
|
|
d.rect.width = seek;
|
|
offx += d.rect.width;
|
|
blitz_drawlabelnoborder(dpy, &d);
|
|
|
|
/* determine maximum items */
|
|
for (i = offset[OFF_CURR]; (i < offset[OFF_NEXT]) && (item = cext_list_get_item(&items, i)); i++) {
|
|
d.data = item->name;
|
|
d.rect.x = offx;
|
|
d.rect.width = XTextWidth(d.font, d.data, strlen(d.data)) + mrect.height;
|
|
offx += d.rect.width;
|
|
/*fprintf(stderr, "%s (%d, %d, %d, %d)\n", item->name, d.rect.x, d.rect.y, d.rect.width, d.rect.height);*/
|
|
if (selitem == item) {
|
|
d.bg = blitz_loadcolor(dpy, screen_num, files[M_SEL_BG_COLOR]->content);
|
|
d.fg = blitz_loadcolor(dpy, screen_num, files[M_SEL_TEXT_COLOR]->content);
|
|
d.border = blitz_loadcolor(dpy, screen_num, files[M_SEL_BORDER_COLOR]-> content);
|
|
blitz_drawlabel(dpy, &d);
|
|
} else {
|
|
d.bg = blitz_loadcolor(dpy, screen_num, files[M_NORM_BG_COLOR]->content);
|
|
d.fg = blitz_loadcolor(dpy, screen_num, files[M_NORM_TEXT_COLOR]->content);
|
|
d.border = blitz_loadcolor(dpy, screen_num, files[M_NORM_BORDER_COLOR]->content);
|
|
blitz_drawlabelnoborder(dpy, &d);
|
|
}
|
|
}
|
|
|
|
d.bg = blitz_loadcolor(dpy, screen_num, files[M_NORM_BG_COLOR]->content);
|
|
d.fg = blitz_loadcolor(dpy, screen_num, files[M_NORM_TEXT_COLOR]->content);
|
|
d.data = item ? ">" : 0;
|
|
d.rect.x = mrect.width - seek;
|
|
d.rect.width = seek;
|
|
blitz_drawlabelnoborder(dpy, &d);
|
|
}
|
|
XCopyArea(dpy, pmap, win, gc, 0, 0, mrect.width, mrect.height, 0, 0);
|
|
XSync(dpy, False);
|
|
}
|
|
|
|
static void handle_kpress(XKeyEvent * e)
|
|
{
|
|
KeySym ksym;
|
|
char buf[32];
|
|
int num;
|
|
static char text[4096];
|
|
size_t len = 0, size = cext_sizeof(&items);
|
|
File *selitem = cext_list_get_item(&items, sel);
|
|
File *hist = cext_stack_get_top_item(&history);
|
|
|
|
text[0] = 0;
|
|
if (files[M_COMMAND]->content) {
|
|
cext_strlcpy(text, files[M_COMMAND]->content, sizeof(text));
|
|
len = strlen(text);
|
|
}
|
|
buf[0] = 0;
|
|
num = XLookupString(e, buf, sizeof(buf), &ksym, 0);
|
|
|
|
if (IsFunctionKey(ksym) || IsKeypadKey(ksym)
|
|
|| IsMiscFunctionKey(ksym) || IsPFKey(ksym)
|
|
|| IsPrivateKeypadKey(ksym))
|
|
return;
|
|
|
|
/* first check if a control mask is omitted */
|
|
if (e->state & ShiftMask) {
|
|
if (ksym == XK_ISO_Left_Tab)
|
|
ksym = XK_Left;
|
|
} else if (e->state & ControlMask) {
|
|
switch (ksym) {
|
|
case XK_E:
|
|
case XK_e:
|
|
ksym = XK_End;
|
|
break;
|
|
case XK_H:
|
|
case XK_h:
|
|
ksym = XK_BackSpace;
|
|
break;
|
|
case XK_J:
|
|
case XK_j:
|
|
ksym = XK_Return;
|
|
break;
|
|
case XK_U:
|
|
case XK_u:
|
|
set_text(0);
|
|
update_items(0);
|
|
draw_menu();
|
|
return;
|
|
break;
|
|
default: /* ignore other control sequences */
|
|
return;
|
|
break;
|
|
}
|
|
}
|
|
switch (ksym) {
|
|
case XK_Left:
|
|
if (!selitem)
|
|
return;
|
|
if (sel > 0) {
|
|
selitem = cext_list_get_item(&items, --sel);
|
|
set_text(selitem->name);
|
|
} else
|
|
return;
|
|
break;
|
|
case XK_Right:
|
|
case XK_Tab:
|
|
if (!selitem)
|
|
return;
|
|
if (sel < size - 1) {
|
|
selitem = cext_list_get_item(&items, ++sel);
|
|
set_text(selitem->name);
|
|
} else
|
|
return;
|
|
break;
|
|
case XK_Down:
|
|
if (hist) {
|
|
set_text(hist->content);
|
|
cext_stack_top_item(&history, cext_list_get_next_item(&items, hist));
|
|
}
|
|
update_items(files[M_COMMAND]->content);
|
|
break;
|
|
case XK_Up:
|
|
if (hist) {
|
|
set_text(hist->content);
|
|
cext_stack_top_item(&history, cext_list_get_prev_item(&items, hist));
|
|
}
|
|
update_items(files[M_COMMAND]->content);
|
|
break;
|
|
case XK_Return:
|
|
if (selitem)
|
|
exec_item(selitem->name);
|
|
else if (text)
|
|
exec_item(text);
|
|
case XK_Escape:
|
|
hide();
|
|
break;
|
|
case XK_BackSpace:
|
|
if (len) {
|
|
size_t i = len;
|
|
if (i) {
|
|
do
|
|
text[--i] = 0;
|
|
while (size && i && size == update_items(text));
|
|
}
|
|
set_text(text);
|
|
update_items(files[M_COMMAND]->content);
|
|
}
|
|
break;
|
|
default:
|
|
if ((num == 1) && !iscntrl((int) buf[0])) {
|
|
buf[num] = 0;
|
|
if (len > 0)
|
|
cext_strlcat(text, buf, sizeof(text));
|
|
else
|
|
cext_strlcpy(text, buf, sizeof(text));
|
|
set_text(text);
|
|
update_items(files[M_COMMAND]->content);
|
|
}
|
|
}
|
|
if (selitem) {
|
|
if (sel < offset[OFF_CURR]) {
|
|
offset[OFF_CURR] = offset[OFF_PREV];
|
|
update_offsets();
|
|
} else if (sel >= offset[OFF_NEXT]) {
|
|
offset[OFF_CURR] = offset[OFF_NEXT];
|
|
update_offsets();
|
|
}
|
|
}
|
|
draw_menu();
|
|
}
|
|
|
|
static void check_event(Connection * c)
|
|
{
|
|
XEvent ev;
|
|
|
|
while (XPending(dpy)) {
|
|
XNextEvent(dpy, &ev);
|
|
switch (ev.type) {
|
|
case KeyPress:
|
|
handle_kpress(&ev.xkey);
|
|
break;
|
|
case Expose:
|
|
if (ev.xexpose.count == 0) {
|
|
draw_menu();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void update_geometry()
|
|
{
|
|
mrect = rect;
|
|
mrect.height = font->ascent + font->descent + 4;
|
|
if (align == SOUTH)
|
|
mrect.y = rect.height - mrect.height;
|
|
XMoveResizeWindow(dpy, win, mrect.x, mrect.y, mrect.width, mrect.height);
|
|
XSync(dpy, False);
|
|
XFreePixmap(dpy, pmap);
|
|
pmap = XCreatePixmap(dpy, win, mrect.width, mrect.height, DefaultDepth(dpy, screen_num));
|
|
XSync(dpy, False);
|
|
}
|
|
|
|
static void handle_after_write(IXPServer * s, File * f)
|
|
{
|
|
int i;
|
|
size_t len;
|
|
|
|
if (files[M_CTL] == f) {
|
|
for (i = 0; i < 2; i++) {
|
|
len = strlen(acttbl[i].name);
|
|
if (!strncmp(acttbl[i].name, (char *) f->content, len)) {
|
|
if (strlen(f->content) > len) {
|
|
acttbl[i].func(0, &((char *) f->content)[len + 1]);
|
|
} else {
|
|
acttbl[i].func(0, 0);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} else if (files[M_GEOMETRY] == f) {
|
|
if (f->content) {
|
|
if (!strncmp(f->content, "south", 6))
|
|
align = SOUTH;
|
|
else if(!strncmp(f->content, "north", 6))
|
|
align = NORTH;
|
|
update_geometry();
|
|
draw_menu();
|
|
}
|
|
} else if (files[M_FONT] == f) {
|
|
XFreeFont(dpy, font);
|
|
font = blitz_getfont(dpy, files[M_FONT]->content);
|
|
update_geometry();
|
|
draw_menu();
|
|
} else if (files[M_COMMAND] == f) {
|
|
update_items(files[M_COMMAND]->content);
|
|
draw_menu();
|
|
}
|
|
check_event(0);
|
|
}
|
|
|
|
static void handle_before_read(IXPServer * s, File * f)
|
|
{
|
|
char buf[64];
|
|
if (f != files[M_GEOMETRY])
|
|
return;
|
|
snprintf(buf, sizeof(buf), "%d,%d,%d,%d", mrect.x, mrect.y,
|
|
mrect.width, mrect.height);
|
|
if (f->content)
|
|
free(f->content);
|
|
f->content = strdup(buf);
|
|
f->size = strlen(buf);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int i;
|
|
XSetWindowAttributes wa;
|
|
XGCValues gcv;
|
|
|
|
/* command line args */
|
|
for (i = 1; (i < argc) && (argv[i][0] == '-'); i++) {
|
|
switch (argv[i][1]) {
|
|
case 'v':
|
|
fprintf(stdout, "%s", version[0]);
|
|
exit(0);
|
|
break;
|
|
case 's':
|
|
if (i + 1 < argc)
|
|
sockfile = argv[++i];
|
|
else
|
|
usage();
|
|
break;
|
|
default:
|
|
usage();
|
|
break;
|
|
}
|
|
}
|
|
|
|
dpy = XOpenDisplay(0);
|
|
if (!dpy) {
|
|
fprintf(stderr, "%s", "wmimenu: cannot open display\n");
|
|
exit(1);
|
|
}
|
|
screen_num = DefaultScreen(dpy);
|
|
|
|
ixps = wmii_setup_server(sockfile);
|
|
|
|
/* init */
|
|
if (!(files[M_CTL] = ixp_create(ixps, "/ctl"))) {
|
|
perror("wmimenu: cannot connect IXP server");
|
|
exit(1);
|
|
}
|
|
files[M_CTL]->after_write = handle_after_write;
|
|
files[M_GEOMETRY] = ixp_create(ixps, "/geometry");
|
|
files[M_GEOMETRY]->before_read = handle_before_read;
|
|
files[M_GEOMETRY]->after_write = handle_after_write;
|
|
files[M_PRE_COMMAND] = ixp_create(ixps, "/precmd");
|
|
files[M_COMMAND] = ixp_create(ixps, "/cmd");
|
|
files[M_COMMAND]->after_write = handle_after_write;
|
|
files[M_HISTORY] = ixp_create(ixps, "/history");
|
|
add_history("");
|
|
files[M_LOOKUP] = ixp_create(ixps, "/lookup");
|
|
files[M_FONT] = wmii_create_ixpfile(ixps, "/font", BLITZ_FONT);
|
|
files[M_FONT]->after_write = handle_after_write;
|
|
font = blitz_getfont(dpy, files[M_FONT]->content);
|
|
files[M_SEL_BG_COLOR] = wmii_create_ixpfile(ixps, "/sstyle/bgcolor", BLITZ_SEL_BG_COLOR);
|
|
files[M_SEL_TEXT_COLOR] = wmii_create_ixpfile(ixps, "/sstyle/fgcolor", BLITZ_SEL_FG_COLOR);
|
|
files[M_SEL_BORDER_COLOR] = wmii_create_ixpfile(ixps, "/sstyle/bordercolor", BLITZ_SEL_BORDER_COLOR);
|
|
files[M_NORM_BG_COLOR] = wmii_create_ixpfile(ixps, "/nstyle/bgcolor", BLITZ_NORM_BG_COLOR);
|
|
files[M_NORM_TEXT_COLOR] = wmii_create_ixpfile(ixps, "/nstyle/fgcolor", BLITZ_NORM_FG_COLOR);
|
|
files[M_NORM_BORDER_COLOR] = wmii_create_ixpfile(ixps, "/nstyle/bordercolor", BLITZ_NORM_BORDER_COLOR);
|
|
|
|
wa.override_redirect = 1;
|
|
wa.background_pixmap = ParentRelative;
|
|
wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask
|
|
| SubstructureRedirectMask | SubstructureNotifyMask;
|
|
|
|
rect.x = rect.y = 0;
|
|
rect.width = DisplayWidth(dpy, screen_num);
|
|
rect.height = DisplayHeight(dpy, screen_num);
|
|
mrect = rect;
|
|
mrect.height = font->ascent + font->descent + 4;
|
|
mrect.y = rect.height - mrect.height;
|
|
|
|
win = XCreateWindow(dpy, RootWindow(dpy, screen_num), mrect.x, mrect.y,
|
|
mrect.width, mrect.height, 0, DefaultDepth(dpy, screen_num),
|
|
CopyFromParent, DefaultVisual(dpy, screen_num),
|
|
CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
|
|
XDefineCursor(dpy, win, XCreateFontCursor(dpy, XC_xterm));
|
|
XSync(dpy, False);
|
|
|
|
/* window pixmap */
|
|
gcv.function = GXcopy;
|
|
gcv.graphics_exposures = False;
|
|
|
|
gc = XCreateGC(dpy, win, 0, 0);
|
|
pmap = XCreatePixmap(dpy, win, mrect.width, mrect.height, DefaultDepth(dpy, screen_num));
|
|
|
|
/* main event loop */
|
|
run_server_with_fd_support(ixps, ConnectionNumber(dpy), check_event, 0);
|
|
deinit_server(ixps);
|
|
XFreePixmap(dpy, pmap);
|
|
XFreeGC(dpy, gc);
|
|
XCloseDisplay(dpy);
|
|
|
|
return 0;
|
|
}
|