#include "app.h"
#include <math.h>
#include <sys/time.h>
#include <sys/types.h>
#include <poll.h>
#include <unistd.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
int appInit(App *app, const char *touch_dev) {
LOGDBG("app: init");
CLEARFROM(app, dpy);
LOGDBG("app: init: connect to xcb");
app->dpy = XOpenDisplay(NULL);
if (!app->dpy)
return LOGERR("app: init: cannot connect to xcb");
LOGDBG("app: init: get screen");
app->screen = DefaultScreen(app->dpy);
app->sw = DisplayWidth(app->dpy, app->screen);
app->sh = DisplayHeight(app->dpy, app->screen);
XRRRotations(app->dpy, app->screen, &app->sr);
LOGDBG("app: init: screen=%d sw=%d sh=%d sr=%02x", app->screen, app->sw, app->sh, app->sr);
LOGDBG("app: init: get root window");
app->root = RootWindow(app->dpy, app->screen);
LOGDBG("app: init: create window");
app->x = 0;
app->w = app->sw * WIDTH_SCALE;
app->h = app->sh * HEIGHT_SCALE;
#if defined(WIDTH_SCALE_P) && defined(HEIGHT_SCALE_P)
if (app->sw < app->sh) {
app->w = app->sw * WIDTH_SCALE_P;
app->h = app->sh * HEIGHT_SCALE_P;
}
#endif
#ifdef TOP
app->y = 0;
#else
app->y = app->sh - app->h;
#endif
XSetWindowAttributes attr = {};
#ifdef NOBORDER
attr.override_redirect = 1;
#endif
app->win = XCreateWindow(
app->dpy, app->root, // display and parent window
app->x, app->y, app->w, app->h, 0, // position, size and border width
CopyFromParent, CopyFromParent, CopyFromParent, // depth, class and visual
CWOverrideRedirect, &attr ); // mask with attributes
XSelectInput(
app->dpy, app->win,
StructureNotifyMask
| ExposureMask
| ButtonPressMask
| ButtonReleaseMask
| Button1MotionMask );
#if defined(NOBORDER) || defined(DOCK)
LOGDBG("app: init: disable decorations");
Atom amh = XInternAtom(app->dpy, "_MOTIF_WM_HINTS", False);
long amhv[5] = { 2 };
XChangeProperty(app->dpy, app->win, amh, amh, 32, PropModeReplace, (unsigned char*)amhv, 5);
#endif
#ifndef NOBORDER
LOGDBG("app: init: set window title");
char *title = TITLE;
XTextProperty wtitle;
if (!XStringListToTextProperty(&title, 1, &wtitle)) {
LOGWRN("app: init: XStringListToTextProperty error for title: %s", title);
} else {
XSetWMName(app->dpy, app->win, &wtitle);
XFree(wtitle.value);
}
#endif
#ifdef DOCK
LOGDBG("app: init: set window as dock");
Atom awt = XInternAtom(app->dpy, "_NET_WM_WINDOW_TYPE", False);
Atom awtv = XInternAtom(app->dpy, "_NET_WM_WINDOW_TYPE_DOCK", False);
XChangeProperty(app->dpy, app->win, awt, XA_ATOM, 32, PropModeReplace, (unsigned char*)&awtv, 1);
Atom aaa = XInternAtom(app->dpy, "_NET_WM_ALLOWED_ACTIONS", False);
XChangeProperty(app->dpy, app->win, aaa, XA_ATOM, 32, PropModeReplace, NULL, 0);
Atom aws = XInternAtom(app->dpy, "_NET_WM_STATE", False);
Atom awss = XInternAtom(app->dpy, "_NET_WM_STATE_STIKY", False);
XChangeProperty(app->dpy, app->win, aws, XA_ATOM, 32, PropModeAppend, (unsigned char*)&awss, 1);
Atom awd = XInternAtom(app->dpy, "_NET_WM_DESKTOP", False);
long all = -1;
XChangeProperty(app->dpy, app->win, awd, XA_ATOM, 32, PropModeReplace, (unsigned char*)&all, 1);
#endif
Atom ahps = XInternAtom(app->dpy, "_HILDON_PORTRAIT_MODE_SUPPORT", True);
if (ahps) {
LOGDBG("app: init: set portrait mode support for hildon");
long v = 1;
XChangeProperty(app->dpy, app->win, ahps, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&v, 1);
}
LOGDBG("app: init: set window minimum size");
XSizeHints *wsh = XAllocSizeHints();
if (!wsh) {
LOGWRN("app: init: XAllocSizeHints error");
} else {
#ifdef LOCK_SIZE
wsh->flags = PMinSize | PMaxSize;
wsh->min_width = wsh->max_width = app->w;
wsh->min_height = wsh->max_height = app->h;
#else
wsh->flags = PMinSize;
wsh->min_width = MIN_WIDTH;
wsh->min_height = MIN_HEIGHT;
#endif
XSetSizeHints(app->dpy, app->win, wsh, XA_WM_NORMAL_HINTS);
XFree(wsh);
}
// window must not grab the focus
LOGDBG("app: init: set window input hint");
XWMHints *wmh = XAllocWMHints();
if (!wmh) {
LOGWRN("app: init: XAllocWMHints error");
} else {
wmh->flags = InputHint;
wmh->input = False;
XSetWMHints(app->dpy, app->win, wmh);
XFree(wmh);
}
// subscribe to windows delete message from window manager
app->aWmDel = XInternAtom(app->dpy, "WM_DELETE_WINDOW", False);
XSetWMProtocols(app->dpy, app->win, &app->aWmDel, 1);
// subscribe to keyboard state and layout changes
XkbSelectEvents(app->dpy, XkbUseCoreKbd, XkbMapNotifyMask | XkbStateNotifyMask, XkbMapNotifyMask | XkbStateNotifyMask);
#ifdef SCREEN_EVENTS
// subscribe to xrandr events
if (!XRRQueryExtension(app->dpy, &app->xrev, &app->xrerr)) {
LOGWRN("app: init: XRRQueryExtension error");
} else {
app->xron = 1;
XRRSelectInput(app->dpy, app->win, RRScreenChangeNotifyMask);
}
#endif
//appMove(app, app->x, app->y, app->w, app->h, 1);
//appUpdateStrut(app);
// init submodules
touchInit(&app->touch, app, touch_dev); // touch is optional
if (graphInit(&app->graph, app)) {
graphResize(&app->graph);
if (inputInit(&app->input, app)) {
if (keyboardInit(&app->keyboard, app)) {
return 1;
}
inputDeinit(&app->input);
}
graphDeinit(&app->graph);
} else {
XCloseDisplay(app->dpy);
}
return 0;
}
void appDeinit(App *app) {
LOGDBG("app: deinit");
keyboardDeinit(&app->keyboard);
inputDeinit(&app->input);
graphDeinit(&app->graph);
XCloseDisplay(app->dpy);
}
int appRun(App *app) {
LOGDBG("app: run");
if (app->run)
return LOGERR("app: run: seems already run");
app->run = 1;
LOGDBG("app: run: show window");
XMapWindow(app->dpy, app->win);
//appMove(app, app->x, app->y, app->w, app->h, 1);
appUpdateStrut(app);
//appMove(app, app->x, app->y, app->w, app->h, 1);
LOGDBG("app: run: get connection file descriptor");
int fd = ConnectionNumber(app->dpy);
if (fd < 0)
LOGERR("app: run: cannot get XLib file descruptor");
LOGDBG("app: run: event loop");
int buttonDown = 0;
unsigned int buttonDownMs = 0;
struct pollfd fds[2] = {};
fds[0].fd = fd;
fds[1].fd = app->touch.fd;
fds[0].events = fds[1].events = POLLIN;
int fdcnt = app->touch.app ? 2 : 1;
XFlush(app->dpy);
while(1) {
int hasEvent = 0;
XEvent event = {};
fds[0].revents = fds[1].revents = 0;
// wait for next event
if (XPending(app->dpy)) {
XNextEvent(app->dpy, &event);
hasEvent = 1;
} else
if (buttonDown != 1) {
// just wait for fds
poll(fds, fdcnt, -1);
} else {
// manually call select for the X-socket to use timeout
int dt = monotonicMs() - buttonDownMs;
if (dt < LONGPRESS_MS) {
dt = LONGPRESS_MS - dt;
LOGDBG("app: run: use poll with timeout %d ms", dt);
XFlush(app->dpy);
poll(fds, fdcnt, dt);
}
}
if (!hasEvent && fds[0].revents && XPending(app->dpy)) {
XNextEvent(app->dpy, &event);
hasEvent = 1;
}
if (hasEvent) {
// handle X11 events
switch(event.type) {
case Expose: {
LOGDBG( "app: expose: x=%d, y=%d, w=%d, h=%d",
event.xexpose.x, event.xexpose.y, event.xexpose.width, event.xexpose.height );
appInvalidateRect(app, event.xexpose.x, event.xexpose.y, event.xexpose.width, event.xexpose.height);
break;
}
case ConfigureNotify: {
if ( event.xconfigure.width
&& event.xconfigure.height
&& ( app->x != event.xconfigure.x
|| app->y != event.xconfigure.y
|| app->w != event.xconfigure.width
|| app->h != event.xconfigure.height ))
{
LOGDBG( "app: moved: x=%d y=%d w=%d, h=%d",
event.xconfigure.x, event.xconfigure.y, event.xconfigure.width, event.xconfigure.height );
int resized = app->w != event.xconfigure.width || app->h != event.xconfigure.height;
app->x = event.xconfigure.x;
app->y = event.xconfigure.y;
app->w = event.xconfigure.width;
app->h = event.xconfigure.height;
if (resized) graphResize(&app->graph);
appUpdateStrut(app);
}
break;
}
case ButtonPress: {
if (event.xbutton.button == 1) {
LOGDBG("app: mouse down: x=%d, y=%d", event.xbutton.x, event.xbutton.y);
if (!buttonDown) { buttonDown = 1; buttonDownMs = monotonicMs(); }
keyboardMouseDown(&app->keyboard, event.xbutton.x, event.xbutton.y);
}
appUpdateStrut(app);
break;
}
case ButtonRelease: {
if (event.xbutton.button == 1) {
LOGDBG("app: mouse up");
buttonDown = 0;
keyboardMouseUp(&app->keyboard);
}
break;
}
case MotionNotify: {
if (event.xmotion.state & Button1Mask) {
LOGDBG("app: mouse motion: x=%d, y=%d", event.xmotion.x, event.xmotion.y);
keyboardMouseMotion(&app->keyboard, event.xmotion.x, event.xmotion.y);
}
break;
}
case MappingNotify: {
if ( event.xmapping.request == MappingModifier
|| event.xmapping.request == MappingKeyboard )
{
LOGDBG("app: kayboard layout changed");
XRefreshKeyboardMapping(&event.xmapping);
inputUpdateLayout(
&app->input,
event.xmapping.first_keycode,
event.xmapping.first_keycode + event.xmapping.count );
}
break;
}
case ClientMessage: {
if (event.xclient.data.l[0] == app->aWmDel) {
LOGDBG("app: delete window event");
appStop(app, 0);
}
break;
}
default:
if (app->xron && event.type == app->xrev + RRScreenChangeNotify) {
LOGDBG("app: screen change event");
XRRScreenChangeNotifyEvent *ev = (XRRScreenChangeNotifyEvent*)&event;
appUpdateScreenSize(app, ev->width, ev->height, ev->rotation);
}
break;
}
}
if (app->touch.app && fds[1].revents) {
// handle direct touch events
int x, y, p;
while(touchGet(&app->touch, &x, &y, &p)) {
if (p && !buttonDown) {
LOGDBG("app: touch pressed: x=%d, y=%d", x, y);
buttonDown = 1; buttonDownMs = monotonicMs();
keyboardMouseDown(&app->keyboard, x, y);
hasEvent = 1;
} else
if (!p && buttonDown) {
LOGDBG("app: touch released");
buttonDown = 0;
keyboardMouseUp(&app->keyboard);
hasEvent = 1;
} else
if (p) {
LOGDBG("app: touch motion: x=%d, y=%d", x, y);
keyboardMouseMotion(&app->keyboard, x, y);
hasEvent = 1;
}
}
}
if (buttonDown == 1 && monotonicMs() - buttonDownMs >= LONGPRESS_MS) {
LOGDBG("app: long press");
buttonDown = 2;
keyboardMouseLongDown(&app->keyboard);
hasEvent = 1;
}
if (app->run != 1) break;
if (!hasEvent) continue;
inputUpdateModifiers(&app->input);
keyboardResize(&app->keyboard);
keyboardUpdateModifiers(&app->keyboard);
if (app->irw > 0 && app->irh > 0) {
LOGDBG("app: draw: x=%d, y=%d, w=%d, h=%d", app->irx, app->iry, app->irw, app->irh);
graphDrawBackgound(&app->graph, app->irx, app->iry, app->irw, app->irh);
keyboardDraw(&app->keyboard, app->irx, app->iry, app->irw, app->irh);
app->irw = 0;
}
}
int err = app->run == -2;
app->run = 0;
LOGDBG("app: run: done err=%d", err);
return !err;
}
void appStop(App *app, int err) {
LOGDBG("app: stop: err=%d", err);
if (!app->run) {
LOGERR("app: stop: seems not started");
return;
}
if (app->run == 1) app->run = -1;
if (err) app->run = -2;
}
void appUpdateStrut(App *app) {
#ifdef DOCK
long v[12] = { 0, 0, 0, app->h, 0, app->sh, 0, app->sh, 0, app->sw, 0, app->sw };
#ifdef TOP
v[2] = app->h;
#else
v[3] = app->h;
#endif
if (app->dockt == v[2] && app->dockb == v[3])
return;
app->dockt = v[2];
app->dockb = v[3];
LOGDBG("app: update strut: %lu %lu %lu %lu", v[0], v[1], v[2], v[3]);
Atom k = XInternAtom(app->dpy, "_NET_WM_STRUT", False);
XChangeProperty(app->dpy, app->win, k, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)v, 4);
Atom kp = XInternAtom(app->dpy, "_NET_WM_STRUT_PARTIAL", False);
XChangeProperty(app->dpy, app->win, kp, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)v, 12);
#ifdef TOP
appMove(app, 0, 0, app->sw, app->h, 1);
#else
appMove(app, 0, app->sh - app->h, app->sw, app->h, 1);
#endif
#endif // DOCK
}
void appUpdateScreenSize(App *app, int sw, int sh, Rotation sr) {
if (sw <= 0 || sh <= 0) return;
if (sw == app->sw && sh == app->sh && sr == app->sr) return;
LOGDBG("app: update screen size: w=%d, h=%d, r=%d", sw, sh, sr);
app->sr = sr;
if (sw == app->sw && sh == app->sh) return;
#if defined(DOCK) || (defined(LOCK_SIZE) && defined(NOBORDER) && defined(NOTITLE))
// static size case
int w = sw * WIDTH_SCALE;
int h = sh * HEIGHT_SCALE;
#if defined(WIDTH_SCALE_P) && defined(HEIGHT_SCALE_P)
if (sw < sh) {
w = sw * WIDTH_SCALE_P;
h = sh * HEIGHT_SCALE_P;
}
#endif
int x0 = 0;
#ifdef TOP
int y0 = 0;
#else
int y0 = sh - h;
#endif
#else
// dynamic size case
double kx = sw/(double)app->sw;
double ky = sh/(double)app->sh;
#if defined(WIDTH_SCALE_P) && defined(HEIGHT_SCALE_P)
// rescale for portrait/landscape switching
if (app->sw < app->sh && sw >= sh) {
// portrait -> landscape
kx *= ((double)WIDTH_SCALE)/((double)WIDTH_SCALE_P);
ky *= ((double)HEIGHT_SCALE)/((double)HEIGHT_SCALE_P);
} else
if (app->sw >= app->sh && sw < sh) {
// landscape -> portrait
kx *= ((double)WIDTH_SCALE_P)/((double)WIDTH_SCALE);
ky *= ((double)HEIGHT_SCALE_P)/((double)HEIGHT_SCALE);
}
#endif
// scale window corners
int x0 = (int)round( app->x*kx );
int y0 = (int)round( app->y*ky );
int x1 = (int)round( (app->x + app->w)*kx );
int y1 = (int)round( (app->y + app->h)*ky );
int w = x1 - x0;
int h = y1 - y0;
#endif
app->sw = sw;
app->sh = sh;
appMove(app, x0, y0, w, h, 0);
}
void appMove(App *app, int x, int y, int w, int h, int force) {
if (w < MIN_WIDTH) w = MIN_WIDTH;
if (h < MIN_HEIGHT) h = MIN_HEIGHT;
unsigned int mask = 0;
XWindowChanges ch = {};
if (force || app->x != x) ch.x = x, mask |= CWX;
if (force || app->y != y) ch.y = y, mask |= CWY;
if (force || app->w != w) ch.width = w, mask |= CWWidth;
if (force || app->h != h) ch.height = h, mask |= CWHeight;
if (!mask) return;
LOGDBG("app: move: x=%d, y=%d, w=%d, h=%d", x, y, w, h);
XConfigureWindow(app->dpy, app->win, mask, &ch);
XFlush(app->dpy);
}
void appInvalidateRect(App *app, int x, int y, int w, int h) {
LOGDBG("app: invalidate rect: x=%d, y=%d, w=%d, h=%d", x, y, w, h);
rectIntersect(&x, &y, &w, &h, 0, 0, app->w, app->h);
rectMerge(&app->irx, &app->iry, &app->irw, &app->irh, x, y, w, h);
LOGDBG("app: invalidate rect: summary x=%d, y=%d, w=%d, h=%d", app->irx, app->iry, app->irw, app->irh);
}