|
|
452870 |
|
|
|
452870 |
#include "app.h"
|
|
|
452870 |
|
|
|
5b6cfc |
#include <sys time.h=""></sys>
|
|
|
5b6cfc |
#include <sys types.h=""></sys>
|
|
|
5b6cfc |
#include <unistd.h></unistd.h>
|
|
|
452870 |
|
|
|
dbe1a1 |
#include <x11 xutil.h=""></x11>
|
|
|
dbe1a1 |
#include <x11 xatom.h=""></x11>
|
|
|
e3897e |
#include <x11 extensions="" xrandr.h=""></x11>
|
|
|
452870 |
|
|
|
452870 |
|
|
|
452870 |
int appInit(App *app) {
|
|
|
452870 |
LOGDBG("app: init");
|
|
|
dbe1a1 |
|
|
|
dbe1a1 |
CLEARFROM(app, dpy);
|
|
|
452870 |
|
|
|
452870 |
LOGDBG("app: init: connect to xcb");
|
|
|
dbe1a1 |
app->dpy = XOpenDisplay(NULL);
|
|
|
dbe1a1 |
if (!app->dpy)
|
|
|
452870 |
return LOGERR("app: init: cannot connect to xcb");
|
|
|
452870 |
|
|
|
452870 |
LOGDBG("app: init: get screen");
|
|
|
dbe1a1 |
app->screen = DefaultScreen(app->dpy);
|
|
|
dbe1a1 |
app->sw = DisplayWidth(app->dpy, app->screen);
|
|
|
dbe1a1 |
app->sh = DisplayHeight(app->dpy, app->screen);
|
|
|
dbe1a1 |
LOGDBG("app: init: screen=%d sw=%d sh=%d", app->screen, app->sw, app->sh);
|
|
|
dbe1a1 |
|
|
|
dbe1a1 |
LOGDBG("app: init: get root window");
|
|
|
dbe1a1 |
app->root = RootWindow(app->dpy, app->screen);
|
|
|
dbe1a1 |
|
|
|
dbe1a1 |
LOGDBG("app: init: create window");
|
|
|
dbe1a1 |
|
|
|
dbe1a1 |
app->x = 0;
|
|
|
4b2399 |
app->w = app->sw * WIDTH_SCALE;
|
|
|
4b2399 |
app->h = app->sh * HEIGHT_SCALE;
|
|
|
0da9df |
#ifdef TOP
|
|
|
0da9df |
app->y = 0;
|
|
|
0da9df |
#else
|
|
|
dbe1a1 |
app->y = app->sh - app->h;
|
|
|
0da9df |
#endif
|
|
|
4b2399 |
|
|
|
dbe1a1 |
XSetWindowAttributes attr = {};
|
|
|
dbe1a1 |
#ifdef NOBORDER
|
|
|
dbe1a1 |
attr.override_redirect = 1;
|
|
|
dbe1a1 |
#endif
|
|
|
dbe1a1 |
|
|
|
dbe1a1 |
app->win = XCreateWindow(
|
|
|
dbe1a1 |
app->dpy, app->root, // display and parent window
|
|
|
dbe1a1 |
app->x, app->y, app->w, app->h, 0, // position, size and border width
|
|
|
dbe1a1 |
CopyFromParent, CopyFromParent, CopyFromParent, // depth, class and visual
|
|
|
dbe1a1 |
CWOverrideRedirect, &attr); // mask with attributes
|
|
|
dbe1a1 |
|
|
|
dbe1a1 |
XSelectInput(
|
|
|
dbe1a1 |
app->dpy, app->win,
|
|
|
dbe1a1 |
StructureNotifyMask
|
|
|
dbe1a1 |
| ExposureMask
|
|
|
dbe1a1 |
| ButtonPressMask
|
|
|
dbe1a1 |
| ButtonReleaseMask
|
|
|
dbe1a1 |
| Button1MotionMask );
|
|
|
dbe1a1 |
|
|
|
dbe1a1 |
LOGDBG("app: init: set window title");
|
|
|
dbe1a1 |
#ifndef NOBORDER
|
|
|
dbe1a1 |
char *title = TITLE;
|
|
|
dbe1a1 |
XTextProperty wtitle;
|
|
|
dbe1a1 |
if (!XStringListToTextProperty(&title, 1, &wtitle)) {
|
|
|
dbe1a1 |
LOGWRN("app: init: XStringListToTextProperty error for title: %s", title);
|
|
|
dbe1a1 |
} else {
|
|
|
dbe1a1 |
XSetWMName(app->dpy, app->win, &wtitle);
|
|
|
dbe1a1 |
XFree(wtitle.value);
|
|
|
dbe1a1 |
}
|
|
|
7aad5f |
#ifdef DOCK
|
|
|
7aad5f |
Atom wt = XInternAtom(app->dpy, "_NET_WM_WINDOW_TYPE", False);
|
|
|
7aad5f |
//Atom wtval = XInternAtom(app->dpy, "_HILDON_WM_WINDOW_TYPE_REMOTE_TEXTURE", False);
|
|
|
7aad5f |
Atom wtval = XInternAtom(app->dpy, "_NET_WM_WINDOW_TYPE_TOOLBAR", False);
|
|
|
7aad5f |
if (wt != None && wtval != None)
|
|
|
7aad5f |
XChangeProperty(app->dpy, app->win, wt, XA_ATOM, 32, PropModeReplace, (unsigned char*)&wtval, 1);
|
|
|
5007f5 |
Atom pm = XInternAtom(app->dpy, "_HILDON_PORTRAIT_MODE_SUPPORT", False);
|
|
|
5007f5 |
unsigned int pmval = 1;
|
|
|
5007f5 |
if (pm)
|
|
|
5007f5 |
XChangeProperty(app->dpy, app->win, pm, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&pmval, 1);
|
|
|
7aad5f |
#endif
|
|
|
dbe1a1 |
#endif
|
|
|
dbe1a1 |
|
|
|
dbe1a1 |
LOGDBG("app: init: set window minimum size");
|
|
|
dbe1a1 |
XSizeHints *wsh = XAllocSizeHints();
|
|
|
dbe1a1 |
if (!wsh) {
|
|
|
dbe1a1 |
LOGWRN("app: init: XAllocSizeHints error");
|
|
|
dbe1a1 |
} else {
|
|
|
4b2399 |
#ifdef LOCK_SIZE
|
|
|
4b2399 |
wsh->flags = PMinSize | PMaxSize;
|
|
|
4b2399 |
wsh->min_width = wsh->max_width = app->w;
|
|
|
4b2399 |
wsh->min_height = wsh->max_height = app->h;
|
|
|
4b2399 |
#else
|
|
|
dbe1a1 |
wsh->flags = PMinSize;
|
|
|
dbe1a1 |
wsh->min_width = MIN_WIDTH;
|
|
|
dbe1a1 |
wsh->min_height = MIN_HEIGHT;
|
|
|
4b2399 |
#endif
|
|
|
dbe1a1 |
XSetSizeHints(app->dpy, app->win, wsh, XA_WM_NORMAL_HINTS);
|
|
|
dbe1a1 |
XFree(wsh);
|
|
|
dbe1a1 |
}
|
|
|
dbe1a1 |
|
|
|
dbe1a1 |
// window must not grab the focus
|
|
|
dbe1a1 |
LOGDBG("app: init: set window input hint");
|
|
|
dbe1a1 |
XWMHints *wmh = XAllocWMHints();
|
|
|
dbe1a1 |
if (!wmh) {
|
|
|
dbe1a1 |
LOGWRN("app: init: XAllocWMHints error");
|
|
|
dbe1a1 |
} else {
|
|
|
dbe1a1 |
wmh->flags = InputHint;
|
|
|
dbe1a1 |
wmh->input = False;
|
|
|
dbe1a1 |
XSetWMHints(app->dpy, app->win, wmh);
|
|
|
dbe1a1 |
XFree(wmh);
|
|
|
dbe1a1 |
}
|
|
|
dbe1a1 |
|
|
|
8864eb |
// subscribe to windows delete message from window manager
|
|
|
8864eb |
app->aWmDel = XInternAtom(app->dpy, "WM_DELETE_WINDOW", False);
|
|
|
8864eb |
XSetWMProtocols(app->dpy, app->win, &app->aWmDel, 1);
|
|
|
8864eb |
|
|
|
8864eb |
// subscribe to keyboard state and layout changes
|
|
|
8864eb |
XkbSelectEvents(app->dpy, XkbUseCoreKbd, XkbMapNotifyMask | XkbStateNotifyMask, XkbMapNotifyMask | XkbStateNotifyMask);
|
|
|
8864eb |
|
|
|
e3897e |
#ifdef SCREEN_EVENTS
|
|
|
e3897e |
// subscribe to xrandr events
|
|
|
e3897e |
if (!XRRQueryExtension(app->dpy, &app->xrev, &app->xrerr)) {
|
|
|
e3897e |
LOGWRN("app: init: XRRQueryExtension error");
|
|
|
e3897e |
} else {
|
|
|
e3897e |
app->xron = 1;
|
|
|
e3897e |
XRRSelectInput(app->dpy, app->win, RRScreenChangeNotifyMask);
|
|
|
e3897e |
}
|
|
|
e3897e |
#endif
|
|
|
e3897e |
|
|
|
dbe1a1 |
// init submodules
|
|
|
dbe1a1 |
if (graphInit(&app->graph, app)) {
|
|
|
dbe1a1 |
graphResize(&app->graph);
|
|
|
dbe1a1 |
if (inputInit(&app->input, app)) {
|
|
|
dbe1a1 |
if (keyboardInit(&app->keyboard, app)) {
|
|
|
dbe1a1 |
return 1;
|
|
|
452870 |
}
|
|
|
dbe1a1 |
inputDeinit(&app->input);
|
|
|
452870 |
}
|
|
|
dbe1a1 |
graphDeinit(&app->graph);
|
|
|
452870 |
} else {
|
|
|
dbe1a1 |
XCloseDisplay(app->dpy);
|
|
|
452870 |
}
|
|
|
452870 |
|
|
|
452870 |
return 0;
|
|
|
452870 |
}
|
|
|
452870 |
|
|
|
452870 |
|
|
|
452870 |
void appDeinit(App *app) {
|
|
|
452870 |
LOGDBG("app: deinit");
|
|
|
452870 |
keyboardDeinit(&app->keyboard);
|
|
|
452870 |
inputDeinit(&app->input);
|
|
|
452870 |
graphDeinit(&app->graph);
|
|
|
dbe1a1 |
XCloseDisplay(app->dpy);
|
|
|
452870 |
}
|
|
|
452870 |
|
|
|
452870 |
|
|
|
452870 |
int appRun(App *app) {
|
|
|
452870 |
LOGDBG("app: run");
|
|
|
452870 |
|
|
|
843e7a |
if (app->run)
|
|
|
843e7a |
return LOGERR("app: run: seems already run");
|
|
|
843e7a |
app->run = 1;
|
|
|
843e7a |
|
|
|
452870 |
LOGDBG("app: run: show window");
|
|
|
dbe1a1 |
XMapWindow(app->dpy, app->win);
|
|
|
452870 |
|
|
|
dbe1a1 |
LOGDBG("app: run: get connection file descriptor");
|
|
|
dbe1a1 |
int fd = ConnectionNumber(app->dpy);
|
|
|
5b6cfc |
if (fd < 0)
|
|
|
dbe1a1 |
LOGERR("app: run: cannot get XLib file descruptor");
|
|
|
5b6cfc |
|
|
|
dbe1a1 |
LOGDBG("app: run: event loop");
|
|
|
5b6cfc |
int buttonDown = 0;
|
|
|
5b6cfc |
unsigned int buttonDownMs = 0;
|
|
|
96c481 |
fd_set fds = {};
|
|
|
96c481 |
int maxFd = fd > app->stopFd ? fd : app->stopFd;
|
|
|
96c481 |
XFlush(app->dpy);
|
|
|
5b6cfc |
while(1) {
|
|
|
dbe1a1 |
int hasEvent = 0;
|
|
|
dbe1a1 |
XEvent event = {};
|
|
|
5b6cfc |
|
|
|
96c481 |
FD_ZERO(&fds);
|
|
|
96c481 |
FD_SET(fd, &fds);
|
|
|
96c481 |
FD_SET(app->stopFd, &fds);
|
|
|
96c481 |
|
|
|
96c481 |
// wait for next event
|
|
|
96c481 |
if (XPending(app->dpy)) {
|
|
|
96c481 |
XNextEvent(app->dpy, &event);
|
|
|
96c481 |
hasEvent = 1;
|
|
|
96c481 |
} else
|
|
|
96c481 |
if (buttonDown != 1) {
|
|
|
96c481 |
// just wait for fds
|
|
|
96c481 |
select(maxFd + 1, &fds, NULL, NULL, NULL);
|
|
|
5b6cfc |
} else {
|
|
|
96c481 |
// manually call select for the X-socket to use timeout
|
|
|
96c481 |
unsigned int dt = monotonicMs() - buttonDownMs;
|
|
|
96c481 |
if (dt < LONGPRESS_MS) {
|
|
|
96c481 |
dt = LONGPRESS_MS - dt;
|
|
|
96c481 |
struct timeval t = {};
|
|
|
96c481 |
t.tv_sec = dt/1000;
|
|
|
96c481 |
t.tv_usec = dt%1000*1000;
|
|
|
96c481 |
|
|
|
96c481 |
LOGDBG("app: run: use select fd=%d, timeout=%ums, sec=%ld, usec=%ld", fd, dt, t.tv_sec, t.tv_usec);
|
|
|
96c481 |
|
|
|
96c481 |
XFlush(app->dpy);
|
|
|
96c481 |
select(maxFd + 1, &fds, NULL, NULL, &t);
|
|
|
96c481 |
}
|
|
|
96c481 |
}
|
|
|
96c481 |
|
|
|
96c481 |
if (!hasEvent && XPending(app->dpy)) {
|
|
|
dbe1a1 |
XNextEvent(app->dpy, &event);
|
|
|
dbe1a1 |
hasEvent = 1;
|
|
|
452870 |
}
|
|
|
5b6cfc |
|
|
|
dbe1a1 |
if (hasEvent) {
|
|
|
dbe1a1 |
switch(event.type) {
|
|
|
dbe1a1 |
case Expose: {
|
|
|
dbe1a1 |
LOGDBG( "app: expose: x=%d, y=%d, w=%d, h=%d",
|
|
|
dbe1a1 |
event.xexpose.x, event.xexpose.y, event.xexpose.width, event.xexpose.height );
|
|
|
dbe1a1 |
appInvalidateRect(app, event.xexpose.x, event.xexpose.y, event.xexpose.width, event.xexpose.height);
|
|
|
5b6cfc |
break;
|
|
|
452870 |
}
|
|
|
dbe1a1 |
case ConfigureNotify: {
|
|
|
dbe1a1 |
if ( event.xconfigure.width
|
|
|
dbe1a1 |
&& event.xconfigure.height
|
|
|
dbe1a1 |
&& ( app->x != event.xconfigure.x
|
|
|
dbe1a1 |
|| app->y != event.xconfigure.y
|
|
|
dbe1a1 |
|| app->w != event.xconfigure.width
|
|
|
dbe1a1 |
|| app->h != event.xconfigure.height ))
|
|
|
5b6cfc |
{
|
|
|
dbe1a1 |
LOGDBG( "app: moved: x=%d y=%d w=%d, h=%d",
|
|
|
dbe1a1 |
event.xconfigure.x, event.xconfigure.y, event.xconfigure.width, event.xconfigure.height );
|
|
|
dbe1a1 |
int resized = app->w != event.xconfigure.width || app->h != event.xconfigure.height;
|
|
|
dbe1a1 |
app->x = event.xconfigure.x;
|
|
|
dbe1a1 |
app->y = event.xconfigure.y;
|
|
|
dbe1a1 |
app->w = event.xconfigure.width;
|
|
|
dbe1a1 |
app->h = event.xconfigure.height;
|
|
|
5b6cfc |
if (resized) graphResize(&app->graph);
|
|
|
5b6cfc |
}
|
|
|
5b6cfc |
break;
|
|
|
452870 |
}
|
|
|
dbe1a1 |
case ButtonPress: {
|
|
|
dbe1a1 |
if (event.xbutton.button == 1) {
|
|
|
dbe1a1 |
LOGDBG("app: mouse down: x=%d, y=%d", event.xbutton.x, event.xbutton.y);
|
|
|
5b6cfc |
if (!buttonDown) { buttonDown = 1; buttonDownMs = monotonicMs(); }
|
|
|
dbe1a1 |
keyboardMouseDown(&app->keyboard, event.xbutton.x, event.xbutton.y);
|
|
|
5b6cfc |
}
|
|
|
5b6cfc |
break;
|
|
|
63daec |
}
|
|
|
dbe1a1 |
case ButtonRelease: {
|
|
|
dbe1a1 |
if (event.xbutton.button == 1) {
|
|
|
5b6cfc |
LOGDBG("app: mouse up");
|
|
|
5b6cfc |
buttonDown = 0;
|
|
|
5b6cfc |
keyboardMouseUp(&app->keyboard);
|
|
|
5b6cfc |
}
|
|
|
5b6cfc |
break;
|
|
|
5b6cfc |
}
|
|
|
dbe1a1 |
case MotionNotify: {
|
|
|
dbe1a1 |
if (event.xmotion.state & Button1Mask) {
|
|
|
dbe1a1 |
LOGDBG("app: mouse motion: x=%d, y=%d", event.xmotion.x, event.xmotion.y);
|
|
|
dbe1a1 |
keyboardMouseMotion(&app->keyboard, event.xmotion.x, event.xmotion.y);
|
|
|
5b6cfc |
}
|
|
|
452870 |
break;
|
|
|
5b6cfc |
}
|
|
|
8864eb |
case MappingNotify: {
|
|
|
8864eb |
if ( event.xmapping.request == MappingModifier
|
|
|
8864eb |
|| event.xmapping.request == MappingKeyboard )
|
|
|
8864eb |
{
|
|
|
8864eb |
LOGDBG("app: kayboard layout changed");
|
|
|
8864eb |
XRefreshKeyboardMapping(&event.xmapping);
|
|
|
8864eb |
inputUpdateLayout(
|
|
|
8864eb |
&app->input,
|
|
|
8864eb |
event.xmapping.first_keycode,
|
|
|
8864eb |
event.xmapping.first_keycode + event.xmapping.count );
|
|
|
8864eb |
}
|
|
|
8864eb |
break;
|
|
|
8864eb |
}
|
|
|
8864eb |
case ClientMessage: {
|
|
|
8864eb |
if (event.xclient.data.l[0] == app->aWmDel) {
|
|
|
8864eb |
LOGDBG("app: delete window event");
|
|
|
8864eb |
appStop(app, 0);
|
|
|
8864eb |
}
|
|
|
8864eb |
break;
|
|
|
8864eb |
}
|
|
|
e3897e |
default:
|
|
|
e3897e |
if (app->xron && event.type == app->xrev + RRScreenChangeNotify) {
|
|
|
e3897e |
LOGDBG("app: screen change event");
|
|
|
e3897e |
XRRScreenChangeNotifyEvent *ev = (XRRScreenChangeNotifyEvent*)&event;
|
|
|
e3897e |
appUpdateScreenSize(app, ev->width, ev->height);
|
|
|
e3897e |
}
|
|
|
e3897e |
break;
|
|
|
5b6cfc |
}
|
|
|
5b6cfc |
}
|
|
|
5b6cfc |
|
|
|
5b6cfc |
if (buttonDown == 1 && monotonicMs() - buttonDownMs >= LONGPRESS_MS) {
|
|
|
5b6cfc |
LOGDBG("app: long press");
|
|
|
5b6cfc |
buttonDown = 2;
|
|
|
5b6cfc |
keyboardMouseLongDown(&app->keyboard);
|
|
|
452870 |
}
|
|
|
452870 |
|
|
|
843e7a |
if (app->run != 1) break;
|
|
|
843e7a |
|
|
|
061fcf |
inputUpdateModifiers(&app->input);
|
|
|
061fcf |
keyboardResize(&app->keyboard);
|
|
|
061fcf |
keyboardUpdateModifiers(&app->keyboard);
|
|
|
452870 |
if (app->irw > 0 && app->irh > 0) {
|
|
|
452870 |
LOGDBG("app: draw: x=%d, y=%d, w=%d, h=%d", app->irx, app->iry, app->irw, app->irh);
|
|
|
452870 |
graphDrawBackgound(&app->graph, app->irx, app->iry, app->irw, app->irh);
|
|
|
452870 |
keyboardDraw(&app->keyboard, app->irx, app->iry, app->irw, app->irh);
|
|
|
452870 |
app->irw = 0;
|
|
|
452870 |
}
|
|
|
452870 |
}
|
|
|
452870 |
|
|
|
843e7a |
int err = app->run == -2;
|
|
|
843e7a |
app->run = 0;
|
|
|
843e7a |
LOGDBG("app: run: done err=%d", err);
|
|
|
843e7a |
return !err;
|
|
|
843e7a |
}
|
|
|
843e7a |
|
|
|
843e7a |
|
|
|
96c481 |
void appStopBySignal(App *app) {
|
|
|
96c481 |
char x = 0;
|
|
|
96c481 |
write(app->stopFd, &x, 1);
|
|
|
96c481 |
}
|
|
|
96c481 |
|
|
|
96c481 |
|
|
|
843e7a |
void appStop(App *app, int err) {
|
|
|
843e7a |
LOGDBG("app: stop: err=%d", err);
|
|
|
843e7a |
if (!app->run) {
|
|
|
843e7a |
LOGERR("app: stop: seems not started");
|
|
|
843e7a |
return;
|
|
|
843e7a |
}
|
|
|
843e7a |
if (app->run == 1) app->run = -1;
|
|
|
843e7a |
if (err) app->run = -2;
|
|
|
843e7a |
}
|
|
|
843e7a |
|
|
|
843e7a |
|
|
|
e3897e |
void appUpdateScreenSize(App *app, int sw, int sh) {
|
|
|
e3897e |
if (sw <= 0 || sh <= 0) return;
|
|
|
e3897e |
if (sw == app->sw && sh == app->sh) return;
|
|
|
e3897e |
LOGDBG("app: update screen size: w=%d, h=%d", sw, sh);
|
|
|
e3897e |
int hw = app->sw/2, hh = app->sh/2;
|
|
|
e3897e |
int x0 = (app->x*sw + hw)/app->sw;
|
|
|
e3897e |
int y0 = (app->y*sh + hh)/app->sh;
|
|
|
e3897e |
int x1 = ((app->x + app->w)*sw + hw)/app->sw;
|
|
|
e3897e |
int y1 = ((app->y + app->h)*sh + hh)/app->sh;
|
|
|
e3897e |
app->sw = sw;
|
|
|
e3897e |
app->sh = sh;
|
|
|
e3897e |
appMove(app, x0, y0, x1 - x0, y1 - y0);
|
|
|
e3897e |
}
|
|
|
e3897e |
|
|
|
e3897e |
|
|
|
843e7a |
void appMove(App *app, int x, int y, int w, int h) {
|
|
|
843e7a |
LOGDBG("app: move: x=%d, y=%d, w=%d, h=%d", x, y, w, h);
|
|
|
db2d04 |
if (w < MIN_WIDTH) w = MIN_WIDTH;
|
|
|
db2d04 |
if (h < MIN_HEIGHT) h = MIN_HEIGHT;
|
|
|
dbe1a1 |
|
|
|
dbe1a1 |
unsigned int mask = 0;
|
|
|
dbe1a1 |
XWindowChanges ch = {};
|
|
|
dbe1a1 |
if (app->x != x) ch.x = x, mask |= CWX;
|
|
|
dbe1a1 |
if (app->y != y) ch.y = y, mask |= CWY;
|
|
|
dbe1a1 |
if (app->w != w) ch.width = w, mask |= CWWidth;
|
|
|
dbe1a1 |
if (app->h != h) ch.height = h, mask |= CWHeight;
|
|
|
dbe1a1 |
if (!mask) return;
|
|
|
dbe1a1 |
|
|
|
dbe1a1 |
XConfigureWindow(app->dpy, app->win, mask, &ch);
|
|
|
db2d04 |
XFlush(app->dpy);
|
|
|
452870 |
}
|
|
|
452870 |
|
|
|
452870 |
|
|
|
452870 |
void appInvalidateRect(App *app, int x, int y, int w, int h) {
|
|
|
452870 |
LOGDBG("app: invalidate rect: x=%d, y=%d, w=%d, h=%d", x, y, w, h);
|
|
|
452870 |
rectIntersect(&x, &y, &w, &h, 0, 0, app->w, app->h);
|
|
|
452870 |
rectMerge(&app->irx, &app->iry, &app->irw, &app->irh, x, y, w, h);
|
|
|
d3e9d7 |
LOGDBG("app: invalidate rect: summary x=%d, y=%d, w=%d, h=%d", app->irx, app->iry, app->irw, app->irh);
|
|
|
452870 |
}
|
|
|
452870 |
|