Event Rewrite Proposal for Scwm Greg J. Badros, Revised 21-Sep-1999, 19-Sep-1999 2-Sep-1998, 3-Sep-1998, 5-Sep-1998, 6-Aug-1998 Introduction: ------------- Motivating desires for a more flexible event-binding infrastructure o Elimination of "contexts" and replacing with event maps -- event maps are more fundamental, and unifying o Mode-specific event maps (e.g., when in an interactive move) o Pinnable-menus event maps o Multi-key bindings with prefix keys that change mode map o Maps on a per window basis (e.g., xlogo could permit fewer modifier keys to accompany an "m" keystroke to signify a desire to move the window) o Quoting mechanism for turning off scwm's handling of events o Unifying X-events and user events As you wrote, event maps and event objects should be exposed to the scheme layer as SMOBs (first class objects). The event maps will contain event objects which are bound to procedures to be invoked. Event maps will be attached to X windows (via Xlib's SaveContext mechanism) and scheme window objects (using their top-level frame window). E.g., an event map can be attached to a titlebar button window, or to the titlebar window, etc. Overview: --------- 1 - Event Objects 2 - Event Map Objects 3 - Dispatch of Events 4 - Binding of Event Maps 1 - Event objects: ------------------ (make-key-event KEYSYM-NAME #&optional MODIFIER-SPECIFIER) (key-event? OBJECT) e.g.: (make-key-event "F1") (make-key-event "Return" '(control shift meta)) KEYSYM-NAME is the name of a keysym (e.g., "x", "BackSpace", etc.). It cannot have prefixed modifiers -- a convenience function can provide conversions from (e.g., "C-S-M-Return" to the 2nd example above). [[ The special pseudo-keysyms "KeyPress" and "KeyRelease" permit events being bound to some combination of modifier keys just being depressed or released. (These may not be necessary thanks to make-x-event)]] MODIFIER-SPECIFIER is a list of symbolic modifiers. If the first element of the list is the pseudo-modifier 'exactly, then exactly the list of modifiers that follows must be depressed for the event to be matched. Modifiers include: 'control 'shift 'meta 'super 'alt 'hyper 'numlock 'capslock 'mod[1-8] '~control '~shift '~meta '~super '~alt '~hyper '~numlock '~capslock '~mod[1-8] '*control '*shift '*meta '*super '*alt '*hyper '*numlock '*capslock '*mod[1-8] The "~"-prefixes mean *not* that modifier, the "*"-prefixes mean don't care either way for that modifier (only useful in conjunction with the pseudo-modifier 'exactly. e.g., '(exactly control shift meta) is the same as '(control shift meta ~super ~alt ~hyper ~numlock ~capslock) and '(exactly control shift meta *numlock) is the same as '(control shift meta ~super ~alt ~hyper ~capslock) Another first-element pseudo-modifier is 'with, which is the same as 'exactly, except for an implicit *numlock *capslock. 'iwith is the same as 'with except for adding an implicit *shift, as well. Listing of 'with, 'iwith, or 'exactly anywhere besides the first position is an error. Listing the same modifier twice (or once when it is also implicit due to a pseudo-modifier) is also an error. If a modifier is listed and there is no key bound to that modifier, the binding will be kept but it will be inactive until that modifier is made available via, e.g., xmodmap. Mixing mod[1-8] with any of the symbolic names in the same specification is also an error. These get converted internally to on and off bitmasks such that the event matches iff: ((MOD | OnMask == MOD) && (MOD & ~OffMask == MOD)). Rebinding of modifiers will recompute the bitmasks. (Thanks to Robert Bihlmeyer who made useful comments regarding this subsection.) (make-mouse-event BUTTON-SPECIFIER X-EVENT-SPECIFIER #&optional MODIFIER-SPECIFIER) (mouse-event? OBJECT) e.g., (make-mouse-event 1 'release) (make-mouse-event 3 'double-click '(with control meta shift)) BUTTON-SPECIFIER is the physical number of the button (a level of indirection could easily be provided by variables named e.g., select drag and menu, corresponding to buttons 1 2 and 3). Button number 0 matches any/all buttons. Button -1 matches no button. Chorded buttons are not specifiable directly -- the attached proc will have to check the event if they are desired (their complexity for user interaction is worth discouraging). X-EVENT-SPECIFIER is one of 'motion, 'press, 'release, 'click, 'single-click or 'double-click. ('click, 'single-click and 'double-click get manufactured by scwm -- a 'click is a press followed by a release w/o intervening motion (of more than a threshold delta). A 'double-click is two clicks w/o intervening motion or too much intervening time, and a 'single-click is a 'click that is not followed by a second click; note that a 'click will always precede a 'double-click, and the second click of a 'double-click will not generate another 'click. A 'single-click is generated after the double-click timeout after a 'click). MODIFIER-SPECIFIER is as described above. (make-x-event X-EVENT-SPECIFIER #&optional MODIFIER-SPECIFIER) (x-event? OBJECT) e.g., (make-x-event 'enter-notify) (make-x-event 'keypress '(with control shift meta)) (make-x-event 'pointer-motion '(with hyper)) X-EVENT-SPECIFIER is a symbol referring to an X11 event. keypress, keyrelease, enter-notify, leave-notify, pointer-motion, property-notify, colormap-notify, focus-in, focus-out, etc. could be supported (see include/X11/X.h for list). Binding procedures to these events could replace some of the ad-hoc mechanisms for property-notification, etc., that we currently have in scwm. Though some of these events are currently handled by global hooks, treating them as proper events permits different behaviours to be specified for different windows instead of requiring that the hook procedures be more globally aware (or have lots of hook procedures invoked). This is an important efficiency optimization. MODIFIER-SPECIFIER is as described above. It is included mostly for completeness and orthogonality. 'keypress events may test for modifiers using the current state of the modifiers, not the state under which the key was pressed. E.g., if Ctrl-Shift-Meta is depressed, whichever of the three modifier keys that is physically recognized last will not be in the modifier bitmask for that keypress, but we might like to bind something (e.g., a help menu) to those three keys being held down. Note that make-x-event can be conceptually though of as the most primitive case, and the other two procedures can be seen as adding a C-level filter and event grabbing capabilities on top of the actual X-event. E.g., (make-mouse-event 1 'release) is applicable if there is a x-event "ButtonRelease" and the button number is 1. X-event bindings will never do any grabs implicitly-- key-events need to be used for that (this is why the other functions are necessary as primitives). 2 - Event Map Objects: ---------------------- Now, on to event map objects. My proposal is similar to yours but instead of managing the installed event-map from Scheme code, I want to attach event map objects to arbitrary X11 windows (e.g., the pager, or a pinned menu, etc.), and have the appropriate grabs and XSelectInput calls happen based on the event map for a given window. First, a means of creating event-maps and populating them with bindings from events to procedures: (make-event-map #&optional PARENT) (event-map? OBJECT) ;; PARENT is an event-map object, or omitted or #f to indicate no parent This will permit a form of inheritance of behaviours. Note, though, that this mechanism should not be necessary for the geometry-based chaining that X11 commonly uses. E.g., an event in a window decoration (say, a button on the titlebar) will first dispatch based on the event map (if any) attached to the decoration, then on the event map for that top-level window, then on the global event map. Each of these event maps would be built with the PARENT argument omitted or #f. A special primitive or symbol can be bound to an event to permit preventing the event from propagating, and instead letting it pass to the application. event-map-global ;; the built-in global event-map object -- it is an implicit ;; parent of all event-maps (event-map-parents EVENT-MAP) ;; Return list of parent event map objects (add-event-map-parent! EVENT-MAP PARENT) ;; Permit multiple parents, though only one can be specified at a time (remove-event-map-parent! EVENT-MAP PARENT) ;; return #t if successful, #f if parent not found (add-event-binding! EVENT-MAP EVENT PROC-OR-SYMBOL) (remove-event-binding! EVENT-MAP EVENT) ;; Similar to your {add,remove}-event-hook. ;; These add and remove bindings for EVENT objects in the given ;; EVENT-MAP object. ;; PROC-OR-SYMBOL is either a procedure or 'pass to indicate ;; that the event should not be grabbed by the wm and the application ;; should get the event (this is only an issue for event maps attached ;; to client windows -- it's a means of overriding and eliminating ;; a parents event map's grab). See dispatch-event for details about how ;; the bindings are invoked. ;; In add-event-binding, using an event object `equal?' to one with a ;; pre-existing binding will replace the old binding (list-event-bindings EVENT-MAP) ;; return a list of all the event bindings added to EVENT-MAP ;; as a cons parent of (EVENT-OBJECT . PROC-OR-SYMOBL) 3 - Dispatch of Events ---------------------- (dispatch-event EVENT-MAP EVENT) ;; EVENT-MAP is an event-map object, EVENT is an event-object ;; All applicable events' procedures should be invoked in the order that ;; they were added to the EVENT-MAP. Parent EVENT-MAPs are handled ;; as discussed above. Returns the value answered by the invoked procedure. ;; We could consider looking at CLOS method combination for more ;; sophisticated dispatching combination techniques, but that's for later. ;; Each procedure is invoked with *no* arguments. Obviously there is ;; information that the bound procedures will need to receive, but having ;; those objects be arguments would necessitate changing the argument ;; list to all procedures that are intended to be bound to an action. ;; Instead, the event-specific information will be available to the ;; dispatched procedure via the thread-local variable `last-event'. That ;; variable should be accessed via the following accessors. ;; E.g., the event-target can be accessed via the expression `(event-target last-event)' ;; The `last-event' variable will be #f if the procedure is not invoked ;; from an event handler. ;; The names of the accessors of `last-event' are: ;; event-target - the scheme object that the event acted on/in (e.g., ;; for a button decoration, it will be that window object; for a menu ;; it will be the menu object, etc.), for the root window it will be ;; 'root-window, or #f if there is no applicable object. ;; Note that `window-context' is largely replaced by ;; `(event-target last-event)' ;; event-window-id - the window id of the X window that got the event. ;; the `window-decoration-ids' primitive can be helpful in mapping this ;; back to what the decoration was. ;; event-timestamp - the time of the event, as reported by the X server ;; in X time format (which wraps reasonably frequently) ;; event-time - the time of the event, as a time_t value (seconds since 1/1/70) ;; event-info - a list of event-specific information or #f; e.g., ;; a property-notify event may use ;; '("WM_TITLE" 'NewValue) ;; property name changed and either 'NewValue or 'Delete ;; for this argument ;; event-map - the event map object (i.e. EVENT-MAP) ;; We will do something to help simplify binding these options ;; to local variables in procedures intended to be action procedure ;; (maybe something like Emacs's (interactive) declaration). ;; Only when no bindings are applicable in the child or its chain of ;; parents should the event dispatching mechanism proceed to chain the ;; event dispatch up to the enclosing event-map context. That is, from ;; decoration->frame->global (button decorations perhaps should forward ;; to the titlebar before the frame, but side-bar decorations probably ;; should not). When the decoration rewrite happens, a decoration ;; module should be able to specify the event processing fallback path. ;; This `dispatch-event' primitive is what will get internally invoked ;; when Scwm gets an event. Note that Scwm will have to manage ;; selecting the appropriate events and grabbing the appropriate keys ;; based on the event-map objects attached to various windows. (Thanks to Robert Bihlmeyer who made useful comments regarding this subsection.) 4 - Binding of Event Maps: -------------------------- Event maps can be attached to any X Window by its X Id or the Scheme window object. (attach-event-map X-WINDOW-ID-OR-WIN EVENT-MAP) e.g., (attach-event-map (button-n-of 1 win) button-1-map-for-win) (attach-event-map (title-bar-of win) title-bar-map-for-win) ;; Only one event map is attached per window id -- attaching ;; a new map removes any old one ;; (will need to add button-n-of, title-bar-of, but can do some ;; using `window-decoration-ids' The window style mechanism should be used to attach a set of event maps at startup for a given window style, e.g.: (window-style "*" #:event-maps (list (1 . default-button-1-map) ('title . default-title-map) ('window . default-window-map))) [this avoids a bunch of attach-event-maps from being needed in the new-window-hook -- the attaching of the default event maps needs to be efficient, since it'll happen for each new window and on lots of decorations -- it's only a single XSaveContext call for each attachment, which should be fine]. (event-map-for X-WINDOW-ID-OR-WIN) ;; Return the currently attach event-map object, if any, or #f (remove-event-map X-WINDOW-ID-OR-WIN) ;; detach any event map from X-WINDOW-ID OTHER ISSUES: Top-level global event map that's used first before any others? I'll try to come up with an example of using this system but please feel free to poke holes in it or query me about how one might accomplish something you're interested in being able to do. (Or if you think it's redundant, overly-general, inefficient, etc.). Thanks!