| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| int appInit(App *app) { |
| 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); |
| LOGDBG("app: init: screen=%d sw=%d sh=%d", app->screen, app->sw, app->sh); |
| |
| 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; |
| app->y = app->sh - app->h; |
| |
| XSetWindowAttributes attr = {}; |
| |
| attr.override_redirect = 1; |
| |
| |
| app->win = XCreateWindow( |
| app->dpy, app->root, |
| app->x, app->y, app->w, app->h, 0, |
| CopyFromParent, CopyFromParent, CopyFromParent, |
| CWOverrideRedirect, &attr); |
| |
| XSelectInput( |
| app->dpy, app->win, |
| StructureNotifyMask |
| | ExposureMask |
| | ButtonPressMask |
| | ButtonReleaseMask |
| | Button1MotionMask ); |
| |
| 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); |
| } |
| |
| Atom wt = XInternAtom(app->dpy, "_NET_WM_WINDOW_TYPE", False); |
| |
| Atom wtval = XInternAtom(app->dpy, "_NET_WM_WINDOW_TYPE_TOOLBAR", False); |
| if (wt != None && wtval != None) |
| XChangeProperty(app->dpy, app->win, wt, XA_ATOM, 32, PropModeReplace, (unsigned char*)&wtval, 1); |
| |
| |
| |
| LOGDBG("app: init: set window minimum size"); |
| XSizeHints *wsh = XAllocSizeHints(); |
| if (!wsh) { |
| LOGWRN("app: init: XAllocSizeHints error"); |
| } else { |
| |
| wsh->flags = PMinSize | PMaxSize; |
| wsh->min_width = wsh->max_width = app->w; |
| wsh->min_height = wsh->max_height = app->h; |
| |
| wsh->flags = PMinSize; |
| wsh->min_width = MIN_WIDTH; |
| wsh->min_height = MIN_HEIGHT; |
| |
| XSetSizeHints(app->dpy, app->win, wsh, XA_WM_NORMAL_HINTS); |
| XFree(wsh); |
| } |
| |
| |
| 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); |
| } |
| |
| |
| app->aWmDel = XInternAtom(app->dpy, "WM_DELETE_WINDOW", False); |
| XSetWMProtocols(app->dpy, app->win, &app->aWmDel, 1); |
| |
| |
| XkbSelectEvents(app->dpy, XkbUseCoreKbd, XkbMapNotifyMask | XkbStateNotifyMask, XkbMapNotifyMask | XkbStateNotifyMask); |
| |
| |
| 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); |
| |
| 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; |
| fd_set fds = {}; |
| int maxFd = fd > app->stopFd ? fd : app->stopFd; |
| XFlush(app->dpy); |
| while(1) { |
| int hasEvent = 0; |
| XEvent event = {}; |
| |
| FD_ZERO(&fds); |
| FD_SET(fd, &fds); |
| FD_SET(app->stopFd, &fds); |
| |
| |
| if (XPending(app->dpy)) { |
| XNextEvent(app->dpy, &event); |
| hasEvent = 1; |
| } else |
| if (buttonDown != 1) { |
| |
| select(maxFd + 1, &fds, NULL, NULL, NULL); |
| } else { |
| |
| unsigned int dt = monotonicMs() - buttonDownMs; |
| if (dt < LONGPRESS_MS) { |
| dt = LONGPRESS_MS - dt; |
| struct timeval t = {}; |
| t.tv_sec = dt/1000; |
| t.tv_usec = dt%1000*1000; |
| |
| LOGDBG("app: run: use select fd=%d, timeout=%ums, sec=%ld, usec=%ld", fd, dt, t.tv_sec, t.tv_usec); |
| |
| XFlush(app->dpy); |
| select(maxFd + 1, &fds, NULL, NULL, &t); |
| } |
| } |
| |
| if (!hasEvent && XPending(app->dpy)) { |
| XNextEvent(app->dpy, &event); |
| hasEvent = 1; |
| } |
| |
| if (hasEvent) { |
| 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); |
| } |
| 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); |
| } |
| 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: |
| break; |
| } |
| } |
| |
| if (buttonDown == 1 && monotonicMs() - buttonDownMs >= LONGPRESS_MS) { |
| LOGDBG("app: long press"); |
| buttonDown = 2; |
| keyboardMouseLongDown(&app->keyboard); |
| } |
| |
| if (app->run != 1) break; |
| |
| 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 appStopBySignal(App *app) { |
| char x = 0; |
| write(app->stopFd, &x, 1); |
| } |
| |
| |
| 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 appMove(App *app, int x, int y, int w, int h) { |
| LOGDBG("app: move: x=%d, y=%d, w=%d, h=%d", x, y, w, h); |
| if (w < MIN_WIDTH) w = MIN_WIDTH; |
| if (h < MIN_HEIGHT) h = MIN_HEIGHT; |
| |
| unsigned int mask = 0; |
| XWindowChanges ch = {}; |
| if (app->x != x) ch.x = x, mask |= CWX; |
| if (app->y != y) ch.y = y, mask |= CWY; |
| if (app->w != w) ch.width = w, mask |= CWWidth; |
| if (app->h != h) ch.height = h, mask |= CWHeight; |
| if (!mask) return; |
| |
| 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); |
| } |
| |