wm/vend/xgbutil/mousebind/doc.go

157 lines
7.0 KiB
Go
Raw Normal View History

2023-06-11 14:21:08 +00:00
/*
Package mousebind provides an easy to use interface to assign callback functions
to human readable button sequences.
Namely, the mousebind package exports two function types: ButtonPressFun and
ButtonReleaseFun. Values of these types are functions, and have a method called
'Connect' that attaches an event handler to be run when a particular button
press is issued.
This is virtually identical to the way calbacks are attached using the xevent
package, but the Connect method in the mousebind package has a couple extra
parameters that are specific to mouse bindings. Namely, the button sequence to
respond to (which is a combination of zero or more modifiers and exactly one
button), whether to establish a passive grab and whether to make the grab
synchronous or not. One can still attach callbacks to Button{Press,Release}
events using xevent, but it will be run for *all* Button{Press,Release} events.
(This is typically what one might do when setting up an active grab.)
Initialization
Before using the mousebind package, you should *always* make a single call to
mousebind.Initialize for each X connection you're working with.
Button sequence format
Button sequences are human readable strings made up of zero or more modifiers
and exactly one button. Namely:
[Mod[-Mod[...]]-]BUTTONNUMBER
Where 'Mod' can be one of: shift, lock, control, mod1, mod2, mod3, mod4, mod5,
button1, button2, button3, button4, button5 or any. You can view which keys
activate each modifier using the 'xmodmap' program. (If you don't have
'xmodmap', you could also run the 'xmodmap' example in the examples directory.)
The 'button[1-5]' modifiers correspond to the button number in the name. (This
implies that buttons 1 through 5 can act as both a button number and a
modifier.)
BUTTONNUMER must correspond to a valid button number on your mouse. The best
way to determine the numbers of each button on your mouse is to launch the xev
program in a terminal, click the corresponding button in the new window that
opens, and read the event output in the terminal that launched xev. Usually a
left click is button 1, a right click is button 3 and a middle click is button
2.
An example button sequence might look like 'Mod4-Control-Shift-1'. The
mouse binding for that button sequence is activated when all three
modifiers---mod4, control and shift---are pressed along with the '1' button on
your mouse.
When to issue a passive grab
One of the parameters of the 'Connect' method is whether to issue a passive
grab or not. A passive grab is useful when you need to respond to a button press
on some parent window (like the root window) without actually focusing that
window. Not using a passive grab is useful when you only need to read button
presses when the window is focused.
For more information on the semantics of passive grabs, please see
http://tronche.com/gui/x/xlib/input/XGrabButton.html.
Also, by default, when issuing a grab on a particular (modifiers, button)
tuple, several grabs are actually made. In particular, for each grab requested,
another grab is made with the "num lock" mask, another grab is made with the
"caps lock" mask, and another grab is made with both the "num lock" and "caps
locks" masks. This allows button events to be reported regardless of whether
caps lock or num lock is enabled.
The extra masks added can be modified by changing the xevent.IgnoreMods slice.
If you modify xevent.IgnoreMods, it should be modified once on program startup
(i.e., before any key or mouse bindings are established) and never modified
again.
When to use a synchronous binding
In the vast majority of cases, 'sync' in the 'Connect' method should be set to
false, which indicates that a passive grab should be asynchronous. (The value
of sync is irrelevant if 'grab' is false.) This implies that any events
generated by the grab are sent to the grabbing window (the second parameter of
the 'Connect' method) and only the grabbing window.
Sometimes, however, you might want button events to cascade down the window
tree. That is, a button press on a parent window is grabbed, but then that
button press should be sent to any children windows. With an asynchronous grab,
this is impossible. With a synchronous grab, however, the button event can be
'replayed' to all child windows. For example:
mousebind.Initialize(XUtilValue) // call once before using mousebind package
mousebind.ButtonPressFun(
func(X *xgbutil.XUtil, ev xevent.ButtonPressEvent) {
// do something when button is pressed
// And now replay the pointer event that fired this handler to all
// child windows. All event processing is stopped on the
// X server until this is called.
xproto.AllowEvents(X.Conn(), xproto.AllowReplayPointer, 0)
}).Connect(XUtilValue, some-window-id,
"Mod4-Control-Shift-1", true, true)
This sort of example is precisely how reparenting window managers allow one to
click on a window and have it be activated or "raised" *and* have the button
press sent to the client window as well.
Note that with a synchronous grab, all event processing will be halted by the X
server until *some* call to xproto.AllowEvents is made.
Mouse bindings on the root window example
To run a particular function whenever the 'Mod4-Control-Shift-1' button
combination is pressed (mod4 is typically the 'super' or 'windows' key, but can
vary based on your system), use something like:
mousebind.Initialize(XUtilValue) // call once before using mousebind package
mousebind.ButtonPressFun(
func(X *xgbutil.XUtil, ev xevent.ButtonPressEvent) {
// do something when button is pressed
}).Connect(XUtilValue, XUtilValue.RootWin(),
"Mod4-Control-Shift-1", false, true)
Note that we issue a passive grab because Button{Press,Release} events on the
root window will only be reported when the root window has focus if no grab
exists.
Mouse bindings on a window you create example
This code snippet attaches an event handler to some window you've created
without using a grab. Thus, the function will only be activated when the button
sequence is pressed and your window has focus.
mousebind.Initialize(XUtilValue) // call once before using mousebind package
mousebind.ButtonPressFun(
func(X *xgbutil.XUtil, ev xevent.ButtonPressEvent) {
// do something when button is pressed
}).Connect(XUtilValue, your-window-id, "Mod4-t", false, false)
Run a function on all button press events example
This code snippet actually does *not* use the mousebind package, but illustrates
how the Button{Press,Release} event handlers in the xevent package can still be
useful. Namely, the mousebind package discriminates among events depending upon
the button sequences pressed, whereas the xevent package is more general: it
can only discriminate at the event level.
xevent.ButtonPressFun(
func(X *xgbutil.XUtil, ev xevent.ButtonPressEvent) {
// do something when any button is pressed
}).Connect(XUtilValue, your-window-id)
This is the kind of handler you might use to capture all button press events.
More examples
A complete working example using the mousebind package can be found in
'simple-mousebinding' in the examples directory of the xgbutil package.
*/
package mousebind