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>
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;
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
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
  #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
c456ae
  #if !defined(NOBORDER) && defined(DOCK)
c456ae
  LOGDBG("app: init: set window as dock");
c456ae
  Atom awt = XInternAtom(app->dpy, "_NET_WM_WINDOW_TYPE", False);
c456ae
  Atom awtv = XInternAtom(app->dpy, "_NET_WM_WINDOW_TYPE_TOOLBAR", False);
c456ae
  if (awt != None && awtv != None)
c456ae
    XChangeProperty(app->dpy, app->win, awt, XA_ATOM, 32, PropModeReplace, (unsigned char*)&awtv, 1);
dbe1a1
  #endif
dbe1a1
c456ae
  Atom ahps = XInternAtom(app->dpy, "_HILDON_PORTRAIT_MODE_SUPPORT", False);
c456ae
  if (ahps) {
c456ae
    LOGDBG("app: init: set portrait mode support for hildon");
c456ae
    unsigned int 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
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);
c456ae
  appUpdateStrut(app);
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;
c456ae
          appUpdateStrut(app);
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
c456ae
void appUpdateStrut(App *app) {
c456ae
  unsigned int v[4] = {};
c456ae
c456ae
  #ifdef DOCK
c456ae
  if (app->y == 0) v[2] = app->h; else
c456ae
    if (app->y + app->h == app->sh) 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
c456ae
  Atom k = XInternAtom(app->dpy, "_NET_WM_STRUT", False);
c456ae
  if (!k) return;
c456ae
c456ae
  LOGDBG("app: update strut: %u %u %u %u", v[0], v[1], v[2], v[3]);
c456ae
  XChangeProperty(app->dpy, app->win, k, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)v, 4);
c456ae
}
c456ae
c456ae
e3897e
void appUpdateScreenSize(App *app, int sw, int sh) {
e3897e
  if (sw <= 0 || sh <= 0) return;
c456ae
e3897e
  if (sw == app->sw && sh == app->sh) return;
e3897e
  LOGDBG("app: update screen size: w=%d, h=%d", sw, sh);
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;
56756f
  appMove(app, x0, y0, w, h);
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