Blame app.c

452870
452870
#include "app.h"
452870
cbe4b8
#include <math.h></math.h>
cbe4b8
5b6cfc
#include <sys time.h=""></sys>
5b6cfc
#include <sys types.h=""></sys>
fd85b0
#include <poll.h></poll.h>
5b6cfc
#include <unistd.h></unistd.h>
452870
dbe1a1
#include <x11 xutil.h=""></x11>
dbe1a1
#include <x11 xatom.h=""></x11>
452870
452870
7e0df9
int appInit(App *app, const char *touch_dev) {
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);
7e0df9
  XRRRotations(app->dpy, app->screen, &app->sr);
7e0df9
  LOGDBG("app: init: screen=%d sw=%d sh=%d sr=%02x", app->screen, app->sw, app->sh, app->sr);
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;
cbe4b8
cbe4b8
  #if defined(WIDTH_SCALE_P) && defined(HEIGHT_SCALE_P)
cbe4b8
  if (app->sw < app->sh) {
56756f
    app->w = app->sw * WIDTH_SCALE_P;
56756f
    app->h = app->sh * HEIGHT_SCALE_P;
cbe4b8
  }
cbe4b8
  #endif
cbe4b8
0da9df
  #ifdef TOP
0da9df
  app->y = 0;
0da9df
  #else
dbe1a1
  app->y = app->sh - app->h;
0da9df
  #endif
7e0df9
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
51e530
    CWOverrideRedirect, &attr );        // mask with attributes
dbe1a1
dbe1a1
  XSelectInput(
dbe1a1
    app->dpy, app->win,
dbe1a1
      StructureNotifyMask
dbe1a1
    | ExposureMask
dbe1a1
    | ButtonPressMask
dbe1a1
    | ButtonReleaseMask
dbe1a1
    | Button1MotionMask );
dbe1a1
51e530
  #if defined(NOBORDER) || defined(DOCK)
51e530
  LOGDBG("app: init: disable decorations");
51e530
  Atom amh = XInternAtom(app->dpy, "_MOTIF_WM_HINTS", False);
51e530
  long amhv[5] = { 2 };
51e530
  XChangeProperty(app->dpy, app->win, amh, amh, 32, PropModeReplace, (unsigned char*)amhv, 5);
51e530
  #endif
51e530
dbe1a1
  #ifndef NOBORDER
c456ae
  LOGDBG("app: init: set window title");
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
  #endif
c456ae
51e530
  #ifdef DOCK
c456ae
  LOGDBG("app: init: set window as dock");
c456ae
  Atom awt = XInternAtom(app->dpy, "_NET_WM_WINDOW_TYPE", False);
451405
  Atom awtv = XInternAtom(app->dpy, DOCK_TYPE, False);
51e530
  XChangeProperty(app->dpy, app->win, awt, XA_ATOM, 32, PropModeReplace, (unsigned char*)&awtv, 1);
51e530
  Atom aaa = XInternAtom(app->dpy, "_NET_WM_ALLOWED_ACTIONS", False);
51e530
  XChangeProperty(app->dpy, app->win, aaa, XA_ATOM, 32, PropModeReplace, NULL, 0);
325e70
  Atom aws = XInternAtom(app->dpy, "_NET_WM_STATE", False);
325e70
  Atom awss = XInternAtom(app->dpy, "_NET_WM_STATE_STIKY", False);
325e70
  XChangeProperty(app->dpy, app->win, aws, XA_ATOM, 32, PropModeAppend, (unsigned char*)&awss, 1);
325e70
  Atom awd = XInternAtom(app->dpy, "_NET_WM_DESKTOP", False);
325e70
  long all = -1;
325e70
  XChangeProperty(app->dpy, app->win, awd, XA_ATOM, 32, PropModeReplace, (unsigned char*)&all, 1);
dbe1a1
  #endif
dbe1a1
51e530
  Atom ahps = XInternAtom(app->dpy, "_HILDON_PORTRAIT_MODE_SUPPORT", True);
c456ae
  if (ahps) {
c456ae
    LOGDBG("app: init: set portrait mode support for hildon");
51e530
    long v = 1;
c456ae
    XChangeProperty(app->dpy, app->win, ahps, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&v, 1);
c456ae
  }
c456ae
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
51e530
  //appMove(app, app->x, app->y, app->w, app->h, 1);
51e530
  //appUpdateStrut(app);
51e530
dbe1a1
  // init submodules
7e0df9
  touchInit(&app->touch, app, touch_dev); // touch is optional
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);
325e70
  //appMove(app, app->x, app->y, app->w, app->h, 1);
c456ae
  appUpdateStrut(app);
325e70
  //appMove(app, app->x, app->y, app->w, app->h, 1);
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;
8603b4
fd85b0
  struct pollfd fds[2] = {};
8603b4
  fds[0].fd = fd;
fd85b0
  fds[1].fd = app->touch.fd;
fd85b0
  fds[0].events = fds[1].events = POLLIN;
fd85b0
  int fdcnt = app->touch.app ? 2 : 1;
7e0df9
96c481
  XFlush(app->dpy);
5b6cfc
  while(1) {
dbe1a1
    int hasEvent = 0;
dbe1a1
    XEvent event = {};
fd85b0
    fds[0].revents = fds[1].revents = 0;
5b6cfc
    
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
3ec526
      poll(fds, fdcnt, -1);
5b6cfc
    } else {
96c481
      // manually call select for the X-socket to use timeout
8603b4
      int dt = monotonicMs() - buttonDownMs;
96c481
      if (dt < LONGPRESS_MS) {
96c481
        dt = LONGPRESS_MS - dt;
8603b4
        LOGDBG("app: run: use poll with timeout %d ms", dt);
96c481
        XFlush(app->dpy);
3ec526
        poll(fds, fdcnt, dt);
96c481
      }
96c481
    }
96c481
    
3ec526
    if (!hasEvent && fds[0].revents && XPending(app->dpy)) {
dbe1a1
      XNextEvent(app->dpy, &event);
dbe1a1
      hasEvent = 1;
452870
    }
5b6cfc
    
dbe1a1
    if (hasEvent) {
7e0df9
      // handle X11 events
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);
51e530
          appUpdateStrut(app);
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
        }
51e530
        appUpdateStrut(app);
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:
13b128
        if (app->xron && event.type == app->xrev + RRScreenChangeNotify && XRRUpdateConfiguration(&event)) {
e3897e
          LOGDBG("app: screen change event");
13b128
          Rotation r = 0;
e3897e
          XRRScreenChangeNotifyEvent *ev = (XRRScreenChangeNotifyEvent*)&event;
13b128
          XRRRotations(app->dpy, app->screen, &r); // seems rotation in event is wrong
13b128
          appUpdateScreenSize(app, ev->width, ev->height, r);
e3897e
        }
e3897e
        break;
5b6cfc
      }
5b6cfc
    }
5b6cfc
    
fd85b0
    if (app->touch.app && fds[1].revents) {
3ec526
      // handle direct touch events
3ec526
      int x, y, p;
3ec526
      while(touchGet(&app->touch, &x, &y, &p)) {
3ec526
        if (p && !buttonDown) {
3ec526
          LOGDBG("app: touch pressed: x=%d, y=%d", x, y);
3ec526
          buttonDown = 1; buttonDownMs = monotonicMs();
3ec526
          keyboardMouseDown(&app->keyboard, x, y);
3ec526
          hasEvent = 1;
3ec526
        } else
3ec526
        if (!p && buttonDown) {
3ec526
          LOGDBG("app: touch released");
3ec526
          buttonDown = 0;
3ec526
          keyboardMouseUp(&app->keyboard);
3ec526
          hasEvent = 1;
3ec526
        } else
3ec526
        if (p) {
3ec526
          LOGDBG("app: touch motion: x=%d, y=%d", x, y);
3ec526
          keyboardMouseMotion(&app->keyboard, x, y);
3ec526
          hasEvent = 1;
3ec526
        }
3ec526
      }
3ec526
    }
3ec526
    
5b6cfc
    if (buttonDown == 1 && monotonicMs() - buttonDownMs >= LONGPRESS_MS) {
5b6cfc
        LOGDBG("app: long press");
5b6cfc
        buttonDown = 2;
5b6cfc
        keyboardMouseLongDown(&app->keyboard);
3ec526
        hasEvent = 1;
452870
    }
452870
    
843e7a
    if (app->run != 1) break;
3ec526
    if (!hasEvent) continue;
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
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
c456ae
void appUpdateStrut(App *app) {
c456ae
  #ifdef DOCK
51e530
  long v[12] = { 0, 0, 0, app->h, 0, app->sh, 0, app->sh, 0, app->sw, 0, app->sw };
51e530
51e530
  #ifdef TOP
51e530
  v[2] = app->h;
51e530
  #else
51e530
  v[3] = app->h;
c456ae
  #endif
c456ae
c456ae
  if (app->dockt == v[2] && app->dockb == v[3])
c456ae
    return;
c456ae
  app->dockt = v[2];
c456ae
  app->dockb = v[3];
c456ae
51e530
  LOGDBG("app: update strut: %lu %lu %lu %lu", v[0], v[1], v[2], v[3]);
7e0df9
c456ae
  Atom k = XInternAtom(app->dpy, "_NET_WM_STRUT", False);
51e530
  XChangeProperty(app->dpy, app->win, k, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)v, 4);
c456ae
51e530
  Atom kp = XInternAtom(app->dpy, "_NET_WM_STRUT_PARTIAL", False);
51e530
  XChangeProperty(app->dpy, app->win, kp, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)v, 12);
51e530
51e530
  #ifdef TOP
51e530
  appMove(app, 0, 0, app->sw, app->h, 1);
51e530
  #else
51e530
  appMove(app, 0, app->sh - app->h, app->sw, app->h, 1);
51e530
  #endif
51e530
  #endif // DOCK
c456ae
}
c456ae
c456ae
7e0df9
void appUpdateScreenSize(App *app, int sw, int sh, Rotation sr) {
e3897e
  if (sw <= 0 || sh <= 0) return;
c456ae
7e0df9
  if (sw == app->sw && sh == app->sh && sr == app->sr) return;
13b128
  LOGDBG("app: update screen size: w=%d, h=%d, r=%02x", sw, sh, sr);
7e0df9
7e0df9
  app->sr = sr;
e3897e
  if (sw == app->sw && sh == app->sh) return;
cbe4b8
460d1a
  #if defined(DOCK) || (defined(LOCK_SIZE) && defined(NOBORDER) && defined(NOTITLE))
56756f
  // static size case
56756f
  int w = sw * WIDTH_SCALE;
56756f
  int h = sh * HEIGHT_SCALE;
56756f
  #if defined(WIDTH_SCALE_P) && defined(HEIGHT_SCALE_P)
56756f
  if (sw < sh) {
56756f
    w = sw * WIDTH_SCALE_P;
56756f
    h = sh * HEIGHT_SCALE_P;
56756f
  }
56756f
  #endif
56756f
56756f
  int x0 = 0;
56756f
  #ifdef TOP
56756f
  int y0 = 0;
56756f
  #else
56756f
  int y0 = sh - h;
56756f
  #endif
56756f
56756f
  #else
56756f
  // dynamic size case
cbe4b8
  double kx = sw/(double)app->sw;
cbe4b8
  double ky = sh/(double)app->sh;
cbe4b8
cbe4b8
  #if defined(WIDTH_SCALE_P) && defined(HEIGHT_SCALE_P)
cbe4b8
  // rescale for portrait/landscape switching
cbe4b8
  if (app->sw < app->sh && sw >= sh) {
cbe4b8
    // portrait -> landscape
cbe4b8
    kx *= ((double)WIDTH_SCALE)/((double)WIDTH_SCALE_P);
cbe4b8
    ky *= ((double)HEIGHT_SCALE)/((double)HEIGHT_SCALE_P);
cbe4b8
  } else
cbe4b8
  if (app->sw >= app->sh && sw < sh) {
cbe4b8
    // landscape -> portrait
cbe4b8
    kx *= ((double)WIDTH_SCALE_P)/((double)WIDTH_SCALE);
cbe4b8
    ky *= ((double)HEIGHT_SCALE_P)/((double)HEIGHT_SCALE);
cbe4b8
  }
cbe4b8
  #endif
cbe4b8
cbe4b8
  // scale window corners
cbe4b8
  int x0 = (int)round( app->x*kx );
cbe4b8
  int y0 = (int)round( app->y*ky );
cbe4b8
  int x1 = (int)round( (app->x + app->w)*kx );
cbe4b8
  int y1 = (int)round( (app->y + app->h)*ky );
56756f
  int w = x1 - x0;
56756f
  int h = y1 - y0;
56756f
  #endif
cbe4b8
e3897e
  app->sw = sw;
e3897e
  app->sh = sh;
51e530
  appMove(app, x0, y0, w, h, 0);
e3897e
}
e3897e
e3897e
51e530
void appMove(App *app, int x, int y, int w, int h, int force) {
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 = {};
51e530
  if (force || app->x != x) ch.x = x, mask |= CWX;
51e530
  if (force || app->y != y) ch.y = y, mask |= CWY;
51e530
  if (force || app->w != w) ch.width  = w, mask |= CWWidth;
51e530
  if (force || app->h != h) ch.height = h, mask |= CWHeight;
dbe1a1
  if (!mask) return;
dbe1a1
  
51e530
  LOGDBG("app: move: x=%d, y=%d, w=%d, h=%d", x, y, w, h);
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