## handling events from the keyboard, mouse, touch screen, ... # temporary main: interactive editor # hit ctrl-c to exit def! main text:text [ local-scope load-inputs open-console clear-screen null/screen # non-scrolling app editor:&:editor <- new-editor text, 5/left, 45/right editor-render null/screen, editor editor-event-loop null/screen, null/console, editor close-console ] def editor-event-loop screen:&:screen, console:&:console, editor:&:editor -> screen:&:screen, console:&:console, editor:&:editor [ local-scope load-inputs { # looping over each (keyboard or touch) event as it occurs +next-event cursor-row:num <- get *editor, cursor-row:offset cursor-column:num <- get *editor, cursor-column:offset screen <- move-cursor screen, cursor-row, cursor-column e:event, found?:bool, quit?:bool, console <- read-event console loop-unless found? break-if quit? # only in tests trace 10, [app], [next-event] # 'touch' event t:touch-event, is-touch?:bool <- maybe-convert e, touch:variant { break-unless is-touch? move-cursor editor, screen, t loop +next-event } # keyboard events { break-if is-touch? go-render?:bool <- handle-keyboard-event screen, editor, e { break-unless go-render? screen <- editor-render screen, editor } } loop } ] # process click, return if it was on current editor def move-cursor editor:&:editor, screen:&:screen, t:touch-event -> in-focus?:bool, editor:&:editor [ local-scope load-inputs return-unless editor, false click-row:num <- get t, row:offset return-unless click-row, false # ignore clicks on 'menu' click-column:num <- get t, column:offset left:num <- get *editor, left:offset too-far-left?:bool <- lesser-than click-column, left return-if too-far-left?, false right:num <- get *editor, right:offset too-far-right?:bool <- greater-than click-column, right return-if too-far-right?, false # position cursor editor <- snap-cursor editor, screen, click-row, click-column undo-coalesce-tag:num <- copy 0/never # gain focus return true ] # Variant of 'render' that only moves the cursor (coordinates and # before-cursor). If it's past the end of a line, it 'slides' it left. If it's # past the last line it positions at end of last line. def snap-cursor editor:&:editor, screen:&:screen, target-row:num, target-column:num -> editor:&:editor [ local-scope load-inputs return-unless editor left:num <- get *editor, left:offset right:num <- get *editor, right:offset screen-height:num <- screen-height screen # count newlines until screen row curr:&:duplex-list:char <- get *editor, top-of-screen:offset prev:&:duplex-list:char <- copy curr # just in case curr becomes null and we can't compute prev curr <- next curr row:num <- copy 1/top column:num <- copy left *editor <- put *editor, cursor-row:offset, target-row cursor-row:num <- copy target-row *editor <- put *editor, cursor-column:offset, target-column cursor-column:num <- copy target-column before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset { +next-character break-unless curr off-screen?:bool <- greater-or-equal row, screen-height break-if off-screen? # update editor.before-cursor # Doing so at the start of each iteration ensures it stays one step behind # the current character. { at-cursor-row?:bool <- equal row, cursor-row break-unless at-cursor-row? at-cursor?:bool <- equal column, cursor-column break-unless at-cursor? before-cursor <- copy prev *editor <- put *editor, before-cursor:offset, before-cursor } c:char <- get *curr, value:offset { # newline? move to left rather than 0 newline?:bool <- equal c, 10/newline break-unless newline? # adjust cursor if necessary { at-cursor-row?:bool <- equal row, cursor-row break-unless at-cursor-row? left-of-cursor?:bool <- lesser-than column, cursor-column break-unless left-of-cursor? cursor-column <- copy column *editor <- put *editor, cursor-column:offset, cursor-column before-cursor <- copy prev *editor <- put *editor, before-cursor:offset, before-cursor } # skip to next line row <- add row, 1 column <- copy left curr <- next curr prev <- next prev loop +next-character } { # at right? wrap. even if there's only one more letter left; we need # room for clicking on the cursor after it. at-right?:bool <- equal column, right break-unless at-right? column <- copy left row <- add row, 1 # don't increment curr/prev loop +next-character } curr <- next curr prev <- next prev column <- add column, 1 loop } # is cursor to the right of the last line? move to end { at-cursor-row?:bool <- equal row, cursor-row cursor-outside-line?:bool <- lesser-or-equal column, cursor-column before-cursor-on-same-line?:bool <- and at-cursor-row?, cursor-outside-line? above-cursor-row?:bool <- lesser-than row, cursor-row before-cursor?:bool <- or before-cursor-on-same-line?, above-cursor-row? break-unless before-cursor? cursor-row <- copy row *editor <- put *editor, cursor-row:offset, cursor-row cursor-column <- copy column *editor <- put *editor, cursor-column:offset, cursor-column before-cursor <- copy prev *editor <- put *editor, before-cursor:offset, before-cursor } ] # Process an event 'e' and try to minimally update the screen. # Set 'go-render?' to true to indicate the caller must perform a non-minimal update. def handle-keyboard-event screen:&:screen, editor:&:editor, e:event -> go-render?:bool, screen:&:screen, editor:&:editor [ local-scope load-inputs return-unless editor, false/don't-render screen-width:num <- screen-width screen screen-height:num <- screen-height screen left:num <- get *editor, left:offset right:num <- get *editor, right:offset before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset cursor-row:num <- get *editor, cursor-row:offset cursor-column:num <- get *editor, cursor-column:offset save-row:num <- copy cursor-row save-column:num <- copy cursor-column # character { c:char, is-unicode?:bool <- maybe-convert e, text:variant break-unless is-unicode? trace 10, [app], [handle-keyboard-event: special character] # exceptions for special characters go here # ignore any other special characters regular-character?:bool <- greater-or-equal c, 32/space return-unless regular-character?, false/don't-render # otherwise type it in go-render? <- insert-at-cursor editor, c, screen return } # special key to modify the text or move the cursor k:num, is-keycode?:bool <- maybe-convert e:event, keycode:variant assert is-keycode?, [event was of unknown type; neither keyboard nor mouse] # handlers for each special key will go here return true/go-render ] def insert-at-cursor editor:&:editor, c:char, screen:&:screen -> go-render?:bool, editor:&:editor, screen:&:screen [ local-scope load-inputs before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset insert c, before-cursor before-cursor <- next before-cursor *editor <- put *editor, before-cursor:offset, before-cursor cursor-row:num <- get *editor, cursor-row:offset cursor-column:num <- get *editor, cursor-column:offset left:num <- get *editor, left:offset right:num <- get *editor, right:offset save-row:num <- copy cursor-row save-column:num <- copy cursor-column screen-width:num <- screen-width screen screen-height:num <- screen-height screen # occasionally we'll need to mess with the cursor # but mostly we'll just move the cursor right cursor-column <- add cursor-column, 1 *editor <- put *editor, cursor-column:offset, cursor-column next:&:duplex-list:char <- next before-cursor { # at end of all text? no need to scroll? just print the character and leave at-end?:bool <- equal next, null break-unless at-end? bottom:num <- subtract screen-height, 1 at-bottom?:bool <- equal save-row, bottom at-right?:bool <- equal save-column, right overflow?:bool <- and at-bottom?, at-right? break-if overflow? move-cursor screen, save-row, save-column print screen, c return false/don't-render } { # not at right margin? print the character and rest of line break-unless next at-right?:bool <- greater-or-equal cursor-column, screen-width break-if at-right? curr:&:duplex-list:char <- copy before-cursor move-cursor screen, save-row, save-column curr-column:num <- copy save-column { # hit right margin? give up and let caller render at-right?:bool <- greater-than curr-column, right return-if at-right?, true/go-render break-unless curr # newline? done. currc:char <- get *curr, value:offset at-newline?:bool <- equal currc, 10/newline break-if at-newline? print screen, currc curr-column <- add curr-column, 1 curr <- next curr loop } return false/don't-render } return true/go-render ] # helper for tests def editor-render screen:&:screen, editor:&:editor -> screen:&:screen, editor:&:editor [ local-scope load-inputs old-top-idx:num <- save-top-idx screen left:num <- get *editor, left:offset right:num <- get *editor, right:offset row:num, column:num <- render screen, editor draw-horizontal screen, row, left, right, 9480/horizontal-dotted row <- add row, 1 clear-screen-from screen, row, left, left, right assert-no-scroll screen, old-top-idx ] scenario editor-handles-empty-event-queue [ local-scope assume-screen 10/width, 5/height e:&:editor
/*
 * (C)opyright MMVI Anselm R. Garbe <garbeam at gmail dot com>
 * See LICENSE file for license details.
 */

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/keysym.h>
#include <X11/Xatom.h>

#include "dwm.h"

/* local functions */
static void buttonpress(XEvent *e);
static void configurerequest(XEvent *e);
static void destroynotify(XEvent *e);
static void enternotify(XEvent *e);
static void leavenotify(XEvent *e);
static void expose(XEvent *e);
static void keymapnotify(XEvent *e);
static void maprequest(XEvent *e);
static void propertynotify(XEvent *e);
static void unmapnotify(XEvent *e);

void (*handler[LASTEvent]) (XEvent *) = {
	[ButtonPress] = buttonpress,
	[ConfigureRequest] = configurerequest,
	[DestroyNotify] = destroynotify,
	[EnterNotify] = enternotify,
	[LeaveNotify] = leavenotify,
	[Expose] = expose,
	[KeyPress] = keypress,
	[KeymapNotify] = keymapnotify,
	[MapRequest] = maprequest,
	[PropertyNotify] = propertynotify,
	[UnmapNotify] = unmapnotify
};

static void
buttonpress(XEvent *e)
{
	XButtonPressedEvent *ev = &e->xbutton;
	Client *c;

	if(barwin == ev->window)
		barclick(ev);
	else if((c = getclient(ev->window))) {
		craise(c);
		switch(ev->button) {
		default:
			break;
		case Button1:
			mmove(c);
			break;
		case Button2:
			lower(c);
			break;
		case Button3:
			mresize(c);
			break;
		}
	}
}

static void
configurerequest(XEvent *e)
{
	XConfigureRequestEvent *ev = &e->xconfigurerequest;
	XWindowChanges wc;
	Client *c;

	ev->value_mask &= ~CWSibling;
	if((c = getclient(ev->window))) {
		gravitate(c, True);
		if(ev->value_mask & CWX)
			c->x = ev->x;
		if(ev->value_mask & CWY)
			c->y = ev->y;
		if(ev->value_mask & CWWidth)
			c->w = ev->width;
		if(ev->value_mask & CWHeight)
			c->h = ev->height;
		if(ev->value_mask & CWBorderWidth)
			c->border = 1;
		gravitate(c, False);
		resize(c, True);
	}

	wc.x = ev->x;
	wc.y = ev->y;
	wc.