Blame app.c

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>
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;
dbe1a1
  app->y = app->sh - app->h;
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
  }
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
  
8864eb
  
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;
5b6cfc
  while(1) {
dbe1a1
    int hasEvent = 0;
dbe1a1
    XEvent event = {};
5b6cfc
    
dbe1a1
    // wait for next event    
5b6cfc
    if (buttonDown == 1) {
dbe1a1
      if (XPending(app->dpy)) {
dbe1a1
        XNextEvent(app->dpy, &event);
dbe1a1
        hasEvent = 1;
dbe1a1
      } else {
5b6cfc
        // manually call select for the X-socket to use timeout
5b6cfc
        unsigned int dt = monotonicMs() - buttonDownMs;
5b6cfc
        if (dt < LONGPRESS_MS) {
5b6cfc
          dt = LONGPRESS_MS - dt;
5b6cfc
          struct timeval t = {};
5b6cfc
          t.tv_sec = dt/1000;
5b6cfc
          t.tv_usec = dt%1000*1000;
5b6cfc
 
5b6cfc
          LOGDBG("app: run: use select fd=%d, timeout=%ums, sec=%ld, usec=%ld", fd, dt, t.tv_sec, t.tv_usec);
5b6cfc
5b6cfc
          fd_set fds = {};
5b6cfc
          FD_ZERO(&fds);
5b6cfc
          FD_SET(fd, &fds);
dbe1a1
          XFlush(app->dpy);
5b6cfc
          select(fd + 1, &fds, NULL, NULL, &t);
5b6cfc
          
dbe1a1
          if (XPending(app->dpy)) {
dbe1a1
            XNextEvent(app->dpy, &event);
dbe1a1
            hasEvent = 1;
dbe1a1
          }
5b6cfc
        }
452870
      }
5b6cfc
    } else {
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
      }
5b6cfc
      default: 
5b6cfc
          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
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
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