Compare commits

...

18 Commits

Author SHA1 Message Date
a
bbf9ee0ef1 ok 2023-06-15 11:38:39 -05:00
a
1ae56f5f21 doc 2023-06-15 11:24:53 -05:00
a
e8d65481ea ok 2023-06-15 11:15:58 -05:00
a
b31254837d update doc 2023-06-15 11:10:22 -05:00
a
921917d488 doc 2023-06-15 11:00:16 -05:00
a
a595d4980c sock 2023-06-15 10:57:08 -05:00
a
65162b1651 can now query monitors 2023-06-15 10:24:04 -05:00
a
9efd1c2290 capturing windows 2023-06-14 22:20:55 -05:00
a
e5f827aed4 okg 2023-06-11 09:56:54 -05:00
a
99f43d3408 monitor 2023-06-11 09:34:42 -05:00
a
e277164b2a helper 2023-06-11 09:26:55 -05:00
a
ba042cc537 update deps 2023-06-11 09:21:08 -05:00
a
9d961ec0ba lol 2023-06-11 09:19:01 -05:00
a
5e9454d225 update 2023-06-11 09:11:36 -05:00
a
b4da3d53fd ok 2023-06-11 05:24:15 -05:00
a
de99ca3429 fix 2022-12-25 23:23:12 -06:00
a
c3c0b1269b protocol 2022-12-25 23:06:40 -06:00
a
b8647ded0c Attempt 2022-12-25 20:27:23 -06:00
198 changed files with 93956 additions and 143 deletions

1
.env Executable file
View File

@ -0,0 +1 @@
export

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
/bspwm
/bspc
/tspwm
/tspc
*.log
*.sock

View File

@ -1,5 +1,68 @@
WINDOWSIZE=1024:768
WINDOWSIZE=1280x720
VERCMD ?= git describe --tags 2> /dev/null
VERSION := $(shell $(VERCMD) || cat VERSION)
PREFIX ?= /usr/local
BINPREFIX ?= $(PREFIX)/bin
MANPREFIX ?= $(PREFIX)/share/man
DOCPREFIX ?= $(PREFIX)/share/doc/tspwm
BASHCPL ?= $(PREFIX)/share/bash-completion/completions
FISHCPL ?= $(PREFIX)/share/fish/vendor_completions.d
ZSHCPL ?= $(PREFIX)/share/zsh/site-functions
MD_DOCS = README.md doc/CHANGELOG.md doc/CONTRIBUTING.md doc/INSTALL.md doc/MISC.md doc/TODO.md
XSESSIONS ?= $(PREFIX)/share/xsessions
all: tspwm tspc
VPATH=src
tspwm: cmd/bspwm src/**/*
go build -o tspwm ./cmd/bspwm
tspc: cmd/bspc src/**/*
go build -o tspc ./cmd/bspc
xephyr:
Xephyr -br ac -noreset ${WINDOWSIZE} :1
Xephyr :11 -br -ac -noreset -screen ${WINDOWSIZE}
install:
mkdir -p "$(DESTDIR)$(BINPREFIX)"
cp -pf tspwm "$(DESTDIR)$(BINPREFIX)"
cp -pf tspc "$(DESTDIR)$(BINPREFIX)"
mkdir -p "$(DESTDIR)$(MANPREFIX)"/man1
cp -p doc/tspwm.1 "$(DESTDIR)$(MANPREFIX)"/man1
cp -Pp doc/tspc.1 "$(DESTDIR)$(MANPREFIX)"/man1
mkdir -p "$(DESTDIR)$(BASHCPL)"
cp -p contrib/bash_completion "$(DESTDIR)$(BASHCPL)"/tspc
mkdir -p "$(DESTDIR)$(FISHCPL)"
cp -p contrib/fish_completion "$(DESTDIR)$(FISHCPL)"/tspc.fish
mkdir -p "$(DESTDIR)$(ZSHCPL)"
cp -p contrib/zsh_completion "$(DESTDIR)$(ZSHCPL)"/_tspc
#mkdir -p "$(DESTDIR)$(DOCPREFIX)"
#cp -p $(MD_DOCS) "$(DESTDIR)$(DOCPREFIX)"
#mkdir -p "$(DESTDIR)$(DOCPREFIX)"/examples
#cp -pr examples/* "$(DESTDIR)$(DOCPREFIX)"/examples
#mkdir -p "$(DESTDIR)$(XSESSIONS)"
# cp -p contrib/freedesktop/tspwm.desktop "$(DESTDIR)$(XSESSIONS)"
uninstall:
rm -f "$(DESTDIR)$(BINPREFIX)"/tspwm
rm -f "$(DESTDIR)$(BINPREFIX)"/tspc
rm -f "$(DESTDIR)$(MANPREFIX)"/man1/tspwm.1
rm -f "$(DESTDIR)$(MANPREFIX)"/man1/tspc.1
rm -f "$(DESTDIR)$(BASHCPL)"/tspc
rm -f "$(DESTDIR)$(FISHCPL)"/tspc.fish
rm -f "$(DESTDIR)$(ZSHCPL)"/_tspc
rm -rf "$(DESTDIR)$(DOCPREFIX)"
rm -f "$(DESTDIR)$(XSESSIONS)"/tspwm.desktop
doc:
a2x -v -d manpage -f manpage -a revnumber=$(VERSION) doc/tspwm.1.asciidoc
clean:
rm -f $(WM_OBJ) $(CLI_OBJ) tspwm tspc
.PHONY: all install uninstall doc clean

View File

@ -1,57 +0,0 @@
package main
import "tuxpa.in/t/wm/src/cmd"
type Api struct {
Node Node `name:"node" cmd:"node"`
Desktop Desktop `name:"desktop" cmd:"desktop"`
}
type NodeSel string
type Node struct {
NODE_SEL NodeSel `name:"NODE_SEL" arg:"" default:"focused" optional:"" help:"default is focused"`
Focus NodeFocus `name:"focus" cmd:"focus"`
// Activate NodeSel `name:"activate" cmd:"activate"`
// ToDesktop NodeSel `name:"to-desktop" cmd:"to-desktop"`
}
type NodeFocus struct {
}
func (n *NodeFocus) Run(ctx *cmd.Context) error {
panic("not implemented") // TODO: Implement
}
func (n *Node) Run(ctx *cmd.Context) error {
panic("not implemented") // TODO: Implement
}
type Desktop struct {
DESKTOP_SEL string `name:"DESKTOP_SEL"`
}
func (d *Desktop) Run(ctx *cmd.Context) error {
panic("not implemented") // TODO: Implement
}
type Monitor struct {
MONITOR_SEL string `name:"MONITOR_SEL"`
}
type Query struct {
}
type Wm struct {
}
type Rule struct {
}
type Config struct {
}
type Subscribe struct {
}
type Quit struct {
}

View File

@ -8,9 +8,14 @@ import (
)
func main() {
s, err := sock.Default()
s, err := sock.Client("")
errExit(err)
resp, err := s.Send(os.Args[1:]...)
o := os.Args[1:]
if len(o) == 0 {
fmt.Println("no arguments given.")
return
}
resp, err := s.Send(o...)
errExit(err)
fmt.Print(resp)
}

137
cmd/bspwm/main.go Normal file
View File

@ -0,0 +1,137 @@
package main
import (
"context"
"fmt"
"net"
"os"
"os/signal"
"strconv"
"syscall"
"github.com/jezek/xgbutil"
"github.com/jezek/xgbutil/xevent"
"tuxpa.in/a/zlog/log"
"tuxpa.in/t/wm/src/bsp"
"tuxpa.in/t/wm/src/handler"
"tuxpa.in/t/wm/src/handler/domains"
"tuxpa.in/t/wm/src/sock"
)
func main() {
c, err := _main()
if err != nil {
panic(err)
}
os.Exit(c)
}
func _main() (code int, err error) {
// connect to the x server
log.Printf("connecting to xorg")
x11, err := xgbutil.NewConn()
if err != nil {
return 1, err
}
defer x11.Conn().Close()
addr := parsePath(x11, "./bspwm.sock")
// create socket
log.Printf("starting bspwm")
ln, err := sock.Server(addr)
if err != nil {
return 1, err
}
defer ln.Close()
// construct context
ctx, stop := signal.NotifyContext(context.Background(),
os.Interrupt,
syscall.SIGTERM,
syscall.SIGQUIT,
syscall.SIGINT,
)
defer stop()
// initialize WM state
w := bsp.NewWM()
// create a wm-x11 connection
xwm := bsp.NewXWM(w, x11)
go func() {
err := xwm.Start(ctx)
if err != nil {
log.Err(err).Msg("x server shutdown")
stop()
}
}()
// create a handler
h := &handler.Handler{
XWM: xwm,
}
// install the handlers
handler.AddDomain[domains.Node](h, "node")
handler.AddDomain[domains.Todo](h, "desktop")
handler.AddDomain[domains.Monitor](h, "monitor")
handler.AddDomain[domains.Wm](h, "wm")
handler.AddDomain[domains.Todo](h, "rule")
handler.AddDomain[domains.Config](h, "config")
handler.AddDomain[domains.Todo](h, "subscribe")
handler.AddDomain[domains.Query](h, "query")
handler.AddDomain[domains.Echo](h, "echo")
codeCh := make(chan int, 1)
handler.AddDomain[domains.Todo](h, "quit")
h.AddDomainFunc("quit", func() handler.Domain {
d := &domains.Lambda{
Fn: func(x *bsp.XWM, msg *sock.Msg) ([]byte, error) {
if !msg.HasNext() {
codeCh <- 0
return nil, nil
}
str := msg.Next()
cd, err := strconv.Atoi(str)
if err != nil {
return nil, err
}
codeCh <- cd
return nil, nil
},
}
return d
})
beforeCh, afterCh, quitCh := xevent.MainPing(xwm.X)
// message listen loop
for {
select {
case <-beforeCh:
<-afterCh
case m := <-ln.Msg():
h.Run(m)
case cint := <-codeCh:
stop()
return cint, nil
case <-quitCh:
stop()
case <-ctx.Done():
return 0, nil
}
}
}
func parsePath(xc *xgbutil.XUtil, path string) *net.UnixAddr {
if path == "" {
path = os.Getenv(sock.SOCKET_ENV_VAR)
}
if path == "" {
path = fmt.Sprintf(sock.SOCKET_PATH_TPL, "", xc.Conn().DisplayNumber, xc.Conn().DefaultScreen)
}
addr, err := net.ResolveUnixAddr("unix", path)
if err != nil {
panic(err)
}
return addr
}

29
contrib/bash_completion Normal file
View File

@ -0,0 +1,29 @@
_tspc() {
local commands='node desktop monitor query rule wm subscribe config quit'
local settings='external_rules_command status_prefix normal_border_color active_border_color focused_border_color presel_feedback_color border_width window_gap top_padding right_padding bottom_padding left_padding top_monocle_padding right_monocle_padding bottom_monocle_padding left_monocle_padding split_ratio automatic_scheme removal_adjustment initial_polarity directional_focus_tightness presel_feedback borderless_monocle gapless_monocle single_monocle borderless_singleton pointer_motion_interval pointer_modifier pointer_action1 pointer_action2 pointer_action3 click_to_focus swallow_first_click focus_follows_pointer pointer_follows_focus pointer_follows_monitor mapping_events_count ignore_ewmh_focus ignore_ewmh_fullscreen ignore_ewmh_struts center_pseudo_tiled honor_size_hints remove_disabled_monitors remove_unplugged_monitors merge_overlapping_monitors'
COMPREPLY=()
if [[ $COMP_CWORD -ge 1 ]] ; then
local current_word="${COMP_WORDS[COMP_CWORD]}"
if [[ $COMP_CWORD -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "$commands" -- "$current_word") )
return 0
else
local second_word=${COMP_WORDS[1]}
case $second_word in
config)
if [[ $COMP_CWORD -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "$settings" -- "$current_word") )
return 0
fi
;;
esac
fi
fi
}
complete -F _tspc tspc
# vim: set ft=sh:

14
contrib/fish_completion Normal file
View File

@ -0,0 +1,14 @@
function __fish_tspc_needs_command
set cmd (commandline -opc)
[ (count $cmd) -eq 1 -a $cmd[1] = 'tspc' ]; and return 0
return 1
end
function __fish_tspc_using_command
set cmd (commandline -opc)
[ (count $cmd) -gt 1 ]; and [ $argv[1] = $cmd[2] ]; and return 0
return 1
end
complete -f -c tspc -n '__fish_tspc_needs_command' -a 'node desktop monitor query rule wm subscribe config quit'
complete -f -c tspc -n '__fish_tspc_using_command config' -a 'external_rules_command status_prefix normal_border_color active_border_color focused_border_color presel_feedback_color border_width window_gap top_padding right_padding bottom_padding left_padding top_monocle_padding right_monocle_padding bottom_monocle_padding left_monocle_padding split_ratio automatic_scheme removal_adjustment initial_polarity directional_focus_tightness presel_feedback borderless_monocle gapless_monocle single_monocle borderless_singleton pointer_motion_interval pointer_modifier pointer_action1 pointer_action2 pointer_action3 click_to_focus swallow_first_click focus_follows_pointer pointer_follows_focus pointer_follows_monitor mapping_events_count ignore_ewmh_focus ignore_ewmh_fullscreen ignore_ewmh_struts center_pseudo_tiled honor_size_hints remove_disabled_monitors remove_unplugged_monitors merge_overlapping_monitors'

View File

@ -0,0 +1,5 @@
[Desktop Entry]
Name=tspwm
Comment=Binary space partitioning window manager (written in go)
Exec=tspwm
Type=Application

375
contrib/zsh_completion Normal file
View File

@ -0,0 +1,375 @@
#compdef tspc
_tspc_selector() {
[[ ${@[(r)--]} = '--' ]] && shift ${@[(i)--]}
local -a completions=() completions_display=()
local index=0 name id sel_type="$1"
shift 1
case $sel_type in
(node) compset -P '*[#.:/@]' ;;
(desktop) compset -P '*[#.:]' ;;
(monitor) compset -P '*[#.]' ;;
(*) return 1 ;;
esac
case "$sel_type $IPREFIX" in
(desktop*:)
local ipfx=${${IPREFIX##*@}%:}
while do
if completions=("${(@f)$(tspc query --names -D -m ${ipfx} 2> /dev/null)}") ;then
until ((++index > $#completions)) do
completions[index]="^${index}:${completions[index]}"
done
completions+='focused'
_describe "${sel_type} selector" completions $@ -S '' -J ${sel_type}
break
else
completions=()
[ -n "$ipfx[(r)#]" ] &&
ipfx="${ipfx#*#}" ||
break
fi
done
;|
(node*[^@/:.])
tspc query -N -n .window 2> /dev/null |
while read id ;do
id=${id//:/\\:}
if which xdotool &> /dev/null ;then
name=$(xdotool getwindowname $id 2> /dev/null)
elif which xprop &> /dev/null ;then
name=$(xprop -id $id -notype WM_NAME 2> /dev/null) &&
[[ "$name" = 'WM_NAME ='* ]] &&
name="${${name#*\"}%\"*}" ||
name=""
else
name="install xdotool or xprop to see window titles here"
fi
completions+="$id:$name"
done
;|
((desktop|monitor)*[^:.])
local max_name_len=0 max_index_len i
local -a snames names ids
tspc query -${(U)sel_type[1]} 2> /dev/null |
while read id ;do
((index++))
name=$(tspc query --names -${(U)sel_type[1]} -${sel_type[1]} $id 2> /dev/null)
[[ "$name" == *[:.!]* ]] &&
sel_name="%${name//:/\\:}" ||
sel_name="$name"
((max_name_len = $#sel_name > max_name_len ? $#sel_name : max_name_len))
ids+="${id}"
snames+="${sel_name}"
names+="${name}"
done
max_index_len=$(($#index + 1))
((max_name_len >= max_name_len)) && ((max_name_len=max_name_len + 1))
for ((i = 1 ; i <= $#ids ; i++)) ;do
(($#ids[i] <= max_name_len)) &&
completions_display+="${(r($max_name_len+1))ids[i]}:${names[i]}" ||
completions_display+="${ids[i]}:${names[i]}"
completions+="${ids[i]}:${names[i]}"
done
for ((i = 1 ; i <= $#ids ; i++)) ;do
completions+="${snames[i]}:${names[i]}"
completions_display+="${(r($max_name_len))snames[i]}:${names[i]}"
done
for ((i = 1 ; i <= $#ids ; i++)) ;do
completions+="^${i}:${names[i]}"
completions_display+="^${i}:${names[i]}"
done
;|
(node*('@'(*':'|*'/'|)))
_describe 'node path' jump -S '/' -r "#. ${quote}" -J nodes
;|
(node*'/')
;;
(*'.')
_tspc_prefix '!' "${sel_type} modifiers" ${sel_type}_mod $@ -J ${sel_type}_mod
;|
((desktop|monitor)*@)
;&
(*[^:.@/])
if (( $#completions_display)) ;then
_describe "${sel_type} selector" ${sel_type}_desc $@ -S '.' -r ". \n:#${quote}\-" -J ${sel_type} \
-- completions_display completions $@ -S '.' -r ". \n:#${quote}\-" -J ${sel_type}
else
_describe "${sel_type} selector" ${sel_type}_desc $@ -S '.' -r ". \n:#${quote}\-" -J ${sel_type} \
-- completions $@ -S '.' -r ". \n:#${quote}\-" -J ${sel_type}
fi
;|
(node*@*'#'*)
;;
(node*@*)
_tspc_selector desktop -S ':' -qr ".#\-\n ${quote}"
;;
(desktop*)
_tspc_selector monitor -S ':' -r ".#\-\n ${quote}"
;;
esac
}
_tspc_prefix(){
[[ ${@[(r)--]} = '--' ]] && shift ${@[(i)--]}
[[ "$PREFIX[1]" == "$1" ]] &&
local a="-n" b ||
local b="-n" a
_describe $@[2,-1] $a -- $@[3,-1] $b -p "$1"
}
_tspc_query_names() {
[[ ${@[(r)--]} = '--' ]] && shift ${@[(i)--]}
local -a items=("${(@f)$(tspc query $2 --names 2> /dev/null)}") ||
return
local c
for c in '\' ':' '[' '(' '*'
items=("${(@)items//$c/\\$c}")
_values -w "$1" "${items[@]}"
}
_tspc() {
local -a commands=(node desktop monitor query rule wm subscribe config quit) \
resize_handle=(top bottom top_left top_right bottom_left bottom_right left right) \
node_state=(tiled pseudo_tiled floating fullscreen) \
flag=(hidden sticky private locked marked urgent) \
layer=(below normal above) \
dir=(north west south east) \
cycle_dir=(next prev)
local -a jump=($dir first second brother parent 1 2) \
node_desc=($dir $cycle_dir any last newest older newer focused pointed biggest smallest) \
node_mod=($node_state $flag $layer focused automatic local \
active leaf window same_class descendant_of ancestor_of) \
desktop_desc=($cycle_dir any last newest older newer focused) \
desktop_mod=(focused occupied local urgent) \
monitor_desc=($dir $cycle_dir any last newest older newer focused pointed primary) \
monitor_mod=(focused occupied) \
presel_dir=($dir cancel)
local quote="${compstate[quote]}" context state state_descr line
typeset -A opt_args
compset -n 2
compset -S "${quote}"
if ((CURRENT==1)) ;then
_describe 'command or domain' commands
return
fi
case $words[1] in
(node)
((CURRENT==2)) && _tspc_selector node
((CURRENT>2)) && [[ "$words[2]" != '-'* ]] && compset -n 2
((CURRENT>2)) && [[ "$words[CURRENT-2]" =~ "^-(m|d|n|s|-to-(monitor|desktop|node)|-swap)$" ]] &&
_values 'option' '--follow[If passed, the focused node will stay focused]'
_arguments -C \
'*'{-a,--activate}'[Activate the selected or given node]::node selector:_tspc_selector -- node'\
'*'{-B,--balance}'[Adjust the split ratios of the tree rooted at the selected node so that all windows occupy the same area]'\
'*'{-C,--circulate}'[Circulate the windows of the tree rooted at the selected node]:direction:(forward backward)'\
'*'{-c,--close}'[Close the windows rooted at the selected node]'\
'*'{-d,--to-desktop}'[Send the selected node to the given desktop]:desktop selector:_tspc_selector -- desktop'\
'*'{-E,--equalize}'[Reset the split ratios of the tree rooted at the selected node to their default value]'\
'*'{-F,--flip}'[Flip the the tree rooted at selected node]: :(horizontal vertical)'\
'*'{-f,--focus}'[Focus the selected or given node]::node selector:_tspc_selector -- node'\
'*'{-g,--flag}'[Set or toggle the given flag for the selected node]: :-> flag'\
'*'{-i,--insert-receptacle}'[Insert a receptacle node at the selected node]'\
'*'{-k,--kill}'[Kill the windows rooted at the selected node]'\
'*'{-l,--layer}"[Set the stacking layer of the selected window]:stacking layer:($layer)"\
'*'{-m,--to-monitor}'[Send the selected node to the given monitor]:monitor selector:_tspc_selector -- monitor'\
'*'{-n,--to-node}'[Transplant the selected node to the given node]:node selector:_tspc_selector -- node'\
'*'{-o,--presel-ratio}'[Set the splitting ratio of the preselection area]:preselect ratio: ( )'\
'*'{-p,--presel-dir}'[Preselect the splitting area of the selected node or cancel the preselection]: :_tspc_prefix -- "~" preselect presel_dir'\
'*'{-y,--type}'[Set the splitting type of the selected node]: :(horizontal vertical)'\
'*'{-r,--ratio}'[Set the splitting ratio of the selected node (0 < ratio < 1)]: :( )'\
'*'{-R,--rotate}'[Rotate the tree rooted at the selected node]:angle:(90 270 180)'\
'*'{-s,--swap}'[Swap the selected node with the given node]:node selector:_tspc_selector -- node'\
'*'{-t,--state}'[Set the state of the selected window]: :_tspc_prefix -- "~" "node state" node_state '\
'*'{-v,--move}'[Move the selected window by dx pixels horizontally and dy pixels vertically]:dx:( ):dy:( )'\
'*'{-z,--resize}"[Resize the selected window by moving the given handle by dx pixels horizontally and dy pixels vertically]:handle:($resize_handle):dx:( ):dy:( )"
[ "$state" = flag ] && _values 'flag' "${flag[@]:#urgent}::set flag:(on off)"
;;
(desktop)
((CURRENT==2)) && _tspc_selector desktop
((CURRENT>2)) && [[ "$words[2]" != '-'* ]] && compset -n 2
((CURRENT>2)) && [[ "$words[CURRENT-2]" =~ "^-m|-s|--monitor|--swap$" ]] &&
_values 'option' '--follow[If passed, the focused desktop will stay focused]'
_arguments \
'*'{-a,--activate}'[Activate the selected or given desktop]:: :_tspc_selector -- desktop'\
'*'{-b,--bubble}"[Bubble the selected desktop in the given direction]:direction:($cycle_dir)"\
'*'{-f,--focus}'[Focus the selected or given desktop]:: :_tspc_selector -- desktop'\
'*'{-l,--layout}"[Set or cycle the layout of the selected desktop]:desktop layout:($cycle_dir monocle tiled)"\
'*'{-m,--to-monitor}'[Send the selected desktop to the given monitor]: :_tspc_selector -- monitor'\
'*'{-n,--rename}'[Rename the selected desktop]:desktop name:( )'\
'*'{-r,--remove}'[Remove the selected desktop]'\
'*'{-s,--swap}'[Swap the selected desktop with the given desktop]: :_tspc_selector -- desktop'
;;
(monitor)
((CURRENT==2)) && _tspc_selector monitor
((CURRENT>2)) && [[ "$words[2]" != '-'* ]] && compset -n 2
_arguments \
'*'{-a,--add-desktops}'[Create desktops with the given names in the selected monitor]:*:add desktops:( )'\
'*'{-d,--reset-desktops}'[Rename, add or remove desktops]:*: :->desktops'\
'*'{-f,--focus}'[Focus the selected or given monitor]:: :_tspc_selector -- monitor'\
'*'{-g,--rectangle}'[Set the rectangle of the selected monitor]:WxH+X+Y:( )'\
'*'{-n,--rename}'[Rename the selected monitor]: :( )'\
'*'{-o,--reorder-desktops}'[Reorder the desktops of the selected monitor]:*:reorder desktops:_tspc_query_names -- desktops -D'\
'*'{-r,--remove}'[Remove the selected monitor]'\
'*'{-s,--swap}'[Swap the selected monitor with the given monitor]: :_tspc_selector -- monitor'
;;
(query)
local -a cmds_no_names=('-T' '--tree' '-N' '--nodes')
local -a cmds=($cmds_no_names '-D' '--desktops' '-M' '--monitors')
_arguments \
'*'{-d,--desktop}'[Constrain matches to the selected desktop]: :_tspc_selector -- desktop'\
'*'{-m,--monitor}'[Constrain matches to the selected monitor]: :_tspc_selector -- monitor'\
'*'{-n,--node}'[Constrain matches to the selected node]: :_tspc_selector -- node'\
"($cmds_no_names --names)--names[Print names instead of IDs. Can only be used with -M and -D]"\
"($cmds --names)"{-N,--nodes}'[List the IDs of the matching nodes]'\
"($cmds --names)"{-T,--tree}'[Print a JSON representation of the matching item]'\
"($cmds)"{-D,--desktops}'[List the IDs (or names) of the matching desktops]'\
"($cmds)"{-M,--monitors}'[List the IDs (or names) of the matching monitors]'
;;
(wm)
_arguments \
'*'{-d,--dump-state}'[Dump the current world state on standard output]'\
'*'{-l,--load-state}'[Load a world state from the given file]:load state from file:_files'\
'*'{-a,--add-monitor}'[Add a monitor for the given name and rectangle]:add monitor:( )'\
'*'{-O,--reorder-monitors}'[Reorder the list of monitors to match the given order]:*: :_tspc_query_names -- monitors -M'\
'*'{-o,--adopt-orphans}'[Manage all the unmanaged windows remaining from a previous session]'\
'*'{-h,--record-history}'[Enable or disable the recording of node focus history]:history:(on off)'\
'*'{-g,--get-status}'[Print the current status information]'\
'*'{-r,--restart}'[Restart the window manager]'
;;
(subscribe)
if [[ "$words[CURRENT-1]" != (-c|--count) ]] ;then
_values -w "options" \
'(-f --fifo)'{-f,--fifo}'[Print a path to a FIFO from which events can be read and return]'\
'(-c --count)'{-c,--count}'[Stop the corresponding tspc process after having received specified count of events]'
_values -w -S "_" events all report pointer_action \
"monitor:: :(add rename remove swap focus geometry)"\
"desktop:: :(add rename remove swap transfer focus activate layout)"\
"node:: :(add remove swap transfer focus activate presel stack geometry state flag layer)"
fi
;;
(rule)
local -a completions by_index
local index=0 target settings class id instance json
_arguments -C \
{-a,--add}'[Create a new rule]:*: :->add'\
{-r,--remove}'[Remove the given rules]:*: :->remove'\
'(-l --list)'{-l,--list}'[List the rules]'
compset -N "-([ar]|-add|-remove)"
case $state$CURRENT in
(add1)
compset -P '*:'
tspc query -N -n '.window' 2> /dev/null |
while read id ; do
json=$(tspc query -T -n $id 2>/dev/null) || continue
[[ "$json[1]" = '{' ]] || continue
class=${${json##*\"className\":\"}%%\",\"*}
instance=${${json##*\"instanceName\":\"}%%\",\"*}
[[ "$class[1]" != '{' && "$instance[1]" != '{' ]] || continue
if [ -n "$IPREFIX" ] ;then
[[ "$IPREFIX" == ("${class}:"|('\'|)'*:') ]] &&
completions[(r)$instance]=$instance
else
class=${class%%:/\\:}
completions[(r)$class]="$class"
fi
done
;;
(add*)
_values -w 'add rule' {border,focus,follow,manage,center}': :(on off)'\
'(--one-shot)-o'\
'(-o)--one-shot'\
'monitor: :_tspc_selector -- monitor'\
'desktop: :_tspc_selector -- desktop'\
'node: :_tspc_selector -- node'\
'rectangle: :( )'\
'split_ratio:split ratio:( )'\
"split_dir:split direction:(${dir})"\
"state:state:(${node_state})"\
"${flag[@]:#urgent}:set flag:(on off)"\
"layer:layer:(${layer})"
return
;;
(remove*)
compset -P '*:'
tspc rule -l 2> /dev/null |
while IFS=" " read target settings ;do
by_index+="^$((++index)):${target} ${settings}"
if [ -n "$IPREFIX" ] ;then
completions+="${target#*:}"
else
completions+="${target%:*}"
fi
done
[[ -z "$IPREFIX" ]] &&
_describe 'remove rule by position' '(head tail)' -J by_index -- by_index -J by_index
;;
(*)
return
;;
esac
completions[(r)\*]=*
[ -n "$IPREFIX" ] &&
_describe 'match window instance' completions ||
_describe 'match window class' completions -q -S ':'
;;
(config)
local -a {look,behaviour,input}{_bool,}
look_bool=(presel_feedback borderless_monocle gapless_monocle borderless_singleton)
look=({normal,active,focused}_border_color {top,right,bottom,left}_padding {top,right,bottom,left}_monocle_padding presel_feedback_color border_width window_gap)
behaviour_bool=(single_monocle removal_adjustment ignore_ewmh_focus ignore_ewmh_struts center_pseudo_tiled honor_size_hints remove_disabled_monitors remove_unplugged_monitors merge_overlapping_monitors)
behaviour=(mapping_events_count ignore_ewmh_fullscreen external_rules_command split_ratio automatic_scheme initial_polarity directional_focus_tightness status_prefix)
input_bool=(swallow_first_click focus_follows_pointer pointer_follows_{focus,monitor})
input=(click_to_focus pointer_motion_interval pointer_modifier pointer_action{1,2,3})
if [[ "$CURRENT" == (2|3) ]];then
_arguments \
'-d[Set settings for the selected desktop]: :_tspc_selector -- desktop'\
'-m[Set settings for the selected monitor]: :_tspc_selector -- monitor'\
'-n[Set settings for the selected node]: :_tspc_selector -- node'
fi
if [[ "${words[2]}" == -* ]] ;then
(( CURRENT == 3 )) && return
if (( CURRENT > 3 )) ;then
compset -n 3
fi
fi
if ((CURRENT==2)) ;then
_describe 'look' look -J look -- look_bool -J look
_describe 'input' input -J input -- input_bool -J input
_describe 'behaviour' behaviour -J behaviour -- behaviour_bool -J behaviour
elif ((CURRENT==3)) ;then
setting=$words[2]
case $setting in
(ignore_ewmh_fullscreen)
_values -S "," "set $setting" all none "enter:: :(exit)" "exit:: :(enter)"
;;
(initial_polarity)
_values "set $setting" first_child second_child
;;
(pointer_action(1|2|3))
_values "set $setting" move resize_side resize_corner focus none
;;
(pointer_modifier)
_values "set $setting" shift control lock mod1 mod2 mod3 mod4 mod5
;;
(directional_focus_tightness)
_values "set $setting" low high
;;
(click_to_focus)
_values "set $setting" any button1 button2 button3 none
;;
(*)
[[ -n $look_bool[(r)$setting] ]] ||
[[ -n $behaviour_bool[(r)$setting] ]] ||
[[ -n $input_bool[(r)$setting] ]] &&
_values "set $setting" true false
;;
esac
fi
;;
esac
}
_tspc "$@"

1
doc/tspc.1 Symbolic link
View File

@ -0,0 +1 @@
tspwm.1

1662
doc/tspwm.1 Normal file

File diff suppressed because it is too large Load Diff

987
doc/tspwm.1.asciidoc Normal file
View File

@ -0,0 +1,987 @@
:man source: tspwm
:man version: {revnumber}
:man manual: tspwm Manual
tspwm(1)
========
Name
----
tspwm - Binary space partitioning window manager
Synopsis
--------
*tspwm* [*-h*|*-v*|*-c* 'CONFIG_PATH']
*tspc --print-socket-path*
*tspc* 'DOMAIN' ['SELECTOR'] 'COMMANDS'
*tspc* 'COMMAND' ['OPTIONS'] ['ARGUMENTS']
Description
-----------
*tspwm* is a tiling window manager that represents windows as the leaves of a full binary tree.
It is controlled and configured via *tspc*.
it is a rewrite of *bspwm* in pure go.
bspwm can be found at *git@github.com:baskerville/bspwm.git*.
Options
-------
*-h*::
Print the synopsis and exit.
*-v*::
Print the version and exit.
*-c* 'CONFIG_PATH'::
Use the given configuration file.
*--print-socket-path*::
Print the *tspwm* socket path and exit.
Common Definitions
------------------
----
DIR := north | west | south | east
CYCLE_DIR := next | prev
----
Selectors
---------
Selectors are used to select a target node, desktop, or monitor. A selector
can either describe the target relatively or name it globally.
Selectors consist of an optional reference, a descriptor and any number of
non-conflicting modifiers as follows:
[REFERENCE#]DESCRIPTOR(.MODIFIER)*
The relative targets are computed in relation to the given reference (the
default reference value is *focused*).
An exclamation mark can be prepended to any modifier in order to reverse its
meaning.
The following characters cannot be used in monitor or desktop names: *#*, *:*, *.*.
The special selector *%<name>* can be used to select a monitor or a desktop with an invalid name.
Node
~~~~
Select a node.
----
NODE_SEL := [NODE_SEL#](DIR|CYCLE_DIR|PATH|any|first_ancestor|last|newest|
older|newer|focused|pointed|biggest|smallest|
<node_id>)[.[!]focused][.[!]active][.[!]automatic][.[!]local]
[.[!]leaf][.[!]window][.[!]STATE][.[!]FLAG][.[!]LAYER][.[!]SPLIT_TYPE]
[.[!]same_class][.[!]descendant_of][.[!]ancestor_of]
STATE := tiled|pseudo_tiled|floating|fullscreen
FLAG := hidden|sticky|private|locked|marked|urgent
LAYER := below|normal|above
SPLIT_TYPE := horizontal|vertical
PATH := @[DESKTOP_SEL:][[/]JUMP](/JUMP)*
JUMP := first|1|second|2|brother|parent|DIR
----
Descriptors
^^^^^^^^^^^
'DIR'::
Selects the window in the given (spacial) direction relative to the reference node.
'CYCLE_DIR'::
Selects the node in the given (cyclic) direction relative to the reference node within a depth-first in-order traversal of the tree.
'PATH'::
Selects the node at the given path.
any::
Selects the first node that matches the given selectors.
first_ancestor::
Selects the first ancestor of the reference node that matches the given selectors.
last::
Selects the previously focused node relative to the reference node.
newest::
Selects the newest node in the history of the focused node.
older::
Selects the node older than the reference node in the history.
newer::
Selects the node newer than the reference node in the history.
focused::
Selects the currently focused node.
pointed::
Selects the leaf under the pointer.
biggest::
Selects the biggest leaf.
smallest::
Selects the smallest leaf.
<node_id>::
Selects the node with the given ID.
Path Jumps
^^^^^^^^^^
The initial node is the focused node (or the root if the path starts with '/') of the reference desktop (or the selected desktop if the path has a 'DESKTOP_SEL' prefix).
1|first::
Jumps to the first child.
2|second::
Jumps to the second child.
brother::
Jumps to the brother node.
parent::
Jumps to the parent node.
'DIR'::
Jumps to the node holding the edge in the given direction.
Modifiers
^^^^^^^^^
[!]focused::
Only consider the focused node.
[!]active::
Only consider nodes that are the focused node of their desktop.
[!]automatic::
Only consider nodes in automatic insertion mode. See also *--presel-dir* under *Node* in the *DOMAINS* section below.
[!]local::
Only consider nodes in the reference desktop.
[!]leaf::
Only consider leaf nodes.
[!]window::
Only consider nodes that hold a window.
[!](tiled|pseudo_tiled|floating|fullscreen)::
Only consider windows in the given state.
[!]same_class::
Only consider windows that have the same class as the reference window.
[!]descendant_of::
Only consider nodes that are descendants of the reference node.
[!]ancestor_of::
Only consider nodes that are ancestors of the reference node.
[!](hidden|sticky|private|locked|marked|urgent)::
Only consider windows that have the given flag set.
[!](below|normal|above)::
Only consider windows in the given layer.
[!](horizontal|vertical)::
Only consider nodes with the given split type.
Desktop
~~~~~~~
Select a desktop.
----
DESKTOP_SEL := [DESKTOP_SEL#](CYCLE_DIR|any|last|newest|older|newer|
[MONITOR_SEL:](focused|^<n>)|
<desktop_id>|<desktop_name>)[.[!]focused][.[!]active]
[.[!]occupied][.[!]urgent][.[!]local]
[.[!]LAYOUT][.[!]user_LAYOUT]
LAYOUT := tiled|monocle
----
Descriptors
^^^^^^^^^^^
'CYCLE_DIR'::
Selects the desktop in the given direction relative to the reference desktop.
any::
Selects the first desktop that matches the given selectors.
last::
Selects the previously focused desktop relative to the reference desktop.
newest::
Selects the newest desktop in the history of the focused desktops.
older::
Selects the desktop older than the reference desktop in the history.
newer::
Selects the desktop newer than the reference desktop in the history.
focused::
Selects the currently focused desktop.
^<n>::
Selects the nth desktop. If *MONITOR_SEL* is given, selects the nth desktop on the selected monitor.
<desktop_id>::
Selects the desktop with the given ID.
<desktop_name>::
Selects the desktop with the given name.
Modifiers
^^^^^^^^^
[!]focused::
Only consider the focused desktop.
[!]active::
Only consider desktops that are the focused desktop of their monitor.
[!]occupied::
Only consider occupied desktops.
[!]urgent::
Only consider urgent desktops.
[!]local::
Only consider desktops inside the reference monitor.
[!](tiled|monocle)::
Only consider desktops with the given layout.
[!](user_tiled|user_monocle)::
Only consider desktops which have the given layout as userLayout.
Monitor
~~~~~~~
Select a monitor.
----
MONITOR_SEL := [MONITOR_SEL#](DIR|CYCLE_DIR|any|last|newest|older|newer|
focused|pointed|primary|^<n>|
<monitor_id>|<monitor_name>)[.[!]focused][.[!]occupied]
----
Descriptors
^^^^^^^^^^^
'DIR'::
Selects the monitor in the given (spacial) direction relative to the reference monitor.
'CYCLE_DIR'::
Selects the monitor in the given (cyclic) direction relative to the reference monitor.
any::
Selects the first monitor that matches the given selectors.
last::
Selects the previously focused monitor relative to the reference monitor.
newest::
Selects the newest monitor in the history of the focused monitors.
older::
Selects the monitor older than the reference monitor in the history.
newer::
Selects the monitor newer than the reference monitor in the history.
focused::
Selects the currently focused monitor.
pointed::
Selects the monitor under the pointer.
primary::
Selects the primary monitor.
^<n>::
Selects the nth monitor.
<monitor_id>::
Selects the monitor with the given ID.
<monitor_name>::
Selects the monitor with the given name.
Modifiers
^^^^^^^^^
[!]focused::
Only consider the focused monitor.
[!]occupied::
Only consider monitors where the focused desktop is occupied.
Window States
-------------
tiled::
Its size and position are determined by the window tree.
pseudo_tiled::
A tiled window that automatically shrinks but doesn't stretch beyond its floating size.
floating::
Can be moved/resized freely. Although it doesn't use any tiling space, it is still part of the window tree.
fullscreen::
Fills its monitor rectangle and has no borders.
Node Flags
----------
hidden::
Is hidden and doesn't occupy any tiling space.
sticky::
Stays in the focused desktop of its monitor.
private::
Tries to keep the same tiling position/size.
locked::
Ignores the *node --close* message.
marked::
Is marked (useful for deferred actions). A marked node becomes unmarked after being sent on a preselected node.
urgent::
Has its urgency hint set. This flag is set externally.
Stacking Layers
--------------
There's three stacking layers: BELOW, NORMAL and ABOVE.
In each layer, the window are orderered as follow: tiled & pseudo-tiled < floating < fullscreen.
Receptacles
-----------
A leaf node that doesn't hold any window is called a receptacle. When a node is inserted on a receptacle in automatic mode, it will replace the receptacle. A receptacle can be inserted on a node, preselected and killed. Receptacles can therefore be used to build a tree whose leaves are receptacles. Using the appropriate rules, one can then send windows on the leaves of this tree. This feature is used in 'examples/receptacles' to store and recreate layouts.
Domains
-------
Node
~~~~
General Syntax
^^^^^^^^^^^^^^
node ['NODE_SEL'] 'COMMANDS'
If 'NODE_SEL' is omitted, *focused* is assumed.
Commands
^^^^^^^^
*-f*, *--focus* ['NODE_SEL']::
Focus the selected or given node.
*-a*, *--activate* ['NODE_SEL']::
Activate the selected or given node.
*-d*, *--to-desktop* 'DESKTOP_SEL' [*--follow*]::
Send the selected node to the given desktop. If *--follow* is passed, the focused node will stay focused.
*-m*, *--to-monitor* 'MONITOR_SEL' [*--follow*]::
Send the selected node to the given monitor. If *--follow* is passed, the focused node will stay focused.
*-n*, *--to-node* 'NODE_SEL' [*--follow*]::
Send the selected node on the given node. If *--follow* is passed, the focused node will stay focused.
*-s*, *--swap* 'NODE_SEL' [*--follow*]::
Swap the selected node with the given node. If *--follow* is passed, the focused node will stay focused.
*-p*, *--presel-dir* \[~]'DIR'|cancel::
Preselect the splitting area of the selected node (or cancel the preselection). If *~* is prepended to 'DIR' and the current preselection direction matches 'DIR', then the argument is interpreted as *cancel*. A node with a preselected area is said to be in "manual insertion mode".
*-o*, *--presel-ratio* 'RATIO'::
Set the splitting ratio of the preselection area.
*-v*, *--move* 'dx' 'dy'::
Move the selected window by 'dx' pixels horizontally and 'dy' pixels vertically.
*-z*, *--resize* top|left|bottom|right|top_left|top_right|bottom_right|bottom_left 'dx' 'dy'::
Resize the selected window by moving the given handle by 'dx' pixels horizontally and 'dy' pixels vertically.
*-y*, *--type* 'CYCLE_DIR'|horizontal|vertical::
Set or cycle the splitting type of the selected node.
*-r*, *--ratio* 'RATIO'|(+|-)('PIXELS'|'FRACTION')::
Set the splitting ratio of the selected node (0 < 'RATIO' < 1).
*-R*, *--rotate* '90|270|180'::
Rotate the tree rooted at the selected node.
*-F*, *--flip* 'horizontal|vertical'::
Flip the tree rooted at selected node.
*-E*, *--equalize*::
Reset the split ratios of the tree rooted at the selected node to their default value.
*-B*, *--balance*::
Adjust the split ratios of the tree rooted at the selected node so that all windows occupy the same area.
*-C*, *--circulate* forward|backward::
Circulate the windows of the tree rooted at the selected node.
*-t*, *--state* \~|\[~]'STATE'::
Set the state of the selected window. If *\~* is present and the current state matches 'STATE', then the argument is interpreted as its last state. If the argument is just *~* with 'STATE' omitted, then the state of the selected window is set to its last state.
*-g*, *--flag* hidden|sticky|private|locked|marked[=on|off]::
Set or toggle the given flag for the selected node.
*-l*, *--layer* below|normal|above::
Set the stacking layer of the selected window.
*-i*, *--insert-receptacle*::
Insert a receptacle node at the selected node.
*-c*, *--close*::
Close the windows rooted at the selected node.
*-k*, *--kill*::
Kill the windows rooted at the selected node.
Desktop
~~~~~~~
General Syntax
^^^^^^^^^^^^^^
desktop ['DESKTOP_SEL'] 'COMMANDS'
If 'DESKTOP_SEL' is omitted, *focused* is assumed.
COMMANDS
^^^^^^^^
*-f*, *--focus* ['DESKTOP_SEL']::
Focus the selected or given desktop.
*-a*, *--activate* ['DESKTOP_SEL']::
Activate the selected or given desktop.
*-m*, *--to-monitor* 'MONITOR_SEL' [*--follow*]::
Send the selected desktop to the given monitor. If *--follow* is passed, the focused desktop will stay focused.
*-s*, *--swap* 'DESKTOP_SEL' [*--follow*]::
Swap the selected desktop with the given desktop. If *--follow* is passed, the focused desktop will stay focused.
*-l*, *--layout* 'CYCLE_DIR'|monocle|tiled::
Set or cycle the layout of the selected desktop.
*-n*, *--rename* <new_name>::
Rename the selected desktop.
*-b*, *--bubble* 'CYCLE_DIR'::
Bubble the selected desktop in the given direction.
*-r*, *--remove*::
Remove the selected desktop.
Monitor
~~~~~~~
General Syntax
^^^^^^^^^^^^^^
monitor ['MONITOR_SEL'] 'COMMANDS'
If 'MONITOR_SEL' is omitted, *focused* is assumed.
Commands
^^^^^^^^
*-f*, *--focus* ['MONITOR_SEL']::
Focus the selected or given monitor.
*-s*, *--swap* 'MONITOR_SEL'::
Swap the selected monitor with the given monitor.
*-a*, *--add-desktops* <name>...::
Create desktops with the given names in the selected monitor.
*-o*, *--reorder-desktops* <name>...::
Reorder the desktops of the selected monitor to match the given order.
*-d*, *--reset-desktops* <name>...::
Rename, add or remove desktops depending on whether the number of given names is equal, superior or inferior to the number of existing desktops.
*-g*, *--rectangle* WxH+X+Y::
Set the rectangle of the selected monitor.
*-n*, *--rename* <new_name>::
Rename the selected monitor.
*-r*, *--remove*::
Remove the selected monitor.
Query
~~~~~
General Syntax
^^^^^^^^^^^^^^
query 'COMMANDS' ['OPTIONS']
Commands
^^^^^^^^
The optional selectors are references.
*-N*, *--nodes* ['NODE_SEL']::
List the IDs of the matching nodes.
*-D*, *--desktops* ['DESKTOP_SEL']::
List the IDs (or names) of the matching desktops.
*-M*, *--monitors* ['MONITOR_SEL']::
List the IDs (or names) of the matching monitors.
*-T*, *--tree*::
Print a JSON representation of the matching item.
Options
^^^^^^^
*-m*,*--monitor* ['MONITOR_SEL'|'MONITOR_MODIFIERS']::
*-d*,*--desktop* ['DESKTOP_SEL'|'DESKTOP_MODIFIERS']::
*-n*,*--node* ['NODE_SEL'|'NODE_MODIFIERS']::
Constrain matches to the selected monitors, desktops or nodes.
*--names*::
Print names instead of IDs. Can only be used with '-M' and '-D'.
Wm
~~
General Syntax
^^^^^^^^^^^^^^
wm 'COMMANDS'
Commands
^^^^^^^^
*-d*, *--dump-state*::
Dump the current world state on standard output.
*-l*, *--load-state* <file_path>::
Load a world state from the given file. The path must be absolute.
*-a*, *--add-monitor* <name> WxH+X+Y::
Add a monitor for the given name and rectangle.
*-O*, *--reorder-monitors* <name>...::
Reorder the list of monitors to match the given order.
*-o*, *--adopt-orphans*::
Manage all the unmanaged windows remaining from a previous session.
*-h*, *--record-history* on|off::
Enable or disable the recording of node focus history.
*-g*, *--get-status*::
Print the current status information.
*-r*, *--restart*::
Restart the window manager
Rule
~~~~
General Syntax
^^^^^^^^^^^^^^
rule 'COMMANDS'
Commands
^^^^^^^^
*-a*, *--add* (<class_name>|\*)[:(<instance_name>|\*)[:(<name>|\*)]] [*-o*|*--one-shot*] [monitor=MONITOR_SEL|desktop=DESKTOP_SEL|node=NODE_SEL] [state=STATE] [layer=LAYER] [honor_size_hints=(true|false|tiled|floating)] [split_dir=DIR] [split_ratio=RATIO] [(hidden|sticky|private|locked|marked|center|follow|manage|focus|border)=(on|off)] [rectangle=WxH+X+Y]::
Create a new rule. Colons in the 'instance_name', 'class_name', or 'name'
fields can be escaped with a backslash.
*-r*, *--remove* ^<n>|head|tail|(<class_name>|\*)[:(<instance_name>|\*)[:(<name>|*)]]...::
Remove the given rules.
*-l*, *--list*::
List the rules.
Config
~~~~~~
General Syntax
^^^^^^^^^^^^^^
config [-m 'MONITOR_SEL'|-d 'DESKTOP_SEL'|-n 'NODE_SEL'] <setting> [<value>]::
Get or set the value of <setting>.
Subscribe
~~~~~~~~~
General Syntax
^^^^^^^^^^^^^^
subscribe ['OPTIONS'] (all|report|monitor|desktop|node|...)*::
Continuously print events. See the *EVENTS* section for the description of each event.
Options
^^^^^^^
*-f*, *--fifo*::
Print a path to a FIFO from which events can be read and return.
*-c*, *--count* 'COUNT'::
Stop the corresponding *tspc* process after having received 'COUNT' events.
Quit
~~~~
General Syntax
^^^^^^^^^^^^^^
quit [<status>]::
Quit with an optional exit status.
Exit Codes
----------
If the server can't handle a message, *tspc* will return with a non-zero exit code.
Settings
--------
Colors are in the form '#RRGGBB', booleans are 'true', 'on', 'false' or 'off'.
All the boolean settings are 'false' by default unless stated otherwise.
Global Settings
~~~~~~~~~~~~~~~
'normal_border_color'::
Color of the border of an unfocused window.
'active_border_color'::
Color of the border of a focused window of an unfocused monitor.
'focused_border_color'::
Color of the border of a focused window of a focused monitor.
'presel_feedback_color'::
Color of the *node --presel-{dir,ratio}* message feedback area.
'split_ratio'::
Default split ratio.
'status_prefix'::
Prefix prepended to each of the status lines.
'external_rules_command'::
Absolute path to the command used to retrieve rule consequences. The command will receive the following arguments: window ID, class name, instance name, and intermediate consequences. The output of that command must have the following format: *key1=value1 key2=value2 ...* (the valid key/value pairs are given in the description of the 'rule' command).
'automatic_scheme'::
The insertion scheme used when the insertion point is in automatic mode. Accept the following values: *longest_side*, *alternate*, *spiral*.
'initial_polarity'::
On which child should a new window be attached when adding a window on a single window tree in automatic mode. Accept the following values: *first_child*, *second_child*.
'directional_focus_tightness'::
The tightness of the algorithm used to decide whether a window is on the 'DIR' side of another window. Accept the following values: *high*, *low*.
'removal_adjustment'::
Adjust the brother when unlinking a node from the tree in accordance with the automatic insertion scheme.
'presel_feedback'::
Draw the preselection feedback area. Defaults to 'true'.
'borderless_monocle'::
Remove borders of tiled windows for the *monocle* desktop layout.
'gapless_monocle'::
Remove gaps of tiled windows for the *monocle* desktop layout.
'top_monocle_padding'::
'right_monocle_padding'::
'bottom_monocle_padding'::
'left_monocle_padding'::
Padding space added at the sides of the screen for the *monocle* desktop layout.
'single_monocle'::
Set the desktop layout to *monocle* if there's only one tiled window in the tree.
'borderless_singleton'::
Remove borders of the only window on the only monitor regardless its layout.
'pointer_motion_interval'::
The minimum interval, in milliseconds, between two motion notify events.
'pointer_modifier'::
Keyboard modifier used for moving or resizing windows. Accept the following values: *shift*, *control*, *lock*, *mod1*, *mod2*, *mod3*, *mod4*, *mod5*.
'pointer_action1'::
'pointer_action2'::
'pointer_action3'::
Action performed when pressing 'pointer_modifier' + 'button<n>'. Accept the following values: *move*, *resize_side*, *resize_corner*, *focus*, *none*.
'click_to_focus'::
Button used for focusing a window (or a monitor). The possible values are: *button1*, *button2*, *button3*, *any*, *none*. Defaults to *button1*.
'swallow_first_click'::
Don't replay the click that makes a window focused if 'click_to_focus' isn't *none*.
'focus_follows_pointer'::
Focus the window under the pointer.
'pointer_follows_focus'::
When focusing a window, put the pointer at its center.
'pointer_follows_monitor'::
When focusing a monitor, put the pointer at its center.
'mapping_events_count'::
Handle the next *mapping_events_count* mapping notify events. A negative value implies that every event needs to be handled.
'ignore_ewmh_focus'::
Ignore EWMH focus requests coming from applications.
'ignore_ewmh_fullscreen'::
Block the fullscreen state transitions that originate from an EWMH request. The possible values are: *none*, *all*, or a comma separated list of the following values: *enter*, *exit*.
'ignore_ewmh_struts'::
Ignore strut hinting from clients requesting to reserve space (i.e. task bars).
'center_pseudo_tiled'::
Center pseudo tiled windows into their tiling rectangles. Defaults to 'true'.
'remove_disabled_monitors'::
Consider disabled monitors as disconnected.
'remove_unplugged_monitors'::
Remove unplugged monitors.
'merge_overlapping_monitors'::
Merge overlapping monitors (the bigger remains).
Monitor and Desktop Settings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'top_padding'::
'right_padding'::
'bottom_padding'::
'left_padding'::
Padding space added at the sides of the monitor or desktop.
Desktop Settings
~~~~~~~~~~~~~~~~
'window_gap'::
Size of the gap that separates windows.
Node Settings
~~~~~~~~~~~~~
'border_width'::
Window border width.
'honor_size_hints'::
If 'true', apply ICCCM window size hints to all windows. If 'floating', only apply them to floating and pseudo tiled windows. If 'tiled', only apply them to tiled windows. If 'false', don't apply them. Defaults to 'false'.
Pointer Bindings
----------------
'click_to_focus'::
Focus the window (or the monitor) under the pointer if the value isn't *none*.
'pointer_modifier' + 'button1'::
Move the window under the pointer.
'pointer_modifier' + 'button2'::
Resize the window under the pointer by dragging the nearest side.
'pointer_modifier' + 'button3'::
Resize the window under the pointer by dragging the nearest corner.
The behavior of 'pointer_modifier' + 'button<n>' can be modified through the 'pointer_action<n>' setting.
Events
------
'report'::
See the next section for the description of the format.
'monitor_add <monitor_id> <monitor_name> <monitor_geometry>'::
A monitor is added.
'monitor_rename <monitor_id> <old_name> <new_name>'::
A monitor is renamed.
'monitor_remove <monitor_id>'::
A monitor is removed.
'monitor_swap <src_monitor_id> <dst_monitor_id>'::
A monitor is swapped.
'monitor_focus <monitor_id>'::
A monitor is focused.
'monitor_geometry <monitor_id> <monitor_geometry>'::
The geometry of a monitor changed.
'desktop_add <monitor_id> <desktop_id> <desktop_name>'::
A desktop is added.
'desktop_rename <monitor_id> <desktop_id> <old_name> <new_name>'::
A desktop is renamed.
'desktop_remove <monitor_id> <desktop_id>'::
A desktop is removed.
'desktop_swap <src_monitor_id> <src_desktop_id> <dst_monitor_id> <dst_desktop_id>'::
A desktop is swapped.
'desktop_transfer <src_monitor_id> <src_desktop_id> <dst_monitor_id>'::
A desktop is transferred.
'desktop_focus <monitor_id> <desktop_id>'::
A desktop is focused.
'desktop_activate <monitor_id> <desktop_id>'::
A desktop is activated.
'desktop_layout <monitor_id> <desktop_id> tiled|monocle'::
The layout of a desktop changed.
'node_add <monitor_id> <desktop_id> <ip_id> <node_id>'::
A node is added.
'node_remove <monitor_id> <desktop_id> <node_id>'::
A node is removed.
'node_swap <src_monitor_id> <src_desktop_id> <src_node_id> <dst_monitor_id> <dst_desktop_id> <dst_node_id>'::
A node is swapped.
'node_transfer <src_monitor_id> <src_desktop_id> <src_node_id> <dst_monitor_id> <dst_desktop_id> <dst_node_id>'::
A node is transferred.
'node_focus <monitor_id> <desktop_id> <node_id>'::
A node is focused.
'node_activate <monitor_id> <desktop_id> <node_id>'::
A node is activated.
'node_presel <monitor_id> <desktop_id> <node_id> (dir DIR|ratio RATIO|cancel)'::
A node is preselected.
'node_stack <node_id_1> below|above <node_id_2>'::
A node is stacked below or above another node.
'node_geometry <monitor_id> <desktop_id> <node_id> <node_geometry>'::
The geometry of a window changed.
'node_state <monitor_id> <desktop_id> <node_id> tiled|pseudo_tiled|floating|fullscreen on|off'::
The state of a window changed.
'node_flag <monitor_id> <desktop_id> <node_id> hidden|sticky|private|locked|marked|urgent on|off'::
One of the flags of a node changed.
'node_layer <monitor_id> <desktop_id> <node_id> below|normal|above'::
The layer of a window changed.
'pointer_action <monitor_id> <desktop_id> <node_id> move|resize_corner|resize_side begin|end'::
A pointer action occurred.
Please note that *tspwm* initializes monitors before it reads messages on its socket, therefore the initial monitor events can't be received.
Report Format
-------------
Each report event message is composed of items separated by colons.
Each item has the form '<type><value>' where '<type>' is the first character of the item.
'M<monitor_name>'::
Focused monitor.
'm<monitor_name>'::
Unfocused monitor.
'O<desktop_name>'::
Occupied focused desktop.
'o<desktop_name>'::
Occupied unfocused desktop.
'F<desktop_name>'::
Free focused desktop.
'f<desktop_name>'::
Free unfocused desktop.
'U<desktop_name>'::
Urgent focused desktop.
'u<desktop_name>'::
Urgent unfocused desktop.
'L(T|M)'::
Layout of the focused desktop of a monitor.
'T(T|P|F|=|@)'::
State of the focused node of a focused desktop.
'G(S?P?L?M?)'::
Active flags of the focused node of a focused desktop.
Environment Variables
---------------------
'BSPWM_SOCKET'::
The path of the socket used for the communication between *tspc* and *tspwm*. If it isn't defined, then the following path is used: '/tmp/bspwm<host_name>_<display_number>_<screen_number>-socket'.
Contributors
------------
a <a at tuxpa.in>
Author
------
a <a at tuxpa.in>

5
example.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh

28
go.mod
View File

@ -2,19 +2,23 @@ module tuxpa.in/t/wm
go 1.19
require github.com/alecthomas/kong v0.7.1
replace github.com/jezek/xgb v1.1.0 => ./vend/xgb
require github.com/jezek/xgb v1.1.0 // indirect
replace github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0 => ./vend/xgbutil
require (
github.com/google/uuid v1.3.0 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
modernc.org/libc v1.21.2 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.4.0 // indirect
modernc.org/xau v1.0.13 // indirect
modernc.org/xcb v1.0.13
modernc.org/xdmcp v1.0.14 // indirect
github.com/jezek/xgb v1.1.0
github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0
github.com/stretchr/testify v1.8.4
tuxpa.in/a/zlog v1.61.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/zerolog v1.28.0 // indirect
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

56
go.sum
View File

@ -1,27 +1,29 @@
github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0=
github.com/alecthomas/kong v0.7.1 h1:azoTh0IOfwlAX3qN9sHWTxACE2oV8Bg2gAwBsMwDQY4=
github.com/alecthomas/kong v0.7.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U=
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
modernc.org/libc v1.21.2 h1:V053DgNSpAY+IPrO3XlWqrFKUiQqHyPqG4dsx42Ulck=
modernc.org/libc v1.21.2/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/xau v1.0.13 h1:AEYsSsJFSmkfhSwV6/dx6GoHt02BnZLmMcRT1O8EUOo=
modernc.org/xau v1.0.13/go.mod h1:5ORRqBKlhiUXwoVdM0+ZPy8plvcq0OPOdEgTWyeokhk=
modernc.org/xcb v1.0.13 h1:SIMh1yKsKjkfT/qAxcxSv/W+mRe4RnPzp+XewnD4rS4=
modernc.org/xcb v1.0.13/go.mod h1:M5m1dSVaHvBUf5XUg5Y/b1DZdJs6NK7pYLqsQ+d0swU=
modernc.org/xdmcp v1.0.14 h1:hRCxbYfl75rvOdCmVCPLBRCedClOPjpHiq+tOBzGS3Q=
modernc.org/xdmcp v1.0.14/go.mod h1:TDsH3iMey1HJ3tMCePfTy4dIX6hL/MIVnGREX97nCrE=
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ=
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc=
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
tuxpa.in/a/zlog v1.61.0 h1:7wrS6G4QwpnOmgHRQknrr7IgiMXrfGpekkU0PjM9FhE=
tuxpa.in/a/zlog v1.61.0/go.mod h1:CNpMe8laDHLSypx/DyxfX1S0oyxUydeo3aGTEbtRBhg=

9
readme.md Normal file
View File

@ -0,0 +1,9 @@
# tspwm
basically, i'm trying to rewrite https://github.com/baskerville/bspwm in go
my goal is to implement the features that i use day to day.

181
src/bsp/cfg/cfg.go Normal file
View File

@ -0,0 +1,181 @@
package cfg
import (
"fmt"
"reflect"
"strconv"
"strings"
"sync"
"tuxpa.in/t/wm/src/copies"
)
type Modifier[T any] struct {
Ref T
setters map[string]func(v string) error
getters map[string]func() (string, error)
mu sync.RWMutex
}
func NewModifier[T any](start T) *Modifier[T] {
m := &Modifier[T]{
Ref: start,
setters: map[string]func(v string) error{},
getters: map[string]func() (string, error){},
}
m.setup()
return m
}
func (m *Modifier[T]) Set(k, v string) error {
fn, ok := m.setters[k]
if !ok {
// TODO: some error here
return nil
}
m.mu.Lock()
defer m.mu.Unlock()
return fn(v)
}
func (m *Modifier[T]) GetString(k string) (string, error) {
fn, ok := m.getters[k]
if !ok {
// TODO: some error here
return "", fmt.Errorf("config key '%s' not found", k)
}
m.mu.RLock()
defer m.mu.RUnlock()
return fn()
}
func (m *Modifier[T]) FillDefaults() {
rt := reflect.TypeOf(m.Ref).Elem()
for i := 0; i < rt.NumField(); i++ {
ft := rt.Field(i)
k := ft.Tag.Get("cfg")
dv := ft.Tag.Get("default")
m.Set(k, dv)
}
return
}
func (m *Modifier[T]) setup() {
// grab the value
rv := reflect.ValueOf(m.Ref).Elem()
rt := reflect.TypeOf(m.Ref).Elem()
for i := 0; i < rv.NumField(); i++ {
fv := rv.Field(i)
ft := rt.Field(i)
k := ft.Tag.Get("cfg")
kind := ft.Type.Elem().Kind()
switch kind {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
m.setters[k] = func(v string) (err error) {
var val uint64
if v != "" {
val, err = strconv.ParseUint(v, 10, 64)
if err != nil {
return copies.NewInvalidValueErr(k, v)
}
}
if fv.IsNil() {
fv.Set(reflect.New(ft.Type.Elem()))
}
fv.Elem().SetUint(uint64(val))
return nil
}
m.getters[k] = func() (string, error) {
if fv.IsNil() {
return "", nil
}
return fmt.Sprintf("%v", fv.Elem()), nil
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
m.setters[k] = func(v string) (err error) {
var val int
if v != "" {
val, err = strconv.Atoi(v)
if err != nil {
return copies.NewInvalidValueErr(k, v)
}
}
if fv.IsNil() {
fv.Set(reflect.New(ft.Type.Elem()))
}
fv.Elem().SetInt(int64(val))
return nil
}
m.getters[k] = func() (string, error) {
if fv.IsNil() {
return "", nil
}
return fmt.Sprintf("%v", fv.Elem()), nil
}
case reflect.Float32, reflect.Float64:
m.setters[k] = func(v string) (err error) {
var val float64
if v != "" {
val, err = strconv.ParseFloat(v, 10)
if err != nil {
return copies.NewInvalidValueErr(k, v)
}
}
if fv.IsNil() {
fv.Set(reflect.New(ft.Type.Elem()))
}
fv.Elem().SetFloat(val)
return nil
}
m.getters[k] = func() (string, error) {
if fv.IsNil() {
return "", nil
}
return fmt.Sprintf("%v", fv.Elem()), nil
}
case reflect.Bool:
m.setters[k] = func(v string) error {
var b bool
switch strings.ToLower(v) {
case "true", "on":
b = true
case "false", "off", "":
b = false
default:
return copies.NewInvalidValueErr(k, v)
}
if fv.IsNil() {
fv.Set(reflect.New(ft.Type.Elem()))
}
fv.Elem().SetBool(b)
return nil
}
m.getters[k] = func() (string, error) {
if fv.IsNil() {
return "", nil
}
return fmt.Sprintf("%v", fv.Elem()), nil
}
case reflect.String:
m.setters[k] = func(v string) error {
if fv.IsNil() {
fv.Set(reflect.New(ft.Type.Elem()))
}
fv.Elem().SetString(strings.TrimSpace(v))
return nil
}
m.getters[k] = func() (string, error) {
if fv.IsNil() {
return "", nil
}
return fmt.Sprintf("%v", fv.Elem()), nil
}
}
}
}
func getStructTag(f reflect.StructField, tagName string) string {
return string(f.Tag.Get(tagName))
}

37
src/bsp/cfg/cfg_test.go Normal file
View File

@ -0,0 +1,37 @@
package cfg_test
import (
"testing"
"github.com/stretchr/testify/assert"
"tuxpa.in/t/wm/src/bsp/cfg"
)
type E int
const (
D0 E = 0
D1
)
type s1 struct {
A *int `cfg:"a" default:"4"`
B *bool `cfg:"b"`
C *string `cfg:"c" default:"crabs"`
D *E `cfg:"d"`
}
func TestModifierS1(t *testing.T) {
s := &s1{}
m := cfg.NewModifier(s)
m.Set("b", "on")
assert.EqualValues(t, *m.Ref.B, true)
assert.Nil(t, m.Ref.D)
m.Set("d", "442")
assert.EqualValues(t, *m.Ref.D, 442)
m.FillDefaults()
assert.EqualValues(t, *m.Ref.C, "crabs")
}

17
src/bsp/cfg/helper.go Normal file
View File

@ -0,0 +1,17 @@
package cfg
// Read from the refs of n modifiers with lock protection
func Read[T any, V any](fn func(*T) *V, xs ...*Modifier[T]) V {
for _, v := range xs {
if v == nil {
continue
}
v.mu.RLock()
vv := fn(&v.Ref)
v.mu.RUnlock()
if vv != nil {
return *vv
}
}
return *new(V)
}

77
src/bsp/client.go Normal file
View File

@ -0,0 +1,77 @@
package bsp
import (
"github.com/jezek/xgb/xproto"
"github.com/jezek/xgbutil/ewmh"
"github.com/jezek/xgbutil/icccm"
"github.com/jezek/xgbutil/xwindow"
"tuxpa.in/a/zlog/log"
)
type Client struct {
win *xwindow.Window
winId xproto.Window
name string
}
// registers a client to be obtained later
func (xwm *XWM) RegisterClient(wid xproto.Window) *Client {
c := &Client{
winId: wid,
}
xwm.X.Grab()
defer xwm.X.Ungrab()
// dont duplicate registration
if xwm.FindClient(wid) != nil {
log.Trace().Any("id", wid).Msg("duplicate client registration")
return nil
}
// ok this is a new one, so we need to make a new client
c.win = xwindow.New(xwm.X, wid)
if _, err := c.win.Geometry(); err != nil {
log.Err(err).Any("id", wid).Msg("get geometry")
return nil
}
err := xproto.ChangeSaveSetChecked(xwm.X.Conn(), xproto.SetModeInsert, c.winId).Check()
if err != nil {
log.Err(err).Any("id", wid).Msg("change save set checked")
return nil
}
xwm.initClient(c)
return c
}
func (xwm *XWM) initClient(c *Client) {
xwm.updateName(c)
}
func (xwm *XWM) updateName(c *Client) {
newName := func() (n string) {
n, _ = ewmh.WmNameGet(xwm.X, c.winId)
if len(n) > 0 {
return
}
n, _ = icccm.WmNameGet(xwm.X, c.winId)
if len(n) > 0 {
return
}
return
}()
if newName != c.name {
c.name = newName
ewmh.WmVisibleNameSet(xwm.X, c.winId, c.name)
}
}
func (xwm *XWM) FindClient(wid xproto.Window) (c *Client) {
xwm.W.View(func() error {
val, ok := xwm.W.Clients[wid]
if ok {
c = val
}
return nil
})
return
}

3
src/bsp/color.go Normal file
View File

@ -0,0 +1,3 @@
package bsp
type ColorCode string

108
src/bsp/config.go Normal file
View File

@ -0,0 +1,108 @@
package bsp
type AUTOMATIC_SCHEME_T int
const (
SCHEME_LONGEST_SIDE AUTOMATIC_SCHEME_T = iota
SCHEME_ALTERNATE
SCHEME_SPIRAL
)
type HONOR_SIZE_HINTS_MODE_T int
const (
HONOR_SIZE_HINTS_NO HONOR_SIZE_HINTS_MODE_T = iota
HONOR_SIZE_HINTS_YES
HONOR_SIZE_HINTS_FLOATING
HONOR_SIZE_HINTS_TILED
HONOR_SIZE_HINTS_DEFAULT
)
type TIGHTNESS_T int
const (
TIGHTNESS_LOW TIGHTNESS_T = iota
TIGHTNESS_HIGH
)
type CHILD_POLARITY_T int
const (
FIRST_CHILD CHILD_POLARITY_T = iota
SECOND_CHILD
)
type Config struct {
// pointer options
PointerAction1 *string `cfg:"pointer_action1"`
PointerAction2 *string `cfg:"pointer_action2"`
PointerAction3 *string `cfg:"pointer_action3"`
PointerFollowsMonitor *bool `cfg:"pointer_follows_monitor"`
FocusFollowsPointer *bool `cfg:"focus_follows_pointer"`
PointerFollowsFocus *bool `cfg:"pointer_follows_focus"`
PointerMotionInterval *string `cfg:"pointer_motion_interval"`
// control
PointerModifier *string `cfg:"pointer_modifier" default:"XCB_MOD_MASK_4"`
StatusPrefix *string `cfg:"status_prefix" default:"W"`
ExternalRulesCommand *string `cfg:"external_rules_command" default:""`
// click
ClickToFocus *string `cfg:"click_to_focus" default:"XCB_BUTTON_INDEX_1"`
SwallowFirstClick *bool `cfg:"swallow_first_click"`
// ewmh
IgnoreEwmhFocus *bool `cfg:"ignore_ewmh_focus"`
IgnoreEwmhFullscreen *bool `cfg:"ignore_ewmh_fullscreen"`
IgnoreEwmhStruts *bool `cfg:"ignore_ewmh_struts"`
// monocole
PreselFeedback *bool `cfg:"presel_feedback" default:"true"`
GaplessMonocle *bool `cfg:"gapless_monocle"`
BorderlessMonocle *bool `cfg:"borderless_monocle"`
SingleMonocle *bool `cfg:"single_monocle"`
// padding
TopPadding *int `cfg:"top_padding"`
BottomPadding *int `cfg:"bottom_padding"`
LeftPadding *int `cfg:"left_padding"`
RightPadding *int `cfg:"right_padding"`
// monocle padding
TopMonoclePadding *int `cfg:"top_monocle_padding"`
BottomMonoclePadding *int `cfg:"bottom_monocle_padding"`
LeftMonoclePadding *int `cfg:"left_monocle_padding"`
RightMonoclePadding *int `cfg:"right_monocle_padding"`
// splits
SplitRatio *float64 `cfg:"split_ratio" default:"0.5"`
AutomaticScheme *AUTOMATIC_SCHEME_T `cfg:"automatic_scheme"`
RemovalAdjustment *string `cfg:"removal_adjustment"`
// gap
WindowGap *int `cfg:"window_gap"`
BorderWidth *int `cfg:"border_width"`
BorderlessSingleton *bool `cfg:"borderless_singleton"`
//colors
ActiveBorderColor *ColorCode `cfg:"active_border_color"`
PreselFeedbackColor *ColorCode `cfg:"presel_feedback_color"`
FocusedBorderColor *ColorCode `cfg:"focused_border_color"`
NormalBorderColor *ColorCode `cfg:"normal_border_color"`
//monitor
RemoveDisabledMonitors *bool `cfg:"remove_disabled_monitors"`
RemoveUnpluggedMonitors *bool `cfg:"remove_unplugged_monitors"`
MergeOverlappingMonitors *bool `cfg:"merge_overlapping_monitors"`
// more
CenterPseudoTiled *bool `cfg:"center_pseudo_tiled" default:"true"`
HonorSizeHints *HONOR_SIZE_HINTS_MODE_T `cfg:"honor_size_hints"`
MappingEventsCount *int `cfg:"mapping_events_count" default:"1"`
//etc
DirectionalFocusTightness *TIGHTNESS_T `cfg:"directional_focus_tightness"`
InitialPolarity *CHILD_POLARITY_T `cfg:"initial_polarity"`
}

179
src/bsp/loop.go Normal file
View File

@ -0,0 +1,179 @@
package bsp
import (
"context"
"fmt"
"github.com/jezek/xgb/randr"
"github.com/jezek/xgb/xinerama"
"github.com/jezek/xgb/xproto"
"github.com/jezek/xgbutil"
"github.com/jezek/xgbutil/keybind"
"github.com/jezek/xgbutil/mousebind"
"github.com/jezek/xgbutil/xevent"
"github.com/jezek/xgbutil/xwindow"
"tuxpa.in/a/zlog/log"
)
type XWM struct {
W *WM
X *xgbutil.XUtil
}
func NewXWM(w *WM, x *xgbutil.XUtil) *XWM {
xwm := &XWM{
W: w,
X: x,
}
return xwm
}
func (xwm *XWM) initBinding(ctx context.Context) error {
randr.Init(xwm.X.Conn())
xinerama.Init(xwm.X.Conn())
keybind.Initialize(xwm.X)
mousebind.Initialize(xwm.X)
return nil
}
func (xwm *XWM) Start(ctx context.Context) error {
if err := xwm.initBinding(ctx); err != nil {
return err
}
if err := xwm.initMonitors(ctx); err != nil {
return err
}
if err := xwm.initRoot(ctx); err != nil {
return err
}
if err := xwm.initExistingWindows(ctx); err != nil {
return err
}
// for {
// err := xwm.run(ctx)
// if err != nil {
// return err
// }
// }
return nil
}
func (xwm *XWM) initMonitors(ctx context.Context) error {
xwm.X.Grab()
defer xwm.X.Ungrab()
res, err := randr.GetScreenResources(xwm.X.Conn(), xwm.X.RootWin()).Reply()
if err != nil {
return err
}
for _, output := range res.Outputs {
info, err := randr.GetOutputInfo(xwm.X.Conn(), output, res.ConfigTimestamp).Reply()
if err != nil {
log.Err(err).Any("output", output).Msg("fail get output info")
continue
}
monitorId, err := xproto.NewMonitorId(xwm.X.Conn())
if err != nil {
return err
}
xwm.W.Mutate(func() error {
xwm.W.Monitors = append(xwm.W.Monitors, &Monitor{
Name: string(info.Name),
Id: monitorId,
})
return nil
})
}
return nil
}
func (xwm *XWM) initRoot(ctx context.Context) error {
// important masks
evMasks := xproto.EventMaskPropertyChange |
xproto.EventMaskFocusChange |
xproto.EventMaskButtonPress |
xproto.EventMaskButtonRelease |
xproto.EventMaskStructureNotify |
xproto.EventMaskSubstructureNotify |
xproto.EventMaskSubstructureRedirect |
xproto.EventMaskPointerMotion
err := xwindow.New(xwm.X, xwm.X.RootWin()).Listen(evMasks)
if err != nil {
return fmt.Errorf("cant listen to root events: %w", err)
}
// TODO: need to add listener when root window changes size
// attach root window
if err = xwm.W.Mutate(func() error {
xwm.W.Root = xwindow.New(xwm.X, xwm.X.RootWin())
return nil
}); err != nil {
return err
}
// map requests
xevent.MapRequestFun(func(X *xgbutil.XUtil, ev xevent.MapRequestEvent) {
}).Connect(xwm.X, xwm.W.Root.Id)
captureCombos := []string{
"Mod4-1",
"Mod3-1",
"Mod2-1",
"Mod1-1",
}
for _, combo := range captureCombos {
err := mousebind.ButtonPressFun(func(xu *xgbutil.XUtil, event xevent.ButtonPressEvent) {
log.Trace().Str("name", event.String()).Str("mod", combo).Msg("press")
}).Connect(xwm.X, xwm.X.RootWin(), combo, false, true)
if err != nil {
return err
}
err = mousebind.ButtonReleaseFun(func(xu *xgbutil.XUtil, event xevent.ButtonReleaseEvent) {
log.Trace().Str("name", event.String()).Str("mod", combo).Msg("depress")
}).Connect(xwm.X, xwm.X.RootWin(), combo, false, true)
if err != nil {
return err
}
}
xevent.ConfigureNotifyFun(func(xu *xgbutil.XUtil, event xevent.ConfigureNotifyEvent) {
log.Trace().Str("name", event.String()).Msg("notify event")
}).Connect(xwm.X, xwm.X.RootWin())
return nil
}
func (xwm *XWM) initExistingWindows(ctx context.Context) error {
tree, err := xproto.QueryTree(xwm.X.Conn(), xwm.X.RootWin()).Reply()
if err != nil {
return err
}
for _, v := range tree.Children {
if v == xwm.X.Dummy() {
continue
}
attrs, err := xproto.GetWindowAttributes(xwm.X.Conn(), v).Reply()
if err != nil {
continue
}
if attrs.MapState == xproto.MapStateUnmapped {
continue
}
c := xwm.RegisterClient(v)
if c != nil {
log.Printf("%+v", c)
}
}
return nil
}
func (xwm *XWM) run(ctx context.Context) error {
return nil
}

103
src/bsp/wm.go Normal file
View File

@ -0,0 +1,103 @@
package bsp
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"sync"
"github.com/jezek/xgb/randr"
"github.com/jezek/xgb/xproto"
"github.com/jezek/xgbutil/xwindow"
"tuxpa.in/t/wm/src/bsp/cfg"
)
type WM struct {
Desktops []*Desktop
Monitors []*Monitor
Cfg *cfg.Modifier[*Config]
Root *xwindow.Window
Clients map[xproto.Window]*Client
mu sync.RWMutex
}
type Desktop struct {
Name string
Monitor *Monitor
Cfg *cfg.Modifier[*Config]
}
type Monitor struct {
Name string
Id xproto.Monitor
raw *randr.GetOutputInfoReply
Cfg *cfg.Modifier[*Config]
}
func (m *Monitor) HexId() []byte {
o := bytes.NewBuffer([]byte("0x"))
binary.Write(hex.NewEncoder(o), binary.LittleEndian, m.Id)
return o.Bytes()
}
type Node struct {
Name string
Cfg *cfg.Modifier[*Config]
}
func (w *WM) Mutate(fn func() error) error {
w.mu.Lock()
defer w.mu.Unlock()
if fn != nil {
return fn()
}
return nil
}
func (w *WM) View(fn func() error) error {
w.mu.RLock()
defer w.mu.RUnlock()
if fn != nil {
return fn()
}
return nil
}
func (w *WM) AddDesktop(name string, monitorName string) error {
return w.Mutate(func() error {
var monitor *Monitor
for _, v := range w.Monitors {
if v.Name == monitorName {
monitor = v
}
}
if monitor == nil {
return fmt.Errorf("invalid descriptor found in '%s'", monitorName)
}
w.Desktops = append(w.Desktops, &Desktop{
Name: name,
Cfg: cfg.NewModifier(&Config{}),
Monitor: monitor,
})
return nil
})
}
func NewWM() *WM {
w := &WM{
Cfg: cfg.NewModifier(&Config{}),
Clients: make(map[xproto.Window]*Client),
}
w.Cfg.FillDefaults()
return w
}

View File

@ -1,17 +0,0 @@
package bspc
import "tuxpa.in/t/wm/src/sock"
type NODE_SEL string
type Node struct {
Sel NODE_SEL
}
func (n *Node) Focus(node NODE_SEL) error {
s, err := sock.Default()
if err != nil {
return err
}
return s.Send("node", "-l")
}

View File

@ -1,12 +0,0 @@
package cmd
import "context"
type Context struct {
Debug bool
context.Context
}
type Cmd interface {
Run(ctx *Context) error
}

67
src/copies/errors.go Normal file
View File

@ -0,0 +1,67 @@
package copies
import "fmt"
type ErrInvalidArgumentCount struct {
Name string
Value int
}
func (u *ErrInvalidArgumentCount) Error() string {
return fmt.Sprintf(`Was expecting %s arguments, received %d.\n`, u.Name, u.Value)
}
type ErrInvalidValue struct {
Name string
Value string
}
func (u *ErrInvalidValue) Error() string {
return fmt.Sprintf(`%s: Invalid value: '%s'.\n`, u.Name, u.Value)
}
func NewInvalidValueErr(n, v string) *ErrInvalidValue {
return &ErrInvalidValue{
Name: n,
Value: v,
}
}
type ErrUnknownDomainOrCommand struct {
Str string
}
func (u *ErrUnknownDomainOrCommand) Error() string {
return fmt.Sprintf(`Unknown Command: '%s'.`, u.Str)
}
type ErrUnknownCommand struct {
Cmd string
}
func (u *ErrUnknownCommand) Error() string {
return fmt.Sprintf(`Unknown Command: '%s'.`, u.Cmd)
}
type ErrMissingArguments struct {
}
func (m *ErrMissingArguments) Error() string {
return `Missing arguments`
}
type ErrNoCommandsGiven struct {
}
func (m *ErrNoCommandsGiven) Error() string {
return `No commands given`
}
type ErrTODO struct {
Kind string
Name string
}
func (e *ErrTODO) Error() string {
return fmt.Sprintf(`'%s' not implemented: '%s'.`, e.Kind, e.Name)
}

19
src/handler/domain.go Normal file
View File

@ -0,0 +1,19 @@
package handler
import (
"tuxpa.in/t/wm/src/bsp"
"tuxpa.in/t/wm/src/sock"
)
type Domain interface {
DomainRunner
DomainState
}
type DomainRunner interface {
Run(*sock.Msg) ([]byte, error)
}
type DomainState interface {
SetXWM(*bsp.XWM)
}

View File

@ -0,0 +1,118 @@
package domains
import (
"fmt"
"strings"
"tuxpa.in/t/wm/src/bsp"
"tuxpa.in/t/wm/src/copies"
"tuxpa.in/t/wm/src/sock"
)
type inject struct {
xwm
}
type monitor_sel struct {
monitorsel string
}
func (n *monitor_sel) tryMonitorSel(msg *sock.Msg) (bool, error) {
// first split the str by .
if !msg.HasNext() {
return false, nil
}
str := msg.Peek()
splt := strings.Split(str, ".")
switch splt[0] {
case "any", "first_ancestor",
"last", "newest", "older", "newer",
"focused", "pointed", "biggest", "smallest":
n.monitorsel = str
return true, nil
}
return false, nil
}
type desktop_sel struct {
desktopsel string
}
func (n *desktop_sel) readDesktopSel(msg *sock.Msg) error {
if !msg.HasNext() {
return &copies.ErrMissingArguments{}
}
str := msg.Next()
switch str {
case "any", "first_ancestor",
"last", "newest", "older", "newer",
"focused", "pointed", "biggest", "smallest":
n.desktopsel = str
}
return nil
}
type node_sel struct {
nodesel string
}
func (n *node_sel) readNodeSel(msg *sock.Msg) error {
if !msg.HasNext() {
return &copies.ErrMissingArguments{}
}
str := msg.Next()
switch str {
case "any", "first_ancestor",
"last", "newest", "older", "newer",
"focused", "pointed", "biggest", "smallest":
n.nodesel = str
}
return nil
}
type cmd struct {
Command string
}
func (n *cmd) readCommand(msg *sock.Msg, c string) (bool, error) {
if n.Command == "" {
n.Command = c
return msg.HasNext(), nil
}
return false, fmt.Errorf("multiple commands given")
}
type commandParser struct {
routes map[string]string
}
func newCommandParser() *commandParser {
return &commandParser{
routes: map[string]string{},
}
}
func (c *commandParser) addDef(name string, aliases ...string) *commandParser {
c.routes["--"+strings.TrimSpace(name)] = name
for _, a := range aliases {
c.routes[strings.TrimSpace(a)] = name
}
return c
}
func (c *commandParser) parse(n *cmd, msg *sock.Msg) (bool, error) {
arg := msg.Next()
cmd, ok := c.routes[arg]
if !ok {
return false, fmt.Errorf(`unknown option: '%s'`, arg)
}
return n.readCommand(msg, cmd)
}
type xwm struct {
XWM *bsp.XWM
}
func (x *xwm) SetXWM(z *bsp.XWM) {
x.XWM = z
}

View File

@ -0,0 +1,66 @@
package domains
import (
"tuxpa.in/a/zlog/log"
"tuxpa.in/t/wm/src/copies"
"tuxpa.in/t/wm/src/sock"
)
type Config struct {
UseNames bool
configKey string
configValue string
cmd
inject
}
func (n *Config) Run(msg *sock.Msg) ([]byte, error) {
if !msg.HasNext() {
return nil, &copies.ErrMissingArguments{}
}
for {
ok, err := n.parse(msg)
if err != nil {
return nil, err
}
if !ok {
break
}
}
if n.configValue == "" {
str, err := n.XWM.W.Cfg.GetString(n.configKey)
return []byte(str), err
}
err := n.XWM.W.Cfg.Set(n.configKey, n.configValue)
if err != nil {
return nil, err
}
log.Trace().Str("key", n.configKey).Any("string", n.configValue).Msg("config set")
return nil, nil
}
func (n *Config) parse(msg *sock.Msg) (bool, error) {
if !msg.HasNext() {
return false, &copies.ErrMissingArguments{}
}
arg := msg.Next()
switch arg {
case "-d":
return n.readCommand(msg, "desktops")
case "-m":
return n.readCommand(msg, "monitors")
case "-n":
return n.readCommand(msg, "nodes")
default:
n.configKey = arg
if msg.HasNext() {
n.configValue = msg.Next()
}
if msg.HasNext() {
return false, &copies.ErrInvalidArgumentCount{Name: "2 or 3", Value: len(msg.Args())}
}
return false, nil
}
}

View File

@ -0,0 +1,20 @@
package domains
import (
"strings"
"tuxpa.in/t/wm/src/copies"
"tuxpa.in/t/wm/src/sock"
)
type Echo struct {
inject
}
func (n Echo) Run(msg *sock.Msg) ([]byte, error) {
if !msg.HasNext() {
return nil, &copies.ErrMissingArguments{}
}
out := strings.Join(msg.Args(), " ")
return []byte(out), nil
}

View File

@ -0,0 +1,16 @@
package domains
import (
"tuxpa.in/t/wm/src/bsp"
"tuxpa.in/t/wm/src/sock"
)
type Lambda struct {
Fn func(x *bsp.XWM, msg *sock.Msg) ([]byte, error)
inject
}
func (n *Lambda) Run(msg *sock.Msg) ([]byte, error) {
return n.Fn(n.XWM, msg)
}

View File

@ -0,0 +1,72 @@
package domains
import (
"fmt"
"tuxpa.in/t/wm/src/copies"
"tuxpa.in/t/wm/src/sock"
)
type Monitor struct {
UseNames bool
inject
cmd
monitor_sel
}
func (n *Monitor) Run(msg *sock.Msg) ([]byte, error) {
if !msg.HasNext() {
return nil, &copies.ErrMissingArguments{}
}
for {
ok, err := n.parse(msg)
if err != nil {
return nil, err
}
if !ok {
break
}
}
switch n.Command {
default:
return nil, &copies.ErrTODO{
Kind: "command",
Name: n.Command,
}
}
}
func (n *Monitor) parse(msg *sock.Msg) (bool, error) {
if !msg.HasNext() {
return false, &copies.ErrMissingArguments{}
}
ok, err := n.tryMonitorSel(msg)
if err != nil {
return false, err
}
if ok {
msg.Next()
}
arg := msg.Next()
switch arg {
case "--add-desktops", "-a":
return n.readCommand(msg, "add-desktops")
case "--focus", "-f":
return n.readCommand(msg, "focus")
case "--rectangle", "-g":
return n.readCommand(msg, "rectangle")
case "--remove", "-r":
return n.readCommand(msg, "remove")
case "--reorder-desktops", "-o":
return n.readCommand(msg, "reorder-desktops")
case "--reset-desktops", "-d":
return n.readCommand(msg, "reset-desktops")
case "--swap", "-s":
return n.readCommand(msg, "swap")
default:
return false, fmt.Errorf(`unknown option: '%s'`, arg)
}
}

View File

@ -0,0 +1,64 @@
package domains
import (
"fmt"
"tuxpa.in/t/wm/src/copies"
"tuxpa.in/t/wm/src/sock"
)
type Node struct {
UseNames bool
inject
cmd
}
func (n *Node) Run(msg *sock.Msg) ([]byte, error) {
if !msg.HasNext() {
return nil, &copies.ErrMissingArguments{}
}
for {
ok, err := n.parse(msg)
if err != nil {
return nil, err
}
if !ok {
break
}
}
switch n.Command {
default:
return nil, &copies.ErrTODO{
Kind: "command",
Name: n.Command,
}
}
}
func (n *Node) parse(msg *sock.Msg) (bool, error) {
if !msg.HasNext() {
return false, &copies.ErrMissingArguments{}
}
arg := msg.Next()
switch arg {
case "--desktop", "-d":
case "--desktops", "-D":
return n.readCommand(msg, "desktops")
case "--monitor", "-m":
case "--monitors", "-M":
return n.readCommand(msg, "monitors")
case "--names":
n.UseNames = true
case "--node", "-n":
case "--nodes", "-N":
return n.readCommand(msg, "nodes")
case "--tree", "-T":
return n.readCommand(msg, "tree")
default:
return false, fmt.Errorf(`unknown option: '%s'`, arg)
}
return msg.HasNext(), nil
}

View File

@ -0,0 +1,86 @@
package domains
import (
"bytes"
"fmt"
"tuxpa.in/t/wm/src/copies"
"tuxpa.in/t/wm/src/sock"
)
type Query struct {
UseNames bool
inject
cmd
}
func (n *Query) Run(msg *sock.Msg) ([]byte, error) {
if !msg.HasNext() {
return nil, &copies.ErrMissingArguments{}
}
for {
ok, err := n.parse(msg)
if err != nil {
return nil, err
}
if !ok {
break
}
}
switch n.Command {
case "desktops":
return n.desktops(msg)
case "monitors":
return n.monitors(msg)
default:
return nil, &copies.ErrTODO{}
}
}
func (n *Query) monitors(msg *sock.Msg) ([]byte, error) {
o := new(bytes.Buffer)
n.XWM.W.View(func() error {
for _, v := range n.XWM.W.Monitors {
if n.UseNames {
o.WriteString(v.Name)
} else {
o.Write(v.HexId())
}
o.WriteRune('\n')
}
return nil
})
return o.Bytes(), nil
}
func (n *Query) desktops(msg *sock.Msg) ([]byte, error) {
o := new(bytes.Buffer)
o.WriteString("hi there")
return o.Bytes(), nil
}
var queryParser = newCommandParser().
addDef("desktop", "-d", "--desktops")
func (n *Query) parse(msg *sock.Msg) (bool, error) {
if !msg.HasNext() {
return false, &copies.ErrMissingArguments{}
}
arg := msg.Next()
switch arg {
case "--desktop", "-d", "--desktops", "-D":
return n.readCommand(msg, "desktops")
case "--monitor", "-m", "--monitors", "-M":
return n.readCommand(msg, "monitors")
case "--names":
n.UseNames = true
case "--node", "-n", "--nodes", "-N":
return n.readCommand(msg, "nodes")
case "--tree", "-T":
return n.readCommand(msg, "tree")
default:
return false, fmt.Errorf(`unknown option: '%s'`, arg)
}
return msg.HasNext(), nil
}

View File

@ -0,0 +1,14 @@
package domains
import (
"tuxpa.in/t/wm/src/copies"
"tuxpa.in/t/wm/src/sock"
)
type Todo struct {
inject
}
func (n Todo) Run(msg *sock.Msg) ([]byte, error) {
return nil, &copies.ErrTODO{}
}

67
src/handler/domains/wm.go Normal file
View File

@ -0,0 +1,67 @@
package domains
import (
"fmt"
"tuxpa.in/t/wm/src/copies"
"tuxpa.in/t/wm/src/sock"
)
type Wm struct {
UseNames bool
inject
cmd
}
func (n *Wm) Run(msg *sock.Msg) ([]byte, error) {
if !msg.HasNext() {
return nil, &copies.ErrMissingArguments{}
}
for {
ok, err := n.parse(msg)
if err != nil {
return nil, err
}
if !ok {
break
}
}
switch n.Command {
default:
return nil, &copies.ErrTODO{
Kind: "command",
Name: n.Command,
}
}
}
func (n *Wm) parse(msg *sock.Msg) (bool, error) {
if !msg.HasNext() {
return false, &copies.ErrMissingArguments{}
}
arg := msg.Next()
switch arg {
case "--add-monitor", "-a":
return n.readCommand(msg, "add-monitor")
case "--adopt-orphans", "-o":
return n.readCommand(msg, "adopt-orphans")
case "--dump-state", "-d":
return n.readCommand(msg, "dump-state")
case "--get-status", "-g":
return n.readCommand(msg, "get-status")
case "--load-state", "-l":
return n.readCommand(msg, "load-state")
case "--record-history", "-h":
return n.readCommand(msg, "record-history")
case "--reorder-monitors", "-O":
return n.readCommand(msg, "reorder-monitors")
case "--restart", "-r":
return n.readCommand(msg, "restart")
default:
return false, fmt.Errorf(`unknown option: '%s'`, arg)
}
}

72
src/handler/handler.go Normal file
View File

@ -0,0 +1,72 @@
package handler
import (
"fmt"
"tuxpa.in/t/wm/src/bsp"
"tuxpa.in/t/wm/src/copies"
"tuxpa.in/t/wm/src/sock"
)
type Handler struct {
XWM *bsp.XWM
domains map[string]func() Domain
}
func AddDomain[T any, PT interface {
Domain
*T
}](h *Handler, name string) {
if h.domains == nil {
h.domains = map[string]func() Domain{}
}
h.domains[name] = func() Domain {
domain := PT(new(T))
domain.SetXWM(h.XWM)
return domain
}
}
func (h *Handler) AddDomainFunc(name string, fn func() Domain) {
if h.domains == nil {
h.domains = map[string]func() Domain{}
}
h.domains[name] = func() Domain {
d := fn()
d.SetXWM(h.XWM)
return d
}
}
func (h *Handler) Run(msg *sock.Msg) {
resp, err := h.run(msg)
if msg.Err(err) {
return
}
err = msg.Reply(resp)
if msg.Err(err) {
return
}
msg.Reply(nil)
}
func (h *Handler) run(msg *sock.Msg) ([]byte, error) {
if !msg.HasNext() {
return nil, &copies.ErrMissingArguments{}
}
cmd := msg.Next()
d, ok := h.domains[cmd]
if !ok {
return nil, &copies.ErrUnknownDomainOrCommand{Str: cmd}
}
return h.runDomain(cmd, msg, d())
}
func (h *Handler) runDomain(name string, msg *sock.Msg, d DomainRunner) ([]byte, error) {
str, err := d.Run(msg)
if err != nil {
return nil, fmt.Errorf("%s: %w", name, err)
}
return str, nil
}

75
src/sock/msg.go Normal file
View File

@ -0,0 +1,75 @@
package sock
import (
"bufio"
"net"
"sync/atomic"
)
type Msg struct {
c *net.UnixConn
args []string
closed atomic.Bool
cur int
}
func (m *Msg) HasNext() bool {
return m.Has(0)
}
func (m *Msg) Peek() string {
return m.Get(0)
}
func (m *Msg) Next() string {
if m.Has(0) {
m.cur = m.cur + 1
return m.args[m.cur-1]
}
return ""
}
func (m *Msg) Has(i int) bool {
return len(m.args) > (i + m.cur)
}
func (m *Msg) Get(i int) string {
if m.Has(i) {
return m.args[i+m.cur]
}
return ""
}
func (m *Msg) Args() []string {
if m.HasNext() {
return m.args[m.cur:]
}
return nil
}
func (m *Msg) Err(e error) bool {
if e == nil {
return false
}
if !m.closed.CompareAndSwap(false, true) {
return true
}
defer m.c.Close()
wr := bufio.NewWriter(m.c)
wr.Write([]byte{7})
wr.Write([]byte(e.Error()))
wr.Write([]byte("\n"))
wr.Flush()
return true
}
func (m *Msg) Reply(xs []byte) error {
if !m.closed.CompareAndSwap(false, true) {
return nil
}
defer m.c.Close()
wr := bufio.NewWriter(m.c)
_, err := wr.Write(xs)
if err != nil {
return err
}
if len(xs) != 0 && xs[len(xs)-1] != '\n' {
wr.Write([]byte("\n"))
}
wr.Write([]byte{0})
wr.Flush()
return nil
}

65
src/sock/server.go Normal file
View File

@ -0,0 +1,65 @@
package sock
import (
"bytes"
"fmt"
"net"
"strings"
)
type SockListener struct {
l *net.UnixListener
ch chan *Msg
}
func (s *SockListener) Msg() <-chan *Msg {
return s.ch
}
func Server(addr *net.UnixAddr) (*SockListener, error) {
s := &SockListener{}
s.ch = make(chan *Msg, 1)
conn, err := net.ListenUnix("unix", addr)
if err != nil {
return nil, err
}
s.l = conn
go func() {
for {
conn, err := s.l.AcceptUnix()
if err != nil {
continue
}
go func() {
err = s.acceptConn(conn)
if err != nil {
fmt.Printf("fail accept conn: %s\n", err)
}
}()
}
}()
return s, nil
}
func (s *SockListener) Close() error {
err := s.l.Close()
return err
}
func (s *SockListener) acceptConn(c *net.UnixConn) error {
msg := &Msg{}
bts := make([]byte, 4096)
n, err := c.Read(bts)
if err != nil {
return err
}
bts = bts[:n]
splt := bytes.Split(bts, []byte{0})
for _, v := range splt {
if len(strings.TrimSpace(string(v))) > 0 {
msg.args = append(msg.args, string(v))
}
}
msg.c = c
s.ch <- msg
return nil
}

View File

@ -1,6 +1,7 @@
package sock
import (
"bufio"
"fmt"
"io"
"net"
@ -11,28 +12,28 @@ import (
const SOCKET_PATH_TPL = "/tmp/bspwm%s_%d_%d-socket"
const SOCKET_ENV_VAR = "BSPWM_SOCKET"
type Sock struct {
C net.Conn
C *net.UnixConn
}
func (s *Sock) Send(args ...string) (string, error) {
if len(args) == 0 {
return "", s.C.Close()
}
wr := bufio.NewWriter(s.C)
for _, msg := range args {
_, err := s.C.Write([]byte(msg))
if err != nil {
return "", err
}
_, err = s.C.Write([]byte{0})
if err != nil {
return "", err
}
wr.WriteString(msg)
wr.WriteByte(0)
}
wr.Flush()
s.C.CloseWrite()
bts, err := io.ReadAll(s.C)
if err != nil {
return "", err
}
s.C.CloseRead()
if len(bts) > 0 {
if bts[0] == 7 {
return "", fmt.Errorf(string(bts[1:]))
@ -41,19 +42,23 @@ func (s *Sock) Send(args ...string) (string, error) {
return string(bts), nil
}
func Default() (*Sock, error) {
return New(os.Getenv("BSPWM_SOCKET"))
}
func New(path string) (*Sock, error) {
func Client(path string) (*Sock, error) {
xc, err := xgb.NewConn()
if err != nil {
return nil, err
}
defer xc.Close()
if path == "" {
path = os.Getenv(SOCKET_ENV_VAR)
}
if path == "" {
path = fmt.Sprintf(SOCKET_PATH_TPL, "", xc.DisplayNumber, xc.DefaultScreen)
}
conn, err := net.Dial("unix", path)
addr, err := net.ResolveUnixAddr("unix", path)
if err != nil {
return nil, err
}
conn, err := net.DialUnix("unix", nil, addr)
if err != nil {
return nil, err
}

2
vend/xgb/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
xgbgen/xgbgen
.*.swp

18
vend/xgb/AUTHORS Normal file
View File

@ -0,0 +1,18 @@
Andrew Gallant is the maintainer of this fork. What follows is the original
list of authors for the x-go-binding.
# This is the official list of XGB authors for copyright purposes.
# This file is distinct from the CONTRIBUTORS files.
# See the latter for an explanation.
# Names should be added to this file as
# Name or Organization <email address>
# The email address is not required for organizations.
# Please keep the list sorted.
Anthony Martin <ality@pbrane.org>
Firmansyah Adiputra <frm.adiputra@gmail.com>
Google Inc.
Scott Lawrence <bytbox@gmail.com>
Tor Andersson <tor.andersson@gmail.com>

39
vend/xgb/CONTRIBUTORS Normal file
View File

@ -0,0 +1,39 @@
Andrew Gallant is the maintainer of this fork. What follows is the original
list of contributors for the x-go-binding.
# This is the official list of people who can contribute
# (and typically have contributed) code to the XGB repository.
# The AUTHORS file lists the copyright holders; this file
# lists people. For example, Google employees are listed here
# but not in AUTHORS, because Google holds the copyright.
#
# The submission process automatically checks to make sure
# that people submitting code are listed in this file (by email address).
#
# Names should be added to this file only after verifying that
# the individual or the individual's organization has agreed to
# the appropriate Contributor License Agreement, found here:
#
# http://code.google.com/legal/individual-cla-v1.0.html
# http://code.google.com/legal/corporate-cla-v1.0.html
#
# The agreement for individuals can be filled out on the web.
#
# When adding J Random Contributor's name to this file,
# either J's name or J's organization's name should be
# added to the AUTHORS file, depending on whether the
# individual or corporate CLA was used.
# Names should be added to this file like so:
# Name <email address>
# Please keep the list sorted.
Anthony Martin <ality@pbrane.org>
Firmansyah Adiputra <frm.adiputra@gmail.com>
Ian Lance Taylor <iant@golang.org>
Nigel Tao <nigeltao@golang.org>
Robert Griesemer <gri@golang.org>
Russ Cox <rsc@golang.org>
Scott Lawrence <bytbox@gmail.com>
Tor Andersson <tor.andersson@gmail.com>

42
vend/xgb/LICENSE Normal file
View File

@ -0,0 +1,42 @@
// Copyright (c) 2009 The XGB Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Subject to the terms and conditions of this License, Google hereby
// grants to You a perpetual, worldwide, non-exclusive, no-charge,
// royalty-free, irrevocable (except as stated in this section) patent
// license to make, have made, use, offer to sell, sell, import, and
// otherwise transfer this implementation of XGB, where such license
// applies only to those patent claims licensable by Google that are
// necessarily infringed by use of this implementation of XGB. If You
// institute patent litigation against any entity (including a
// cross-claim or counterclaim in a lawsuit) alleging that this
// implementation of XGB or a Contribution incorporated within this
// implementation of XGB constitutes direct or contributory patent
// infringement, then any patent licenses granted to You under this
// License for this implementation of XGB shall terminate as of the date
// such litigation is filed.

80
vend/xgb/Makefile Normal file
View File

@ -0,0 +1,80 @@
# This Makefile is used by the developer. It is not needed in any way to build
# a checkout of the XGB repository.
# It will be useful, however, if you are hacking at the code generator.
# i.e., after making a change to the code generator, run 'make' in the
# xgb directory. This will build xgbgen and regenerate each sub-package.
# 'make test' will then run any appropriate tests (just tests xproto right now).
# 'make bench' will test a couple of benchmarks.
# 'make build-all' will then try to build each extension. This isn't strictly
# necessary, but it's a good idea to make sure each sub-package is a valid
# Go package.
# My path to the X protocol XML descriptions.
ifndef XPROTO
XPROTO=/usr/share/xcb
endif
# All of the XML files in my /usr/share/xcb directory EXCEPT XKB. -_-
# This is intended to build xgbgen and generate Go code for each supported
# extension.
all: build-xgbgen \
bigreq.xml composite.xml damage.xml dpms.xml dri2.xml \
ge.xml glx.xml randr.xml record.xml render.xml res.xml \
screensaver.xml shape.xml shm.xml xc_misc.xml \
xevie.xml xf86dri.xml xf86vidmode.xml xfixes.xml xinerama.xml \
xprint.xml xproto.xml xselinux.xml xtest.xml \
xvmc.xml xv.xml
build-xgbgen:
(cd xgbgen && go build)
# Builds each individual sub-package to make sure its valid Go code.
build-all: bigreq.b composite.b damage.b dpms.b dri2.b ge.b glx.b randr.b \
record.b render.b res.b screensaver.b shape.b shm.b xcmisc.b \
xevie.b xf86dri.b xf86vidmode.b xfixes.b xinerama.b \
xprint.b xproto.b xselinux.b xtest.b xv.b xvmc.b
%.b:
(cd $* ; go build)
# Installs each individual sub-package.
install: bigreq.i composite.i damage.i dpms.i dri2.i ge.i glx.i randr.i \
record.i render.i res.i screensaver.i shape.i shm.i xcmisc.i \
xevie.i xf86dri.i xf86vidmode.i xfixes.i xinerama.i \
xprint.i xproto.i xselinux.i xtest.i xv.i xvmc.i
go install
%.i:
(cd $* ; go install)
# xc_misc is special because it has an underscore.
# There's probably a way to do this better, but Makefiles aren't my strong suit.
xc_misc.xml: build-xgbgen
mkdir -p xcmisc
xgbgen/xgbgen --proto-path $(XPROTO) $(XPROTO)/xc_misc.xml > xcmisc/xcmisc.go
%.xml: build-xgbgen
mkdir -p $*
xgbgen/xgbgen --proto-path $(XPROTO) $(XPROTO)/$*.xml > $*/$*.go
# Just test the xproto core protocol for now.
test:
(cd xproto ; go test)
# Force all xproto benchmarks to run and no tests.
bench:
(cd xproto ; go test -run 'nomatch' -bench '.*' -cpu 1,2,3,6)
# gofmt all non-auto-generated code.
# (auto-generated code is already gofmt'd.)
# Also do a column check (80 cols) after a gofmt.
# But don't check columns on auto-generated code, since I don't care if they
# break 80 cols.
gofmt:
gofmt -w *.go xgbgen/*.go examples/*.go examples/*/*.go xproto/xproto_test.go
colcheck *.go xgbgen/*.go examples/*.go examples/*/*.go xproto/xproto_test.go
push:
git push origin master
git push github master

62
vend/xgb/README Normal file
View File

@ -0,0 +1,62 @@
XGB is the X Go Binding, which is a low-level API to communicate with the
core X protocol and many of the X extensions. It is closely modeled after
XCB and xpyb.
It is thread safe and gets immediate improvement from parallelism when
GOMAXPROCS > 1. (See the benchmarks in xproto/xproto_test.go for evidence.)
Please see doc.go for more info.
Note that unless you know you need XGB, you can probably make your life
easier by using a slightly higher level library: xgbutil.
This is a fork of github.com/BurntSushi/xgb
Quick Usage
===========
go get github.com/jezek/xgb
go run go/path/src/github.com/jezek/xgb/examples/create-window/main.go
jezek's Fork
============
I've forked the XGB repository from BurntSushi's github to apply some
patches which caused panics and memory leaks upon close and tests were added,
to test multiple server close scenarios.
BurntSushi's Fork
=================
I've forked the XGB repository from Google Code due to inactivty upstream.
Godoc documentation can be found here:
https://godoc.org/github.com/BurntSushi/xgb
Much of the code has been rewritten in an effort to support thread safety
and multiple extensions. Namely, go_client.py has been thrown away in favor
of an xgbgen package.
The biggest parts that *haven't* been rewritten by me are the connection and
authentication handshakes. They're inherently messy, and there's really no
reason to re-work them. The rest of XGB has been completely rewritten.
I like to release my code under the WTFPL, but since I'm starting with someone
else's work, I'm leaving the original license/contributor/author information
in tact.
I suppose I can legitimately release xgbgen under the WTFPL. To be fair, it is
at least as complex as XGB itself. *sigh*
What follows is the original README:
XGB README
==========
XGB is the X protocol Go language Binding.
It is the Go equivalent of XCB, the X protocol C-language Binding
(http://xcb.freedesktop.org/).
Unless otherwise noted, the XGB source files are distributed
under the BSD-style license found in the LICENSE file.
Contributions should follow the same procedure as for the Go project:
http://golang.org/doc/contribute.html

29
vend/xgb/STYLE Normal file
View File

@ -0,0 +1,29 @@
I like to keep all my code to 80 columns or less. I have plenty of screen real
estate, but enjoy 80 columns so that I can have multiple code windows open side
to side and not be plagued by the ugly auto-wrapping of a text editor.
If you don't oblige me, I will fix any patch you submit to abide 80 columns.
Note that this style restriction does not preclude gofmt, but introduces a few
peculiarities. The first is that gofmt will occasionally add spacing (typically
to comments) that ends up going over 80 columns. Either shorten the comment or
put it on its own line.
The second and more common hiccup is when a function definition extends beyond
80 columns. If one adds line breaks to keep it below 80 columns, gofmt will
indent all subsequent lines in a function definition to the same indentation
level of the function body. This results in a less-than-ideal separation
between function definition and function body. To remedy this, simply add a
line break like so:
func RestackWindowExtra(xu *xgbutil.XUtil, win xproto.Window, stackMode int,
sibling xproto.Window, source int) error {
return ClientEvent(xu, win, "_NET_RESTACK_WINDOW", source, int(sibling),
stackMode)
}
Something similar should also be applied to long 'if' or 'for' conditionals,
although it would probably be preferrable to break up the conditional to
smaller chunks with a few helper variables.

110
vend/xgb/auth.go Normal file
View File

@ -0,0 +1,110 @@
package xgb
/*
auth.go contains functions to facilitate the parsing of .Xauthority files.
It is largely unmodified from the original XGB package that I forked.
*/
import (
"encoding/binary"
"errors"
"io"
"os"
)
// readAuthority reads the X authority file for the DISPLAY.
// If hostname == "" or hostname == "localhost",
// then use the system's hostname (as returned by os.Hostname) instead.
func readAuthority(hostname, display string) (
name string, data []byte, err error) {
// b is a scratch buffer to use and should be at least 256 bytes long
// (i.e. it should be able to hold a hostname).
b := make([]byte, 256)
// As per /usr/include/X11/Xauth.h.
const familyLocal = 256
const familyWild = 65535
if len(hostname) == 0 || hostname == "localhost" {
hostname, err = os.Hostname()
if err != nil {
return "", nil, err
}
}
fname := os.Getenv("XAUTHORITY")
if len(fname) == 0 {
home := os.Getenv("HOME")
if len(home) == 0 {
err = errors.New("Xauthority not found: $XAUTHORITY, $HOME not set")
return "", nil, err
}
fname = home + "/.Xauthority"
}
r, err := os.Open(fname)
if err != nil {
return "", nil, err
}
defer r.Close()
for {
var family uint16
if err := binary.Read(r, binary.BigEndian, &family); err != nil {
return "", nil, err
}
addr, err := getString(r, b)
if err != nil {
return "", nil, err
}
disp, err := getString(r, b)
if err != nil {
return "", nil, err
}
name0, err := getString(r, b)
if err != nil {
return "", nil, err
}
data0, err := getBytes(r, b)
if err != nil {
return "", nil, err
}
addrmatch := (family == familyWild) ||
(family == familyLocal && addr == hostname)
dispmatch := (disp == "") || (disp == display)
if addrmatch && dispmatch {
return name0, data0, nil
}
}
panic("unreachable")
}
func getBytes(r io.Reader, b []byte) ([]byte, error) {
var n uint16
if err := binary.Read(r, binary.BigEndian, &n); err != nil {
return nil, err
} else if n > uint16(len(b)) {
return nil, errors.New("bytes too long for buffer")
}
if _, err := io.ReadFull(r, b[0:n]); err != nil {
return nil, err
}
return b[0:n], nil
}
func getString(r io.Reader, b []byte) (string, error) {
b, err := getBytes(r, b)
if err != nil {
return "", err
}
return string(b), nil
}

152
vend/xgb/bigreq/bigreq.go Normal file
View File

@ -0,0 +1,152 @@
// Package bigreq is the X client API for the BIG-REQUESTS extension.
package bigreq
// This file is automatically generated from bigreq.xml. Edit at your peril!
import (
"github.com/jezek/xgb"
"github.com/jezek/xgb/xproto"
)
// Init must be called before using the BIG-REQUESTS extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 12, "BIG-REQUESTS").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named BIG-REQUESTS could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["BIG-REQUESTS"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["BIG-REQUESTS"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["BIG-REQUESTS"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["BIG-REQUESTS"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["BIG-REQUESTS"] = make(map[int]xgb.NewErrorFun)
}
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// EnableCookie is a cookie used only for Enable requests.
type EnableCookie struct {
*xgb.Cookie
}
// Enable sends a checked request.
// If an error occurs, it will be returned with the reply by calling EnableCookie.Reply()
func Enable(c *xgb.Conn) EnableCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["BIG-REQUESTS"]; !ok {
panic("Cannot issue request 'Enable' using the uninitialized extension 'BIG-REQUESTS'. bigreq.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(enableRequest(c), cookie)
return EnableCookie{cookie}
}
// EnableUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func EnableUnchecked(c *xgb.Conn) EnableCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["BIG-REQUESTS"]; !ok {
panic("Cannot issue request 'Enable' using the uninitialized extension 'BIG-REQUESTS'. bigreq.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(enableRequest(c), cookie)
return EnableCookie{cookie}
}
// EnableReply represents the data returned from a Enable request.
type EnableReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
MaximumRequestLength uint32
}
// Reply blocks and returns the reply data for a Enable request.
func (cook EnableCookie) Reply() (*EnableReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return enableReply(buf), nil
}
// enableReply reads a byte slice into a EnableReply value.
func enableReply(buf []byte) *EnableReply {
v := new(EnableReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.MaximumRequestLength = xgb.Get32(buf[b:])
b += 4
return v
}
// Write request to wire for Enable
// enableRequest writes a Enable request to a byte slice.
func enableRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["BIG-REQUESTS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}

View File

@ -0,0 +1,721 @@
// Package composite is the X client API for the Composite extension.
package composite
// This file is automatically generated from composite.xml. Edit at your peril!
import (
"github.com/jezek/xgb"
"github.com/jezek/xgb/xfixes"
"github.com/jezek/xgb/xproto"
)
// Init must be called before using the Composite extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 9, "Composite").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named Composite could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["Composite"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["Composite"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["Composite"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["Composite"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["Composite"] = make(map[int]xgb.NewErrorFun)
}
const (
RedirectAutomatic = 0
RedirectManual = 1
)
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// CreateRegionFromBorderClipCookie is a cookie used only for CreateRegionFromBorderClip requests.
type CreateRegionFromBorderClipCookie struct {
*xgb.Cookie
}
// CreateRegionFromBorderClip sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func CreateRegionFromBorderClip(c *xgb.Conn, Region xfixes.Region, Window xproto.Window) CreateRegionFromBorderClipCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'CreateRegionFromBorderClip' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(createRegionFromBorderClipRequest(c, Region, Window), cookie)
return CreateRegionFromBorderClipCookie{cookie}
}
// CreateRegionFromBorderClipChecked sends a checked request.
// If an error occurs, it can be retrieved using CreateRegionFromBorderClipCookie.Check()
func CreateRegionFromBorderClipChecked(c *xgb.Conn, Region xfixes.Region, Window xproto.Window) CreateRegionFromBorderClipCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'CreateRegionFromBorderClip' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(createRegionFromBorderClipRequest(c, Region, Window), cookie)
return CreateRegionFromBorderClipCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook CreateRegionFromBorderClipCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for CreateRegionFromBorderClip
// createRegionFromBorderClipRequest writes a CreateRegionFromBorderClip request to a byte slice.
func createRegionFromBorderClipRequest(c *xgb.Conn, Region xfixes.Region, Window xproto.Window) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 5 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Region))
b += 4
xgb.Put32(buf[b:], uint32(Window))
b += 4
return buf
}
// GetOverlayWindowCookie is a cookie used only for GetOverlayWindow requests.
type GetOverlayWindowCookie struct {
*xgb.Cookie
}
// GetOverlayWindow sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetOverlayWindowCookie.Reply()
func GetOverlayWindow(c *xgb.Conn, Window xproto.Window) GetOverlayWindowCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'GetOverlayWindow' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getOverlayWindowRequest(c, Window), cookie)
return GetOverlayWindowCookie{cookie}
}
// GetOverlayWindowUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetOverlayWindowUnchecked(c *xgb.Conn, Window xproto.Window) GetOverlayWindowCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'GetOverlayWindow' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getOverlayWindowRequest(c, Window), cookie)
return GetOverlayWindowCookie{cookie}
}
// GetOverlayWindowReply represents the data returned from a GetOverlayWindow request.
type GetOverlayWindowReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
OverlayWin xproto.Window
// padding: 20 bytes
}
// Reply blocks and returns the reply data for a GetOverlayWindow request.
func (cook GetOverlayWindowCookie) Reply() (*GetOverlayWindowReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getOverlayWindowReply(buf), nil
}
// getOverlayWindowReply reads a byte slice into a GetOverlayWindowReply value.
func getOverlayWindowReply(buf []byte) *GetOverlayWindowReply {
v := new(GetOverlayWindowReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.OverlayWin = xproto.Window(xgb.Get32(buf[b:]))
b += 4
b += 20 // padding
return v
}
// Write request to wire for GetOverlayWindow
// getOverlayWindowRequest writes a GetOverlayWindow request to a byte slice.
func getOverlayWindowRequest(c *xgb.Conn, Window xproto.Window) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 7 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
return buf
}
// NameWindowPixmapCookie is a cookie used only for NameWindowPixmap requests.
type NameWindowPixmapCookie struct {
*xgb.Cookie
}
// NameWindowPixmap sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func NameWindowPixmap(c *xgb.Conn, Window xproto.Window, Pixmap xproto.Pixmap) NameWindowPixmapCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'NameWindowPixmap' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(nameWindowPixmapRequest(c, Window, Pixmap), cookie)
return NameWindowPixmapCookie{cookie}
}
// NameWindowPixmapChecked sends a checked request.
// If an error occurs, it can be retrieved using NameWindowPixmapCookie.Check()
func NameWindowPixmapChecked(c *xgb.Conn, Window xproto.Window, Pixmap xproto.Pixmap) NameWindowPixmapCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'NameWindowPixmap' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(nameWindowPixmapRequest(c, Window, Pixmap), cookie)
return NameWindowPixmapCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook NameWindowPixmapCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for NameWindowPixmap
// nameWindowPixmapRequest writes a NameWindowPixmap request to a byte slice.
func nameWindowPixmapRequest(c *xgb.Conn, Window xproto.Window, Pixmap xproto.Pixmap) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 6 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
xgb.Put32(buf[b:], uint32(Pixmap))
b += 4
return buf
}
// QueryVersionCookie is a cookie used only for QueryVersion requests.
type QueryVersionCookie struct {
*xgb.Cookie
}
// QueryVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryVersionCookie.Reply()
func QueryVersion(c *xgb.Conn, ClientMajorVersion uint32, ClientMinorVersion uint32) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryVersionUnchecked(c *xgb.Conn, ClientMajorVersion uint32, ClientMinorVersion uint32) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionReply represents the data returned from a QueryVersion request.
type QueryVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
MajorVersion uint32
MinorVersion uint32
// padding: 16 bytes
}
// Reply blocks and returns the reply data for a QueryVersion request.
func (cook QueryVersionCookie) Reply() (*QueryVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryVersionReply(buf), nil
}
// queryVersionReply reads a byte slice into a QueryVersionReply value.
func queryVersionReply(buf []byte) *QueryVersionReply {
v := new(QueryVersionReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.MajorVersion = xgb.Get32(buf[b:])
b += 4
v.MinorVersion = xgb.Get32(buf[b:])
b += 4
b += 16 // padding
return v
}
// Write request to wire for QueryVersion
// queryVersionRequest writes a QueryVersion request to a byte slice.
func queryVersionRequest(c *xgb.Conn, ClientMajorVersion uint32, ClientMinorVersion uint32) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], ClientMajorVersion)
b += 4
xgb.Put32(buf[b:], ClientMinorVersion)
b += 4
return buf
}
// RedirectSubwindowsCookie is a cookie used only for RedirectSubwindows requests.
type RedirectSubwindowsCookie struct {
*xgb.Cookie
}
// RedirectSubwindows sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func RedirectSubwindows(c *xgb.Conn, Window xproto.Window, Update byte) RedirectSubwindowsCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'RedirectSubwindows' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(redirectSubwindowsRequest(c, Window, Update), cookie)
return RedirectSubwindowsCookie{cookie}
}
// RedirectSubwindowsChecked sends a checked request.
// If an error occurs, it can be retrieved using RedirectSubwindowsCookie.Check()
func RedirectSubwindowsChecked(c *xgb.Conn, Window xproto.Window, Update byte) RedirectSubwindowsCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'RedirectSubwindows' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(redirectSubwindowsRequest(c, Window, Update), cookie)
return RedirectSubwindowsCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook RedirectSubwindowsCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for RedirectSubwindows
// redirectSubwindowsRequest writes a RedirectSubwindows request to a byte slice.
func redirectSubwindowsRequest(c *xgb.Conn, Window xproto.Window, Update byte) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 2 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
buf[b] = Update
b += 1
b += 3 // padding
return buf
}
// RedirectWindowCookie is a cookie used only for RedirectWindow requests.
type RedirectWindowCookie struct {
*xgb.Cookie
}
// RedirectWindow sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func RedirectWindow(c *xgb.Conn, Window xproto.Window, Update byte) RedirectWindowCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'RedirectWindow' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(redirectWindowRequest(c, Window, Update), cookie)
return RedirectWindowCookie{cookie}
}
// RedirectWindowChecked sends a checked request.
// If an error occurs, it can be retrieved using RedirectWindowCookie.Check()
func RedirectWindowChecked(c *xgb.Conn, Window xproto.Window, Update byte) RedirectWindowCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'RedirectWindow' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(redirectWindowRequest(c, Window, Update), cookie)
return RedirectWindowCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook RedirectWindowCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for RedirectWindow
// redirectWindowRequest writes a RedirectWindow request to a byte slice.
func redirectWindowRequest(c *xgb.Conn, Window xproto.Window, Update byte) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 1 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
buf[b] = Update
b += 1
b += 3 // padding
return buf
}
// ReleaseOverlayWindowCookie is a cookie used only for ReleaseOverlayWindow requests.
type ReleaseOverlayWindowCookie struct {
*xgb.Cookie
}
// ReleaseOverlayWindow sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func ReleaseOverlayWindow(c *xgb.Conn, Window xproto.Window) ReleaseOverlayWindowCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'ReleaseOverlayWindow' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(releaseOverlayWindowRequest(c, Window), cookie)
return ReleaseOverlayWindowCookie{cookie}
}
// ReleaseOverlayWindowChecked sends a checked request.
// If an error occurs, it can be retrieved using ReleaseOverlayWindowCookie.Check()
func ReleaseOverlayWindowChecked(c *xgb.Conn, Window xproto.Window) ReleaseOverlayWindowCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'ReleaseOverlayWindow' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(releaseOverlayWindowRequest(c, Window), cookie)
return ReleaseOverlayWindowCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook ReleaseOverlayWindowCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for ReleaseOverlayWindow
// releaseOverlayWindowRequest writes a ReleaseOverlayWindow request to a byte slice.
func releaseOverlayWindowRequest(c *xgb.Conn, Window xproto.Window) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 8 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
return buf
}
// UnredirectSubwindowsCookie is a cookie used only for UnredirectSubwindows requests.
type UnredirectSubwindowsCookie struct {
*xgb.Cookie
}
// UnredirectSubwindows sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func UnredirectSubwindows(c *xgb.Conn, Window xproto.Window, Update byte) UnredirectSubwindowsCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'UnredirectSubwindows' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(unredirectSubwindowsRequest(c, Window, Update), cookie)
return UnredirectSubwindowsCookie{cookie}
}
// UnredirectSubwindowsChecked sends a checked request.
// If an error occurs, it can be retrieved using UnredirectSubwindowsCookie.Check()
func UnredirectSubwindowsChecked(c *xgb.Conn, Window xproto.Window, Update byte) UnredirectSubwindowsCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'UnredirectSubwindows' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(unredirectSubwindowsRequest(c, Window, Update), cookie)
return UnredirectSubwindowsCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook UnredirectSubwindowsCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for UnredirectSubwindows
// unredirectSubwindowsRequest writes a UnredirectSubwindows request to a byte slice.
func unredirectSubwindowsRequest(c *xgb.Conn, Window xproto.Window, Update byte) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 4 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
buf[b] = Update
b += 1
b += 3 // padding
return buf
}
// UnredirectWindowCookie is a cookie used only for UnredirectWindow requests.
type UnredirectWindowCookie struct {
*xgb.Cookie
}
// UnredirectWindow sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func UnredirectWindow(c *xgb.Conn, Window xproto.Window, Update byte) UnredirectWindowCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'UnredirectWindow' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(unredirectWindowRequest(c, Window, Update), cookie)
return UnredirectWindowCookie{cookie}
}
// UnredirectWindowChecked sends a checked request.
// If an error occurs, it can be retrieved using UnredirectWindowCookie.Check()
func UnredirectWindowChecked(c *xgb.Conn, Window xproto.Window, Update byte) UnredirectWindowCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'UnredirectWindow' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(unredirectWindowRequest(c, Window, Update), cookie)
return UnredirectWindowCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook UnredirectWindowCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for UnredirectWindow
// unredirectWindowRequest writes a UnredirectWindow request to a byte slice.
func unredirectWindowRequest(c *xgb.Conn, Window xproto.Window, Update byte) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 3 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
buf[b] = Update
b += 1
b += 3 // padding
return buf
}

186
vend/xgb/conn.go Normal file
View File

@ -0,0 +1,186 @@
package xgb
/*
conn.go contains a couple of functions that do some real dirty work related
to the initial connection handshake with X.
This code is largely unmodified from the original XGB package that I forked.
*/
import (
"errors"
"fmt"
"io"
"net"
"os"
"strconv"
"strings"
)
// connect connects to the X server given in the 'display' string,
// and does all the necessary setup handshaking.
// If 'display' is empty it will be taken from os.Getenv("DISPLAY").
// Note that you should read and understand the "Connection Setup" of the
// X Protocol Reference Manual before changing this function:
// http://goo.gl/4zGQg
func (c *Conn) connect(display string) error {
err := c.dial(display)
if err != nil {
return err
}
return c.postConnect()
}
// connect init from to the net.Conn,
func (c *Conn) connectNet(netConn net.Conn) error {
c.conn = netConn
return c.postConnect()
}
// do the postConnect action after Conn get it's underly net.Conn
func (c *Conn) postConnect() error {
// Get authentication data
authName, authData, err := readAuthority(c.host, c.display)
noauth := false
if err != nil {
Logger.Printf("Could not get authority info: %v", err)
Logger.Println("Trying connection without authority info...")
authName = ""
authData = []byte{}
noauth = true
}
// Assume that the authentication protocol is "MIT-MAGIC-COOKIE-1".
if !noauth && (authName != "MIT-MAGIC-COOKIE-1" || len(authData) != 16) {
return errors.New("unsupported auth protocol " + authName)
}
buf := make([]byte, 12+Pad(len(authName))+Pad(len(authData)))
buf[0] = 0x6c
buf[1] = 0
Put16(buf[2:], 11)
Put16(buf[4:], 0)
Put16(buf[6:], uint16(len(authName)))
Put16(buf[8:], uint16(len(authData)))
Put16(buf[10:], 0)
copy(buf[12:], []byte(authName))
copy(buf[12+Pad(len(authName)):], authData)
if _, err = c.conn.Write(buf); err != nil {
return err
}
head := make([]byte, 8)
if _, err = io.ReadFull(c.conn, head[0:8]); err != nil {
return err
}
code := head[0]
reasonLen := head[1]
major := Get16(head[2:])
minor := Get16(head[4:])
dataLen := Get16(head[6:])
if major != 11 || minor != 0 {
return fmt.Errorf("x protocol version mismatch: %d.%d", major, minor)
}
buf = make([]byte, int(dataLen)*4+8, int(dataLen)*4+8)
copy(buf, head)
if _, err = io.ReadFull(c.conn, buf[8:]); err != nil {
return err
}
if code == 0 {
reason := buf[8 : 8+reasonLen]
return fmt.Errorf("x protocol authentication refused: %s",
string(reason))
}
// Unfortunately, it isn't really feasible to read the setup bytes here,
// since the code to do so is in a different package.
// Users must call 'xproto.Setup(X)' to get the setup info.
c.SetupBytes = buf
// But also read stuff that we *need* to get started.
c.setupResourceIdBase = Get32(buf[12:])
c.setupResourceIdMask = Get32(buf[16:])
return nil
}
// dial initializes the actual net connection with X.
func (c *Conn) dial(display string) error {
if len(display) == 0 {
display = os.Getenv("DISPLAY")
}
display0 := display
if len(display) == 0 {
return errors.New("empty display string")
}
colonIdx := strings.LastIndex(display, ":")
if colonIdx < 0 {
return errors.New("bad display string: " + display0)
}
var protocol, socket string
if display[0] == '/' {
socket = display[0:colonIdx]
} else {
slashIdx := strings.LastIndex(display, "/")
if slashIdx >= 0 {
protocol = display[0:slashIdx]
c.host = display[slashIdx+1 : colonIdx]
} else {
c.host = display[0:colonIdx]
}
}
display = display[colonIdx+1 : len(display)]
if len(display) == 0 {
return errors.New("bad display string: " + display0)
}
var scr string
dotIdx := strings.LastIndex(display, ".")
if dotIdx < 0 {
c.display = display[0:]
} else {
c.display = display[0:dotIdx]
scr = display[dotIdx+1:]
}
var err error
c.DisplayNumber, err = strconv.Atoi(c.display)
if err != nil || c.DisplayNumber < 0 {
return errors.New("bad display string: " + display0)
}
if len(scr) != 0 {
c.DefaultScreen, err = strconv.Atoi(scr)
if err != nil {
return errors.New("bad display string: " + display0)
}
}
// Connect to server
if len(socket) != 0 {
c.conn, err = net.Dial("unix", socket+":"+c.display)
} else if len(c.host) != 0 && c.host != "unix" {
if protocol == "" {
protocol = "tcp"
}
c.conn, err = net.Dial(protocol,
c.host+":"+strconv.Itoa(6000+c.DisplayNumber))
} else {
c.host = ""
c.conn, err = net.Dial("unix", "/tmp/.X11-unix/X"+c.display)
}
if err != nil {
return errors.New("cannot connect to " + display0 + ": " + err.Error())
}
return nil
}

178
vend/xgb/cookie.go Normal file
View File

@ -0,0 +1,178 @@
package xgb
import (
"errors"
"io"
)
// Cookie is the internal representation of a cookie, where one is generated
// for *every* request sent by XGB.
// 'cookie' is most frequently used by embedding it into a more specific
// kind of cookie, i.e., 'GetInputFocusCookie'.
type Cookie struct {
conn *Conn
Sequence uint16
replyChan chan []byte
errorChan chan error
pingChan chan bool
}
// NewCookie creates a new cookie with the correct channels initialized
// depending upon the values of 'checked' and 'reply'. Together, there are
// four different kinds of cookies. (See more detailed comments in the
// function for more info on those.)
// Note that a sequence number is not set until just before the request
// corresponding to this cookie is sent over the wire.
//
// Unless you're building requests from bytes by hand, this method should
// not be used.
func (c *Conn) NewCookie(checked, reply bool) *Cookie {
cookie := &Cookie{
conn: c,
Sequence: 0, // we add the sequence id just before sending a request
replyChan: nil,
errorChan: nil,
pingChan: nil,
}
// There are four different kinds of cookies:
// Checked requests with replies get a reply channel and an error channel.
// Unchecked requests with replies get a reply channel and a ping channel.
// Checked requests w/o replies get a ping channel and an error channel.
// Unchecked requests w/o replies get no channels.
// The reply channel is used to send reply data.
// The error channel is used to send error data.
// The ping channel is used when one of the 'reply' or 'error' channels
// is missing but the other is present. The ping channel is way to force
// the blocking to stop and basically say "the error has been received
// in the main event loop" (when the ping channel is coupled with a reply
// channel) or "the request you made that has no reply was successful"
// (when the ping channel is coupled with an error channel).
if checked {
cookie.errorChan = make(chan error, 1)
if !reply {
cookie.pingChan = make(chan bool, 1)
}
}
if reply {
cookie.replyChan = make(chan []byte, 1)
if !checked {
cookie.pingChan = make(chan bool, 1)
}
}
return cookie
}
// Reply detects whether this is a checked or unchecked cookie, and calls
// 'replyChecked' or 'replyUnchecked' appropriately.
//
// Unless you're building requests from bytes by hand, this method should
// not be used.
func (c Cookie) Reply() ([]byte, error) {
// checked
if c.errorChan != nil {
return c.replyChecked()
}
return c.replyUnchecked()
}
// replyChecked waits for a response on either the replyChan or errorChan
// channels. If the former arrives, the bytes are returned with a nil error.
// If the latter arrives, no bytes are returned (nil) and the error received
// is returned.
// Returns (nil, io.EOF) when the connection is closed.
//
// Unless you're building requests from bytes by hand, this method should
// not be used.
func (c Cookie) replyChecked() ([]byte, error) {
if c.replyChan == nil {
return nil, errors.New("Cannot call 'replyChecked' on a cookie that " +
"is not expecting a *reply* or an error.")
}
if c.errorChan == nil {
return nil, errors.New("Cannot call 'replyChecked' on a cookie that " +
"is not expecting a reply or an *error*.")
}
select {
case reply := <-c.replyChan:
return reply, nil
case err := <-c.errorChan:
return nil, err
case <-c.conn.doneRead:
// c.conn.readResponses is no more, there will be no replys or errors
return nil, io.EOF
}
}
// replyUnchecked waits for a response on either the replyChan or pingChan
// channels. If the former arrives, the bytes are returned with a nil error.
// If the latter arrives, no bytes are returned (nil) and a nil error
// is returned. (In the latter case, the corresponding error can be retrieved
// from (Wait|Poll)ForEvent asynchronously.)
// Returns (nil, io.EOF) when the connection is closed.
// In all honesty, you *probably* don't want to use this method.
//
// Unless you're building requests from bytes by hand, this method should
// not be used.
func (c Cookie) replyUnchecked() ([]byte, error) {
if c.replyChan == nil {
return nil, errors.New("Cannot call 'replyUnchecked' on a cookie " +
"that is not expecting a *reply*.")
}
select {
case reply := <-c.replyChan:
return reply, nil
case <-c.pingChan:
return nil, nil
case <-c.conn.doneRead:
// c.conn.readResponses is no more, there will be no replys or pings
return nil, io.EOF
}
}
// Check is used for checked requests that have no replies. It is a mechanism
// by which to report "success" or "error" in a synchronous fashion. (Therefore,
// unchecked requests without replies cannot use this method.)
// If the request causes an error, it is sent to this cookie's errorChan.
// If the request was successful, there is no response from the server.
// Thus, pingChan is sent a value when the *next* reply is read.
// If no more replies are being processed, we force a round trip request with
// GetInputFocus.
// Returns io.EOF error when the connection is closed.
//
// Unless you're building requests from bytes by hand, this method should
// not be used.
func (c Cookie) Check() error {
if c.replyChan != nil {
return errors.New("Cannot call 'Check' on a cookie that is " +
"expecting a *reply*. Use 'Reply' instead.")
}
if c.errorChan == nil {
return errors.New("Cannot call 'Check' on a cookie that is " +
"not expecting a possible *error*.")
}
// First do a quick non-blocking check to see if we've been pinged.
select {
case err := <-c.errorChan:
return err
case <-c.pingChan:
return nil
default:
}
// Now force a round trip and try again, but block this time.
c.conn.Sync()
select {
case err := <-c.errorChan:
return err
case <-c.pingChan:
return nil
case <-c.conn.doneRead:
// c.conn.readResponses is no more, there will be no errors or pings
return io.EOF
}
}

592
vend/xgb/damage/damage.go Normal file
View File

@ -0,0 +1,592 @@
// Package damage is the X client API for the DAMAGE extension.
package damage
// This file is automatically generated from damage.xml. Edit at your peril!
import (
"github.com/jezek/xgb"
"github.com/jezek/xgb/xfixes"
"github.com/jezek/xgb/xproto"
)
// Init must be called before using the DAMAGE extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 6, "DAMAGE").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named DAMAGE could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["DAMAGE"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["DAMAGE"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["DAMAGE"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["DAMAGE"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["DAMAGE"] = make(map[int]xgb.NewErrorFun)
}
// BadBadDamage is the error number for a BadBadDamage.
const BadBadDamage = 0
type BadDamageError struct {
Sequence uint16
NiceName string
}
// BadDamageErrorNew constructs a BadDamageError value that implements xgb.Error from a byte slice.
func BadDamageErrorNew(buf []byte) xgb.Error {
v := BadDamageError{}
v.NiceName = "BadDamage"
b := 1 // skip error determinant
b += 1 // don't read error number
v.Sequence = xgb.Get16(buf[b:])
b += 2
return v
}
// SequenceId returns the sequence id attached to the BadBadDamage error.
// This is mostly used internally.
func (err BadDamageError) SequenceId() uint16 {
return err.Sequence
}
// BadId returns the 'BadValue' number if one exists for the BadBadDamage error. If no bad value exists, 0 is returned.
func (err BadDamageError) BadId() uint32 {
return 0
}
// Error returns a rudimentary string representation of the BadBadDamage error.
func (err BadDamageError) Error() string {
fieldVals := make([]string, 0, 0)
fieldVals = append(fieldVals, "NiceName: "+err.NiceName)
fieldVals = append(fieldVals, xgb.Sprintf("Sequence: %d", err.Sequence))
return "BadBadDamage {" + xgb.StringsJoin(fieldVals, ", ") + "}"
}
func init() {
xgb.NewExtErrorFuncs["DAMAGE"][0] = BadDamageErrorNew
}
type Damage uint32
func NewDamageId(c *xgb.Conn) (Damage, error) {
id, err := c.NewId()
if err != nil {
return 0, err
}
return Damage(id), nil
}
// Notify is the event number for a NotifyEvent.
const Notify = 0
type NotifyEvent struct {
Sequence uint16
Level byte
Drawable xproto.Drawable
Damage Damage
Timestamp xproto.Timestamp
Area xproto.Rectangle
Geometry xproto.Rectangle
}
// NotifyEventNew constructs a NotifyEvent value that implements xgb.Event from a byte slice.
func NotifyEventNew(buf []byte) xgb.Event {
v := NotifyEvent{}
b := 1 // don't read event number
v.Level = buf[b]
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Drawable = xproto.Drawable(xgb.Get32(buf[b:]))
b += 4
v.Damage = Damage(xgb.Get32(buf[b:]))
b += 4
v.Timestamp = xproto.Timestamp(xgb.Get32(buf[b:]))
b += 4
v.Area = xproto.Rectangle{}
b += xproto.RectangleRead(buf[b:], &v.Area)
v.Geometry = xproto.Rectangle{}
b += xproto.RectangleRead(buf[b:], &v.Geometry)
return v
}
// Bytes writes a NotifyEvent value to a byte slice.
func (v NotifyEvent) Bytes() []byte {
buf := make([]byte, 32)
b := 0
// write event number
buf[b] = 0
b += 1
buf[b] = v.Level
b += 1
b += 2 // skip sequence number
xgb.Put32(buf[b:], uint32(v.Drawable))
b += 4
xgb.Put32(buf[b:], uint32(v.Damage))
b += 4
xgb.Put32(buf[b:], uint32(v.Timestamp))
b += 4
{
structBytes := v.Area.Bytes()
copy(buf[b:], structBytes)
b += len(structBytes)
}
{
structBytes := v.Geometry.Bytes()
copy(buf[b:], structBytes)
b += len(structBytes)
}
return buf
}
// SequenceId returns the sequence id attached to the Notify event.
// Events without a sequence number (KeymapNotify) return 0.
// This is mostly used internally.
func (v NotifyEvent) SequenceId() uint16 {
return v.Sequence
}
// String is a rudimentary string representation of NotifyEvent.
func (v NotifyEvent) String() string {
fieldVals := make([]string, 0, 6)
fieldVals = append(fieldVals, xgb.Sprintf("Sequence: %d", v.Sequence))
fieldVals = append(fieldVals, xgb.Sprintf("Level: %d", v.Level))
fieldVals = append(fieldVals, xgb.Sprintf("Drawable: %d", v.Drawable))
fieldVals = append(fieldVals, xgb.Sprintf("Damage: %d", v.Damage))
fieldVals = append(fieldVals, xgb.Sprintf("Timestamp: %d", v.Timestamp))
return "Notify {" + xgb.StringsJoin(fieldVals, ", ") + "}"
}
func init() {
xgb.NewExtEventFuncs["DAMAGE"][0] = NotifyEventNew
}
const (
ReportLevelRawRectangles = 0
ReportLevelDeltaRectangles = 1
ReportLevelBoundingBox = 2
ReportLevelNonEmpty = 3
)
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// AddCookie is a cookie used only for Add requests.
type AddCookie struct {
*xgb.Cookie
}
// Add sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Add(c *xgb.Conn, Drawable xproto.Drawable, Region xfixes.Region) AddCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'Add' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(addRequest(c, Drawable, Region), cookie)
return AddCookie{cookie}
}
// AddChecked sends a checked request.
// If an error occurs, it can be retrieved using AddCookie.Check()
func AddChecked(c *xgb.Conn, Drawable xproto.Drawable, Region xfixes.Region) AddCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'Add' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(addRequest(c, Drawable, Region), cookie)
return AddCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook AddCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for Add
// addRequest writes a Add request to a byte slice.
func addRequest(c *xgb.Conn, Drawable xproto.Drawable, Region xfixes.Region) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DAMAGE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 4 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
xgb.Put32(buf[b:], uint32(Region))
b += 4
return buf
}
// CreateCookie is a cookie used only for Create requests.
type CreateCookie struct {
*xgb.Cookie
}
// Create sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Create(c *xgb.Conn, Damage Damage, Drawable xproto.Drawable, Level byte) CreateCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'Create' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(createRequest(c, Damage, Drawable, Level), cookie)
return CreateCookie{cookie}
}
// CreateChecked sends a checked request.
// If an error occurs, it can be retrieved using CreateCookie.Check()
func CreateChecked(c *xgb.Conn, Damage Damage, Drawable xproto.Drawable, Level byte) CreateCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'Create' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(createRequest(c, Damage, Drawable, Level), cookie)
return CreateCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook CreateCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for Create
// createRequest writes a Create request to a byte slice.
func createRequest(c *xgb.Conn, Damage Damage, Drawable xproto.Drawable, Level byte) []byte {
size := 16
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DAMAGE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 1 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Damage))
b += 4
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
buf[b] = Level
b += 1
b += 3 // padding
return buf
}
// DestroyCookie is a cookie used only for Destroy requests.
type DestroyCookie struct {
*xgb.Cookie
}
// Destroy sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Destroy(c *xgb.Conn, Damage Damage) DestroyCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'Destroy' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(destroyRequest(c, Damage), cookie)
return DestroyCookie{cookie}
}
// DestroyChecked sends a checked request.
// If an error occurs, it can be retrieved using DestroyCookie.Check()
func DestroyChecked(c *xgb.Conn, Damage Damage) DestroyCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'Destroy' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(destroyRequest(c, Damage), cookie)
return DestroyCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook DestroyCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for Destroy
// destroyRequest writes a Destroy request to a byte slice.
func destroyRequest(c *xgb.Conn, Damage Damage) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DAMAGE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 2 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Damage))
b += 4
return buf
}
// QueryVersionCookie is a cookie used only for QueryVersion requests.
type QueryVersionCookie struct {
*xgb.Cookie
}
// QueryVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryVersionCookie.Reply()
func QueryVersion(c *xgb.Conn, ClientMajorVersion uint32, ClientMinorVersion uint32) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryVersionUnchecked(c *xgb.Conn, ClientMajorVersion uint32, ClientMinorVersion uint32) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionReply represents the data returned from a QueryVersion request.
type QueryVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
MajorVersion uint32
MinorVersion uint32
// padding: 16 bytes
}
// Reply blocks and returns the reply data for a QueryVersion request.
func (cook QueryVersionCookie) Reply() (*QueryVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryVersionReply(buf), nil
}
// queryVersionReply reads a byte slice into a QueryVersionReply value.
func queryVersionReply(buf []byte) *QueryVersionReply {
v := new(QueryVersionReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.MajorVersion = xgb.Get32(buf[b:])
b += 4
v.MinorVersion = xgb.Get32(buf[b:])
b += 4
b += 16 // padding
return v
}
// Write request to wire for QueryVersion
// queryVersionRequest writes a QueryVersion request to a byte slice.
func queryVersionRequest(c *xgb.Conn, ClientMajorVersion uint32, ClientMinorVersion uint32) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DAMAGE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], ClientMajorVersion)
b += 4
xgb.Put32(buf[b:], ClientMinorVersion)
b += 4
return buf
}
// SubtractCookie is a cookie used only for Subtract requests.
type SubtractCookie struct {
*xgb.Cookie
}
// Subtract sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Subtract(c *xgb.Conn, Damage Damage, Repair xfixes.Region, Parts xfixes.Region) SubtractCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'Subtract' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(subtractRequest(c, Damage, Repair, Parts), cookie)
return SubtractCookie{cookie}
}
// SubtractChecked sends a checked request.
// If an error occurs, it can be retrieved using SubtractCookie.Check()
func SubtractChecked(c *xgb.Conn, Damage Damage, Repair xfixes.Region, Parts xfixes.Region) SubtractCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'Subtract' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(subtractRequest(c, Damage, Repair, Parts), cookie)
return SubtractCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook SubtractCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for Subtract
// subtractRequest writes a Subtract request to a byte slice.
func subtractRequest(c *xgb.Conn, Damage Damage, Repair xfixes.Region, Parts xfixes.Region) []byte {
size := 16
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DAMAGE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 3 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Damage))
b += 4
xgb.Put32(buf[b:], uint32(Repair))
b += 4
xgb.Put32(buf[b:], uint32(Parts))
b += 4
return buf
}

146
vend/xgb/doc.go Normal file
View File

@ -0,0 +1,146 @@
/*
Package XGB provides the X Go Binding, which is a low-level API to communicate
with the core X protocol and many of the X extensions.
It is *very* closely modeled on XCB, so that experience with XCB (or xpyb) is
easily translatable to XGB. That is, it uses the same cookie/reply model
and is thread safe. There are otherwise no major differences (in the API).
Most uses of XGB typically fall under the realm of window manager and GUI kit
development, but other applications (like pagers, panels, tilers, etc.) may
also require XGB. Moreover, it is a near certainty that if you need to work
with X, xgbutil will be of great use to you as well:
https://github.com/jezek/xgbutil
Example
This is an extremely terse example that demonstrates how to connect to X,
create a window, listen to StructureNotify events and Key{Press,Release}
events, map the window, and print out all events received. An example with
accompanying documentation can be found in examples/create-window.
package main
import (
"fmt"
"github.com/jezek/xgb"
"github.com/jezek/xgb/xproto"
)
func main() {
X, err := xgb.NewConn()
if err != nil {
fmt.Println(err)
return
}
wid, _ := xproto.NewWindowId(X)
screen := xproto.Setup(X).DefaultScreen(X)
xproto.CreateWindow(X, screen.RootDepth, wid, screen.Root,
0, 0, 500, 500, 0,
xproto.WindowClassInputOutput, screen.RootVisual,
xproto.CwBackPixel | xproto.CwEventMask,
[]uint32{ // values must be in the order defined by the protocol
0xffffffff,
xproto.EventMaskStructureNotify |
xproto.EventMaskKeyPress |
xproto.EventMaskKeyRelease})
xproto.MapWindow(X, wid)
for {
ev, xerr := X.WaitForEvent()
if ev == nil && xerr == nil {
fmt.Println("Both event and error are nil. Exiting...")
return
}
if ev != nil {
fmt.Printf("Event: %s\n", ev)
}
if xerr != nil {
fmt.Printf("Error: %s\n", xerr)
}
}
}
Xinerama Example
This is another small example that shows how to query Xinerama for geometry
information of each active head. Accompanying documentation for this example
can be found in examples/xinerama.
package main
import (
"fmt"
"log"
"github.com/jezek/xgb"
"github.com/jezek/xgb/xinerama"
)
func main() {
X, err := xgb.NewConn()
if err != nil {
log.Fatal(err)
}
// Initialize the Xinerama extension.
// The appropriate 'Init' function must be run for *every*
// extension before any of its requests can be used.
err = xinerama.Init(X)
if err != nil {
log.Fatal(err)
}
reply, err := xinerama.QueryScreens(X).Reply()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Number of heads: %d\n", reply.Number)
for i, screen := range reply.ScreenInfo {
fmt.Printf("%d :: X: %d, Y: %d, Width: %d, Height: %d\n",
i, screen.XOrg, screen.YOrg, screen.Width, screen.Height)
}
}
Parallelism
XGB can benefit greatly from parallelism due to its concurrent design. For
evidence of this claim, please see the benchmarks in xproto/xproto_test.go.
Tests
xproto/xproto_test.go contains a number of contrived tests that stress
particular corners of XGB that I presume could be problem areas. Namely:
requests with no replies, requests with replies, checked errors, unchecked
errors, sequence number wrapping, cookie buffer flushing (i.e., forcing a round
trip every N requests made that don't have a reply), getting/setting properties
and creating a window and listening to StructureNotify events.
Code Generator
Both XCB and xpyb use the same Python module (xcbgen) for a code generator. XGB
(before this fork) used the same code generator as well, but in my attempt to
add support for more extensions, I found the code generator extremely difficult
to work with. Therefore, I re-wrote the code generator in Go. It can be found
in its own sub-package, xgbgen, of xgb. My design of xgbgen includes a rough
consideration that it could be used for other languages.
What works
I am reasonably confident that the core X protocol is in full working form. I've
also tested the Xinerama and RandR extensions sparingly. Many of the other
existing extensions have Go source generated (and are compilable) and are
included in this package, but I am currently unsure of their status. They
*should* work.
What does not work
XKB is the only extension that intentionally does not work, although I suspect
that GLX also does not work (however, there is Go source code for GLX that
compiles, unlike XKB). I don't currently have any intention of getting XKB
working, due to its complexity and my current mental incapacity to test it.
*/
package xgb

715
vend/xgb/dpms/dpms.go Normal file
View File

@ -0,0 +1,715 @@
// Package dpms is the X client API for the DPMS extension.
package dpms
// This file is automatically generated from dpms.xml. Edit at your peril!
import (
"github.com/jezek/xgb"
"github.com/jezek/xgb/xproto"
)
// Init must be called before using the DPMS extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 4, "DPMS").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named DPMS could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["DPMS"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["DPMS"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["DPMS"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["DPMS"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["DPMS"] = make(map[int]xgb.NewErrorFun)
}
const (
DPMSModeOn = 0
DPMSModeStandby = 1
DPMSModeSuspend = 2
DPMSModeOff = 3
)
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// CapableCookie is a cookie used only for Capable requests.
type CapableCookie struct {
*xgb.Cookie
}
// Capable sends a checked request.
// If an error occurs, it will be returned with the reply by calling CapableCookie.Reply()
func Capable(c *xgb.Conn) CapableCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'Capable' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(capableRequest(c), cookie)
return CapableCookie{cookie}
}
// CapableUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func CapableUnchecked(c *xgb.Conn) CapableCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'Capable' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(capableRequest(c), cookie)
return CapableCookie{cookie}
}
// CapableReply represents the data returned from a Capable request.
type CapableReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
Capable bool
// padding: 23 bytes
}
// Reply blocks and returns the reply data for a Capable request.
func (cook CapableCookie) Reply() (*CapableReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return capableReply(buf), nil
}
// capableReply reads a byte slice into a CapableReply value.
func capableReply(buf []byte) *CapableReply {
v := new(CapableReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
if buf[b] == 1 {
v.Capable = true
} else {
v.Capable = false
}
b += 1
b += 23 // padding
return v
}
// Write request to wire for Capable
// capableRequest writes a Capable request to a byte slice.
func capableRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DPMS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 1 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}
// DisableCookie is a cookie used only for Disable requests.
type DisableCookie struct {
*xgb.Cookie
}
// Disable sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Disable(c *xgb.Conn) DisableCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'Disable' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(disableRequest(c), cookie)
return DisableCookie{cookie}
}
// DisableChecked sends a checked request.
// If an error occurs, it can be retrieved using DisableCookie.Check()
func DisableChecked(c *xgb.Conn) DisableCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'Disable' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(disableRequest(c), cookie)
return DisableCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook DisableCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for Disable
// disableRequest writes a Disable request to a byte slice.
func disableRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DPMS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 5 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}
// EnableCookie is a cookie used only for Enable requests.
type EnableCookie struct {
*xgb.Cookie
}
// Enable sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Enable(c *xgb.Conn) EnableCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'Enable' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(enableRequest(c), cookie)
return EnableCookie{cookie}
}
// EnableChecked sends a checked request.
// If an error occurs, it can be retrieved using EnableCookie.Check()
func EnableChecked(c *xgb.Conn) EnableCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'Enable' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(enableRequest(c), cookie)
return EnableCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook EnableCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for Enable
// enableRequest writes a Enable request to a byte slice.
func enableRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DPMS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 4 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}
// ForceLevelCookie is a cookie used only for ForceLevel requests.
type ForceLevelCookie struct {
*xgb.Cookie
}
// ForceLevel sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func ForceLevel(c *xgb.Conn, PowerLevel uint16) ForceLevelCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'ForceLevel' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(forceLevelRequest(c, PowerLevel), cookie)
return ForceLevelCookie{cookie}
}
// ForceLevelChecked sends a checked request.
// If an error occurs, it can be retrieved using ForceLevelCookie.Check()
func ForceLevelChecked(c *xgb.Conn, PowerLevel uint16) ForceLevelCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'ForceLevel' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(forceLevelRequest(c, PowerLevel), cookie)
return ForceLevelCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook ForceLevelCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for ForceLevel
// forceLevelRequest writes a ForceLevel request to a byte slice.
func forceLevelRequest(c *xgb.Conn, PowerLevel uint16) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DPMS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 6 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put16(buf[b:], PowerLevel)
b += 2
return buf
}
// GetTimeoutsCookie is a cookie used only for GetTimeouts requests.
type GetTimeoutsCookie struct {
*xgb.Cookie
}
// GetTimeouts sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetTimeoutsCookie.Reply()
func GetTimeouts(c *xgb.Conn) GetTimeoutsCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'GetTimeouts' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getTimeoutsRequest(c), cookie)
return GetTimeoutsCookie{cookie}
}
// GetTimeoutsUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetTimeoutsUnchecked(c *xgb.Conn) GetTimeoutsCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'GetTimeouts' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getTimeoutsRequest(c), cookie)
return GetTimeoutsCookie{cookie}
}
// GetTimeoutsReply represents the data returned from a GetTimeouts request.
type GetTimeoutsReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
StandbyTimeout uint16
SuspendTimeout uint16
OffTimeout uint16
// padding: 18 bytes
}
// Reply blocks and returns the reply data for a GetTimeouts request.
func (cook GetTimeoutsCookie) Reply() (*GetTimeoutsReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getTimeoutsReply(buf), nil
}
// getTimeoutsReply reads a byte slice into a GetTimeoutsReply value.
func getTimeoutsReply(buf []byte) *GetTimeoutsReply {
v := new(GetTimeoutsReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.StandbyTimeout = xgb.Get16(buf[b:])
b += 2
v.SuspendTimeout = xgb.Get16(buf[b:])
b += 2
v.OffTimeout = xgb.Get16(buf[b:])
b += 2
b += 18 // padding
return v
}
// Write request to wire for GetTimeouts
// getTimeoutsRequest writes a GetTimeouts request to a byte slice.
func getTimeoutsRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DPMS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 2 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}
// GetVersionCookie is a cookie used only for GetVersion requests.
type GetVersionCookie struct {
*xgb.Cookie
}
// GetVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetVersionCookie.Reply()
func GetVersion(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) GetVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'GetVersion' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return GetVersionCookie{cookie}
}
// GetVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetVersionUnchecked(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) GetVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'GetVersion' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return GetVersionCookie{cookie}
}
// GetVersionReply represents the data returned from a GetVersion request.
type GetVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
ServerMajorVersion uint16
ServerMinorVersion uint16
}
// Reply blocks and returns the reply data for a GetVersion request.
func (cook GetVersionCookie) Reply() (*GetVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getVersionReply(buf), nil
}
// getVersionReply reads a byte slice into a GetVersionReply value.
func getVersionReply(buf []byte) *GetVersionReply {
v := new(GetVersionReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.ServerMajorVersion = xgb.Get16(buf[b:])
b += 2
v.ServerMinorVersion = xgb.Get16(buf[b:])
b += 2
return v
}
// Write request to wire for GetVersion
// getVersionRequest writes a GetVersion request to a byte slice.
func getVersionRequest(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DPMS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put16(buf[b:], ClientMajorVersion)
b += 2
xgb.Put16(buf[b:], ClientMinorVersion)
b += 2
return buf
}
// InfoCookie is a cookie used only for Info requests.
type InfoCookie struct {
*xgb.Cookie
}
// Info sends a checked request.
// If an error occurs, it will be returned with the reply by calling InfoCookie.Reply()
func Info(c *xgb.Conn) InfoCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'Info' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(infoRequest(c), cookie)
return InfoCookie{cookie}
}
// InfoUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func InfoUnchecked(c *xgb.Conn) InfoCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'Info' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(infoRequest(c), cookie)
return InfoCookie{cookie}
}
// InfoReply represents the data returned from a Info request.
type InfoReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
PowerLevel uint16
State bool
// padding: 21 bytes
}
// Reply blocks and returns the reply data for a Info request.
func (cook InfoCookie) Reply() (*InfoReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return infoReply(buf), nil
}
// infoReply reads a byte slice into a InfoReply value.
func infoReply(buf []byte) *InfoReply {
v := new(InfoReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.PowerLevel = xgb.Get16(buf[b:])
b += 2
if buf[b] == 1 {
v.State = true
} else {
v.State = false
}
b += 1
b += 21 // padding
return v
}
// Write request to wire for Info
// infoRequest writes a Info request to a byte slice.
func infoRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DPMS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 7 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}
// SetTimeoutsCookie is a cookie used only for SetTimeouts requests.
type SetTimeoutsCookie struct {
*xgb.Cookie
}
// SetTimeouts sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func SetTimeouts(c *xgb.Conn, StandbyTimeout uint16, SuspendTimeout uint16, OffTimeout uint16) SetTimeoutsCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'SetTimeouts' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(setTimeoutsRequest(c, StandbyTimeout, SuspendTimeout, OffTimeout), cookie)
return SetTimeoutsCookie{cookie}
}
// SetTimeoutsChecked sends a checked request.
// If an error occurs, it can be retrieved using SetTimeoutsCookie.Check()
func SetTimeoutsChecked(c *xgb.Conn, StandbyTimeout uint16, SuspendTimeout uint16, OffTimeout uint16) SetTimeoutsCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'SetTimeouts' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(setTimeoutsRequest(c, StandbyTimeout, SuspendTimeout, OffTimeout), cookie)
return SetTimeoutsCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook SetTimeoutsCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for SetTimeouts
// setTimeoutsRequest writes a SetTimeouts request to a byte slice.
func setTimeoutsRequest(c *xgb.Conn, StandbyTimeout uint16, SuspendTimeout uint16, OffTimeout uint16) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DPMS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 3 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put16(buf[b:], StandbyTimeout)
b += 2
xgb.Put16(buf[b:], SuspendTimeout)
b += 2
xgb.Put16(buf[b:], OffTimeout)
b += 2
return buf
}

1821
vend/xgb/dri2/dri2.go Normal file

File diff suppressed because it is too large Load Diff

3
vend/xgb/examples/atoms/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
atoms
*.info
*.prof

View File

@ -0,0 +1,12 @@
atoms:
go build
test: atoms
./atoms --requests 500000 --cpu 1 \
--cpuprof cpu1.prof --memprof mem1.prof > atoms1.info 2>&1
./atoms --requests 500000 --cpu 2 \
--cpuprof cpu2.prof --memprof mem2.prof > atoms2.info 2>&1
./atoms --requests 500000 --cpu 3 \
--cpuprof cpu3.prof --memprof mem3.prof > atoms3.info 2>&1
./atoms --requests 500000 --cpu 6 \
--cpuprof cpu6.prof --memprof mem6.prof > atoms6.info 2>&1

View File

@ -0,0 +1,91 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"runtime"
"runtime/pprof"
"time"
"github.com/jezek/xgb"
"github.com/jezek/xgb/xproto"
)
var (
flagRequests int
flagGOMAXPROCS int
flagCpuProfName string
flagMemProfName string
)
func init() {
flag.IntVar(&flagRequests, "requests", 100000, "Number of atoms to intern.")
flag.IntVar(&flagGOMAXPROCS, "cpu", 1, "Value of GOMAXPROCS.")
flag.StringVar(&flagCpuProfName, "cpuprof", "cpu.prof",
"Name of CPU profile file.")
flag.StringVar(&flagMemProfName, "memprof", "mem.prof",
"Name of memory profile file.")
flag.Parse()
runtime.GOMAXPROCS(flagGOMAXPROCS)
}
func seqNames(n int) []string {
names := make([]string, n)
for i := range names {
names[i] = fmt.Sprintf("NAME%d", i)
}
return names
}
func main() {
X, err := xgb.NewConn()
if err != nil {
log.Fatal(err)
}
names := seqNames(flagRequests)
fcpu, err := os.Create(flagCpuProfName)
if err != nil {
log.Fatal(err)
}
defer fcpu.Close()
pprof.StartCPUProfile(fcpu)
defer pprof.StopCPUProfile()
start := time.Now()
cookies := make([]xproto.InternAtomCookie, flagRequests)
for i := 0; i < flagRequests; i++ {
cookies[i] = xproto.InternAtom(X,
false, uint16(len(names[i])), names[i])
}
for _, cookie := range cookies {
cookie.Reply()
}
fmt.Printf("Exec time: %s\n\n", time.Since(start))
fmem, err := os.Create(flagMemProfName)
if err != nil {
log.Fatal(err)
}
defer fmem.Close()
pprof.WriteHeapProfile(fmem)
memStats := &runtime.MemStats{}
runtime.ReadMemStats(memStats)
// This isn't right. I'm not sure what's wrong.
lastGcTime := time.Unix(int64(memStats.LastGC/1000000000),
int64(memStats.LastGC-memStats.LastGC/1000000000))
fmt.Printf("Alloc: %d\n", memStats.Alloc)
fmt.Printf("TotalAlloc: %d\n", memStats.TotalAlloc)
fmt.Printf("LastGC: %s\n", lastGcTime)
fmt.Printf("PauseTotalNs: %d\n", memStats.PauseTotalNs)
fmt.Printf("PauseNs: %d\n", memStats.PauseNs)
fmt.Printf("NumGC: %d\n", memStats.NumGC)
}

View File

@ -0,0 +1,148 @@
// Example create-window shows how to create a window, map it, resize it,
// and listen to structure and key events (i.e., when the window is resized
// by the window manager, or when key presses/releases are made when the
// window has focus). The events are printed to stdout.
package main
import (
"fmt"
"github.com/jezek/xgb"
"github.com/jezek/xgb/xproto"
)
func main() {
X, err := xgb.NewConn()
if err != nil {
fmt.Println(err)
return
}
defer X.Close()
// xproto.Setup retrieves the Setup information from the setup bytes
// gathered during connection.
setup := xproto.Setup(X)
// This is the default screen with all its associated info.
screen := setup.DefaultScreen(X)
// Any time a new resource (i.e., a window, pixmap, graphics context, etc.)
// is created, we need to generate a resource identifier.
// If the resource is a window, then use xproto.NewWindowId. If it's for
// a pixmap, then use xproto.NewPixmapId. And so on...
wid, _ := xproto.NewWindowId(X)
// CreateWindow takes a boatload of parameters.
xproto.CreateWindow(X, screen.RootDepth, wid, screen.Root,
0, 0, 500, 500, 0,
xproto.WindowClassInputOutput, screen.RootVisual, 0, []uint32{})
// This call to ChangeWindowAttributes could be factored out and
// included with the above CreateWindow call, but it is left here for
// instructive purposes. It tells X to send us events when the 'structure'
// of the window is changed (i.e., when it is resized, mapped, unmapped,
// etc.) and when a key press or a key release has been made when the
// window has focus.
// We also set the 'BackPixel' to white so that the window isn't butt ugly.
xproto.ChangeWindowAttributes(X, wid,
xproto.CwBackPixel|xproto.CwEventMask,
[]uint32{ // values must be in the order defined by the protocol
0xffffffff,
xproto.EventMaskStructureNotify |
xproto.EventMaskKeyPress |
xproto.EventMaskKeyRelease,
})
// MapWindow makes the window we've created appear on the screen.
// We demonstrated the use of a 'checked' request here.
// A checked request is a fancy way of saying, "do error handling
// synchronously." Namely, if there is a problem with the MapWindow request,
// we'll get the error *here*. If we were to do a normal unchecked
// request (like the above CreateWindow and ChangeWindowAttributes
// requests), then we would only see the error arrive in the main event
// loop.
//
// Typically, checked requests are useful when you need to make sure they
// succeed. Since they are synchronous, they incur a round trip cost before
// the program can continue, but this is only going to be noticeable if
// you're issuing tons of requests in succession.
//
// Note that requests without replies are by default unchecked while
// requests *with* replies are checked by default.
err = xproto.MapWindowChecked(X, wid).Check()
if err != nil {
fmt.Printf("Checked Error for mapping window %d: %s\n", wid, err)
} else {
fmt.Printf("Map window %d successful!\n", wid)
}
// This is an example of an invalid MapWindow request and what an error
// looks like.
err = xproto.MapWindowChecked(X, 0).Check()
if err != nil {
fmt.Printf("Checked Error for mapping window 0x1: %s\n", err)
} else { // neva
fmt.Printf("Map window 0x1 successful!\n")
}
// Start the main event loop.
for {
// WaitForEvent either returns an event or an error and never both.
// If both are nil, then something went wrong and the loop should be
// halted.
//
// An error can only be seen here as a response to an unchecked
// request.
ev, xerr := X.WaitForEvent()
if ev == nil && xerr == nil {
fmt.Println("Both event and error are nil. Exiting...")
return
}
if ev != nil {
fmt.Printf("Event: %s\n", ev)
}
if xerr != nil {
fmt.Printf("Error: %s\n", xerr)
}
// This is how accepting events work:
// The application checks what event we got and reacts to it
// accordingly. All events are defined in the xproto subpackage.
// To receive events, we have to first register it using
// either xproto.CreateWindow or xproto.ChangeWindowAttributes.
switch ev.(type) {
case xproto.KeyPressEvent:
// See https://pkg.go.dev/github.com/jezek/xgb/xproto#KeyPressEvent
// for documentation about a key press event.
kpe := ev.(xproto.KeyPressEvent)
fmt.Printf("Key pressed: %d\n", kpe.Detail)
// The Detail value depends on the keyboard layout,
// for QWERTY, q is #24.
if kpe.Detail == 24 {
return // exit on q
}
case xproto.DestroyNotifyEvent:
// Depending on the user's desktop environment (especially
// window manager), killing a window might close the
// client's X connection (e. g. the default Ubuntu
// desktop environment).
//
// If that's the case for your environment, closing this example's window
// will also close the underlying Go program (because closing the X
// connection gives a nil event and EOF error).
//
// Consider how a single application might have multiple windows
// (e.g. an open popup or dialog, ...)
//
// With other DEs, the X connection will still stay open even after the
// X window is closed. For these DEs (e.g. i3) we have to check whether
// the WM sent us a DestroyNotifyEvent and close our program.
//
// For more information about closing windows while maintaining
// the X connection see
// https://github.com/jezek/xgbutil/blob/master/_examples/graceful-window-close/main.go
return
}
}
}

21
vend/xgb/examples/doc.go Normal file
View File

@ -0,0 +1,21 @@
/*
Package examples contains a few different use cases of XGB, like creating
a window, reading properties, and querying for information about multiple
heads using the Xinerama or RandR extensions.
If you're looking to get started quickly, I recommend checking out the
create-window example first. It is the most documented and probably covers
some of the more common bare bones cases of creating windows and responding
to events.
If you're looking to query information about your window manager,
get-active-window is a start. However, to do anything extensive requires
a lot of boiler plate. To that end, I'd recommend use of a higher level
library (eg. xgbutil: https://github.com/jezek/xgbutil).
There are also examples of using the Xinerama and RandR extensions, if you're
interested in querying information about your active heads. In RandR's case,
you can also reconfigure your heads, but the example doesn't cover that.
*/
package documentation

View File

@ -0,0 +1,61 @@
// Example get-active-window reads the _NET_ACTIVE_WINDOW property of the root
// window and uses the result (a window id) to get the name of the window.
package main
import (
"fmt"
"log"
"github.com/jezek/xgb"
"github.com/jezek/xgb/xproto"
)
func main() {
X, err := xgb.NewConn()
if err != nil {
log.Fatal(err)
}
// Get the window id of the root window.
setup := xproto.Setup(X)
root := setup.DefaultScreen(X).Root
// Get the atom id (i.e., intern an atom) of "_NET_ACTIVE_WINDOW".
aname := "_NET_ACTIVE_WINDOW"
activeAtom, err := xproto.InternAtom(X, true, uint16(len(aname)),
aname).Reply()
if err != nil {
log.Fatal(err)
}
// Get the atom id (i.e., intern an atom) of "_NET_WM_NAME".
aname = "_NET_WM_NAME"
nameAtom, err := xproto.InternAtom(X, true, uint16(len(aname)),
aname).Reply()
if err != nil {
log.Fatal(err)
}
// Get the actual value of _NET_ACTIVE_WINDOW.
// Note that 'reply.Value' is just a slice of bytes, so we use an
// XGB helper function, 'Get32', to pull an unsigned 32-bit integer out
// of the byte slice. We then convert it to an X resource id so it can
// be used to get the name of the window in the next GetProperty request.
reply, err := xproto.GetProperty(X, false, root, activeAtom.Atom,
xproto.GetPropertyTypeAny, 0, (1<<32)-1).Reply()
if err != nil {
log.Fatal(err)
}
windowId := xproto.Window(xgb.Get32(reply.Value))
fmt.Printf("Active window id: %X\n", windowId)
// Now get the value of _NET_WM_NAME for the active window.
// Note that this time, we simply convert the resulting byte slice,
// reply.Value, to a string.
reply, err = xproto.GetProperty(X, false, windowId, nameAtom.Atom,
xproto.GetPropertyTypeAny, 0, (1<<32)-1).Reply()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Active window name: %s\n", string(reply.Value))
}

View File

@ -0,0 +1,92 @@
// Example randr uses the randr protocol to get information about the active
// heads. It also listens for events that are sent when the head configuration
// changes. Since it listens to events, you'll have to manually kill this
// process when you're done (i.e., ctrl+c.)
//
// While this program is running, if you use 'xrandr' to reconfigure your
// heads, you should see event information dumped to standard out.
//
// For more information, please see the RandR protocol spec:
// http://www.x.org/releases/X11R7.6/doc/randrproto/randrproto.txt
package main
import (
"fmt"
"log"
"github.com/jezek/xgb"
"github.com/jezek/xgb/randr"
"github.com/jezek/xgb/xproto"
)
func main() {
X, _ := xgb.NewConn()
// Every extension must be initialized before it can be used.
err := randr.Init(X)
if err != nil {
log.Fatal(err)
}
// Get the root window on the default screen.
root := xproto.Setup(X).DefaultScreen(X).Root
// Gets the current screen resources. Screen resources contains a list
// of names, crtcs, outputs and modes, among other things.
resources, err := randr.GetScreenResources(X, root).Reply()
if err != nil {
log.Fatal(err)
}
// Iterate through all of the outputs and show some of their info.
for _, output := range resources.Outputs {
info, err := randr.GetOutputInfo(X, output, 0).Reply()
if err != nil {
log.Fatal(err)
}
if info.Connection == randr.ConnectionConnected {
bestMode := info.Modes[0]
for _, mode := range resources.Modes {
if mode.Id == uint32(bestMode) {
fmt.Printf("Width: %d, Height: %d\n",
mode.Width, mode.Height)
}
}
}
}
fmt.Println("")
// Iterate through all of the crtcs and show some of their info.
for _, crtc := range resources.Crtcs {
info, err := randr.GetCrtcInfo(X, crtc, 0).Reply()
if err != nil {
log.Fatal(err)
}
fmt.Printf("X: %d, Y: %d, Width: %d, Height: %d\n",
info.X, info.Y, info.Width, info.Height)
}
// Tell RandR to send us events. (I think these are all of them, as of 1.3.)
err = randr.SelectInputChecked(X, root,
randr.NotifyMaskScreenChange|
randr.NotifyMaskCrtcChange|
randr.NotifyMaskOutputChange|
randr.NotifyMaskOutputProperty).Check()
if err != nil {
log.Fatal(err)
}
// Listen to events and just dump them to standard out.
// A more involved approach will have to read the 'U' field of
// RandrNotifyEvent, which is a union (really a struct) of type
// RanrNotifyDataUnion.
for {
ev, err := X.WaitForEvent()
if err != nil {
log.Fatal(err)
}
fmt.Println(ev)
}
}

View File

@ -0,0 +1,306 @@
// The shapes example shows how to draw basic shapes into a window.
// It can be considered the Go equivalent of
// https://x.org/releases/X11R7.5/doc/libxcb/tutorial/#drawingprim
// Four points, a single polyline, two line segments,
// two rectangles and two arcs are drawn.
// In addition to this, we will also write some text
// and fill a rectangle.
package main
import (
"fmt"
"unicode/utf16"
"github.com/jezek/xgb"
"github.com/jezek/xgb/xproto"
)
func main() {
X, err := xgb.NewConn()
if err != nil {
fmt.Println("error connecting to X:", err)
return
}
defer X.Close()
setup := xproto.Setup(X)
screen := setup.DefaultScreen(X)
wid, err := xproto.NewWindowId(X)
if err != nil {
fmt.Println("error creating window id:", err)
return
}
draw := xproto.Drawable(wid) // for now, we simply draw into the window
// Create the window
xproto.CreateWindow(X, screen.RootDepth, wid, screen.Root,
0, 0, 180, 200, 8, // X, Y, width, height, *border width*
xproto.WindowClassInputOutput, screen.RootVisual,
xproto.CwBackPixel|xproto.CwEventMask,
[]uint32{screen.WhitePixel, xproto.EventMaskStructureNotify | xproto.EventMaskExposure})
// Map the window on the screen
xproto.MapWindow(X, wid)
// Up to here everything is the same as in the `create-window` example.
// We opened a connection, created and mapped the window.
// But this time we'll be drawing some basic shapes.
// Note how this time the border width is set to 8 instead of 0.
//
// First of all we need to create a context to draw with.
// The graphics context combines all properties (e.g. color, line width, font, fill style, ...)
// that should be used to draw something. All available properties
//
// These properties can be set by or'ing their keys (xproto.Gc*)
// and adding the value to the end of the values array.
// The order in which the values have to be given corresponds to the order that they defined
// mentioned in `xproto`.
//
// Here we create a new graphics context
// which only has the foreground (color) value set to black:
foreground, err := xproto.NewGcontextId(X)
if err != nil {
fmt.Println("error creating foreground context:", err)
return
}
mask := uint32(xproto.GcForeground)
values := []uint32{screen.BlackPixel}
xproto.CreateGC(X, foreground, draw, mask, values)
// It is possible to set the foreground value to something different.
// In production, this should use xorg color maps instead for compatibility
// but for demonstration setting the color directly also works.
// For more information on color maps, see the xcb documentation:
// https://x.org/releases/X11R7.5/doc/libxcb/tutorial/#usecolor
red, err := xproto.NewGcontextId(X)
if err != nil {
fmt.Println("error creating red context:", err)
return
}
mask = uint32(xproto.GcForeground)
values = []uint32{0xff0000}
xproto.CreateGC(X, red, draw, mask, values)
// We'll create another graphics context that draws thick lines:
thick, err := xproto.NewGcontextId(X)
if err != nil {
fmt.Println("error creating thick context:", err)
return
}
mask = uint32(xproto.GcLineWidth)
values = []uint32{10}
xproto.CreateGC(X, thick, draw, mask, values)
// It is even possible to set multiple properties at once.
// Only remember to put the values in the same order as they're
// defined in `xproto`:
// Foreground is defined first, so we also set it's value first.
// LineWidth comes second.
blue, err := xproto.NewGcontextId(X)
if err != nil {
fmt.Println("error creating blue context:", err)
return
}
mask = uint32(xproto.GcForeground | xproto.GcLineWidth)
values = []uint32{0x0000ff, 4}
xproto.CreateGC(X, blue, draw, mask, values)
// Properties of an already created gc can also be changed
// if the original values aren't needed anymore.
// In this case, we will change the line width
// and cap (line corner) style of our foreground context,
// to smooth out the polyline:
mask = uint32(xproto.GcLineWidth | xproto.GcCapStyle)
values = []uint32{3, xproto.CapStyleRound}
xproto.ChangeGC(X, foreground, mask, values)
// Writing text needs a bit more setup -- we first have
// to open the required font.
font, err := xproto.NewFontId(X)
if err != nil {
fmt.Println("error creating font id:", err)
return
}
// The font identifier that has to be passed to X for opening the font
// sets all font properties:
// publisher-family-weight-slant-width-adstyl-pxlsz-ptSz-resx-resy-spc-avgWidth-registry-encoding
// For all available fonts, install and run xfontsel.
//
// To load any available font, set all fields to an asterisk.
// To specify a font, set one or multiple fields.
// This can also be seen in xfontsel -- initially every field is set to *,
// however, the more fields are set, the fewer fonts match.
//
// Using a specific font (e.g. Gnu Unifont) can be as easy as
// "-gnu-unifont-*-*-*-*-16-*-*-*-*-*-*-*"
//
// To load any font that is encoded for usage
// with Unicode characters, one would use
// fontname := "-*-*-*-*-*-*-14-*-*-*-*-*-iso10646-1"
//
// For now, we'll simply stick with the fixed font which is available
// to every X session:
fontname := "-*-fixed-*-*-*-*-14-*-*-*-*-*-*-*"
err = xproto.OpenFontChecked(X, font, uint16(len(fontname)), fontname).Check()
if err != nil {
fmt.Println("failed opening the font:", err)
return
}
// And create a context from it. We simply pass the font's ID to the GcFont property.
textCtx, err := xproto.NewGcontextId(X)
if err != nil {
fmt.Println("error creating text context:", err)
return
}
mask = uint32(xproto.GcForeground | xproto.GcBackground | xproto.GcFont)
values = []uint32{screen.BlackPixel, screen.WhitePixel, uint32(font)}
xproto.CreateGC(X, textCtx, draw, mask, values)
text := convertStringToChar2b("Hellö World!") // Unicode capable!
// Close the font handle:
xproto.CloseFont(X, font)
// After all, writing text is way more comfortable using Xft - it supports TrueType,
// and overall better configuration.
points := []xproto.Point{
{X: 10, Y: 10},
{X: 20, Y: 10},
{X: 30, Y: 10},
{X: 40, Y: 10},
}
// A polyline is essentially a line with multiple points.
// The first point is placed absolutely inside the window,
// while every other point is placed relative to the one before it.
polyline := []xproto.Point{
{X: 50, Y: 10},
{X: 5, Y: 20}, // move 5 to the right, 20 down
{X: 25, Y: -20}, // move 25 to the right, 20 up - notice how this point is level again with the first point
{X: 10, Y: 10}, // move 10 to the right, 10 down
}
segments := []xproto.Segment{
{X1: 100, Y1: 10, X2: 140, Y2: 30},
{X1: 110, Y1: 25, X2: 130, Y2: 60},
{X1: 0, Y1: 160, X2: 90, Y2: 100},
}
// Rectangles have a start coordinate (upper left) and width and height.
rectangles := []xproto.Rectangle{
{X: 10, Y: 50, Width: 40, Height: 20},
{X: 80, Y: 50, Width: 10, Height: 40},
}
// This rectangle we will use to demonstrate filling a shape.
rectangles2 := []xproto.Rectangle{
{X: 150, Y: 50, Width: 20, Height: 60},
}
// Arcs are defined by a top left position (notice where the third line goes to)
// their width and height, a starting and end angle.
// Angles are defined in units of 1/64 of a single degree,
// so we have to multiply the degrees by 64 (or left shift them by 6).
arcs := []xproto.Arc{
{X: 10, Y: 100, Width: 60, Height: 40, Angle1: 0 << 6, Angle2: 90 << 6},
{X: 90, Y: 100, Width: 55, Height: 40, Angle1: 20 << 6, Angle2: 270 << 6},
}
for {
evt, err := X.WaitForEvent()
if err != nil {
fmt.Println("error reading event:", err)
return
} else if evt == nil {
return
}
switch evt.(type) {
case xproto.ExposeEvent:
// Draw the four points we specified earlier.
// Notice how we use the `foreground` context to draw them in black.
// Also notice how even though we changed the line width to 3,
// these still only appear as a single pixel.
// To draw points that are bigger than a single pixel,
// one has to either fill rectangles, circles or polygons.
xproto.PolyPoint(X, xproto.CoordModeOrigin, draw, foreground, points)
// Draw the polyline. This time we specified `xproto.CoordModePrevious`,
// which means that every point is placed relatively to the previous.
// If we were to use `xproto.CoordModeOrigin` instead,
// we could specify each point absolutely on the screen.
// It is also possible to use `xproto.CoordModePrevious` for drawing *points*
// which means that each point would be specified relative to the previous one,
// just as we did with the polyline.
xproto.PolyLine(X, xproto.CoordModePrevious, draw, foreground, polyline)
// Draw two lines in red.
xproto.PolySegment(X, draw, red, segments)
// Draw two thick rectangles.
// The line width only specifies the width of the outline.
// Notice how the second rectangle gets completely filled
// due to the line width.
xproto.PolyRectangle(X, draw, thick, rectangles)
// Draw the circular arcs in blue.
xproto.PolyArc(X, draw, blue, arcs)
// There's also a fill variant for all drawing commands:
xproto.PolyFillRectangle(X, draw, red, rectangles2)
// Draw the text. Xorg currently knows two ways of specifying text:
// a) the (extended) ASCII encoding using ImageText8(..., []byte)
// b) UTF16 encoding using ImageText16(..., []Char2b) -- Char2b is
// a structure consisting of two bytes.
// At the bottom of this example, there are two utility functions that help
// convert a go string into an array of Char2b's.
xproto.ImageText16(X, byte(len(text)), draw, textCtx, 10, 160, text)
case xproto.DestroyNotifyEvent:
return
}
}
}
// Char2b is defined as
// Byte1 byte
// Byte2 byte
// and is used as a utf16 character.
// This function takes a string and converts each rune into a char2b.
func convertStringToChar2b(s string) []xproto.Char2b {
var chars []xproto.Char2b
var p []uint16
for _, r := range []rune(s) {
p = utf16.Encode([]rune{r})
if len(p) == 1 {
chars = append(chars, convertUint16ToChar2b(p[0]))
} else {
// If the utf16 representation is larger than 2 bytes
// we can not use it and insert a blank instead:
chars = append(chars, xproto.Char2b{Byte1: 0, Byte2: 32})
}
}
return chars
}
// convertUint16ToChar2b converts a uint16 (which is basically two bytes)
// into a Char2b by using the higher 8 bits of u as Byte1
// and the lower 8 bits of u as Byte2.
func convertUint16ToChar2b(u uint16) xproto.Char2b {
return xproto.Char2b{
Byte1: byte((u & 0xff00) >> 8),
Byte2: byte((u & 0x00ff)),
}
}

View File

@ -0,0 +1,40 @@
// Example xinerama shows how to query the geometry of all active heads.
package main
import (
"fmt"
"log"
"github.com/jezek/xgb"
"github.com/jezek/xgb/xinerama"
)
func main() {
X, err := xgb.NewConn()
if err != nil {
log.Fatal(err)
}
// Initialize the Xinerama extension.
// The appropriate 'Init' function must be run for *every*
// extension before any of its requests can be used.
err = xinerama.Init(X)
if err != nil {
log.Fatal(err)
}
// Issue a request to get the screen information.
reply, err := xinerama.QueryScreens(X).Reply()
if err != nil {
log.Fatal(err)
}
// reply.Number is the number of active heads, while reply.ScreenInfo
// is a slice of XineramaScreenInfo containing the rectangle geometry
// of each head.
fmt.Printf("Number of heads: %d\n", reply.Number)
for i, screen := range reply.ScreenInfo {
fmt.Printf("%d :: X: %d, Y: %d, Width: %d, Height: %d\n",
i, screen.XOrg, screen.YOrg, screen.Width, screen.Height)
}
}

165
vend/xgb/ge/ge.go Normal file
View File

@ -0,0 +1,165 @@
// Package ge is the X client API for the Generic Event Extension extension.
package ge
// This file is automatically generated from ge.xml. Edit at your peril!
import (
"github.com/jezek/xgb"
"github.com/jezek/xgb/xproto"
)
// Init must be called before using the Generic Event Extension extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 23, "Generic Event Extension").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named Generic Event Extension could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["Generic Event Extension"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["Generic Event Extension"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["Generic Event Extension"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["Generic Event Extension"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["Generic Event Extension"] = make(map[int]xgb.NewErrorFun)
}
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// QueryVersionCookie is a cookie used only for QueryVersion requests.
type QueryVersionCookie struct {
*xgb.Cookie
}
// QueryVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryVersionCookie.Reply()
func QueryVersion(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Generic Event Extension"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'Generic Event Extension'. ge.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryVersionUnchecked(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Generic Event Extension"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'Generic Event Extension'. ge.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionReply represents the data returned from a QueryVersion request.
type QueryVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
MajorVersion uint16
MinorVersion uint16
// padding: 20 bytes
}
// Reply blocks and returns the reply data for a QueryVersion request.
func (cook QueryVersionCookie) Reply() (*QueryVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryVersionReply(buf), nil
}
// queryVersionReply reads a byte slice into a QueryVersionReply value.
func queryVersionReply(buf []byte) *QueryVersionReply {
v := new(QueryVersionReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.MajorVersion = xgb.Get16(buf[b:])
b += 2
v.MinorVersion = xgb.Get16(buf[b:])
b += 2
b += 20 // padding
return v
}
// Write request to wire for QueryVersion
// queryVersionRequest writes a QueryVersion request to a byte slice.
func queryVersionRequest(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Generic Event Extension"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put16(buf[b:], ClientMajorVersion)
b += 2
xgb.Put16(buf[b:], ClientMinorVersion)
b += 2
return buf
}

10930
vend/xgb/glx/glx.go Normal file

File diff suppressed because it is too large Load Diff

3
vend/xgb/go.mod Normal file
View File

@ -0,0 +1,3 @@
module github.com/jezek/xgb
go 1.11

105
vend/xgb/help.go Normal file
View File

@ -0,0 +1,105 @@
package xgb
/*
help.go is meant to contain a rough hodge podge of functions that are mainly
used in the auto generated code. Indeed, several functions here are simple
wrappers so that the sub-packages don't need to be smart about which stdlib
packages to import.
Also, the 'Get..' and 'Put..' functions are used through the core xgb package
too. (xgbutil uses them too.)
*/
import (
"fmt"
"strings"
)
// StringsJoin is an alias to strings.Join. It allows us to avoid having to
// import 'strings' in each of the generated Go files.
func StringsJoin(ss []string, sep string) string {
return strings.Join(ss, sep)
}
// Sprintf is so we don't need to import 'fmt' in the generated Go files.
func Sprintf(format string, v ...interface{}) string {
return fmt.Sprintf(format, v...)
}
// Errorf is just a wrapper for fmt.Errorf. Exists for the same reason
// that 'stringsJoin' and 'sprintf' exists.
func Errorf(format string, v ...interface{}) error {
return fmt.Errorf(format, v...)
}
// Pad a length to align on 4 bytes.
func Pad(n int) int {
return (n + 3) & ^3
}
// PopCount counts the number of bits set in a value list mask.
func PopCount(mask0 int) int {
mask := uint32(mask0)
n := 0
for i := uint32(0); i < 32; i++ {
if mask&(1<<i) != 0 {
n++
}
}
return n
}
// Put16 takes a 16 bit integer and copies it into a byte slice.
func Put16(buf []byte, v uint16) {
buf[0] = byte(v)
buf[1] = byte(v >> 8)
}
// Put32 takes a 32 bit integer and copies it into a byte slice.
func Put32(buf []byte, v uint32) {
buf[0] = byte(v)
buf[1] = byte(v >> 8)
buf[2] = byte(v >> 16)
buf[3] = byte(v >> 24)
}
// Put64 takes a 64 bit integer and copies it into a byte slice.
func Put64(buf []byte, v uint64) {
buf[0] = byte(v)
buf[1] = byte(v >> 8)
buf[2] = byte(v >> 16)
buf[3] = byte(v >> 24)
buf[4] = byte(v >> 32)
buf[5] = byte(v >> 40)
buf[6] = byte(v >> 48)
buf[7] = byte(v >> 56)
}
// Get16 constructs a 16 bit integer from the beginning of a byte slice.
func Get16(buf []byte) uint16 {
v := uint16(buf[0])
v |= uint16(buf[1]) << 8
return v
}
// Get32 constructs a 32 bit integer from the beginning of a byte slice.
func Get32(buf []byte) uint32 {
v := uint32(buf[0])
v |= uint32(buf[1]) << 8
v |= uint32(buf[2]) << 16
v |= uint32(buf[3]) << 24
return v
}
// Get64 constructs a 64 bit integer from the beginning of a byte slice.
func Get64(buf []byte) uint64 {
v := uint64(buf[0])
v |= uint64(buf[1]) << 8
v |= uint64(buf[2]) << 16
v |= uint64(buf[3]) << 24
v |= uint64(buf[4]) << 32
v |= uint64(buf[5]) << 40
v |= uint64(buf[6]) << 48
v |= uint64(buf[7]) << 56
return v
}

6607
vend/xgb/randr/randr.go Normal file

File diff suppressed because it is too large Load Diff

1204
vend/xgb/record/record.go Normal file

File diff suppressed because it is too large Load Diff

4029
vend/xgb/render/render.go Normal file

File diff suppressed because it is too large Load Diff

1110
vend/xgb/res/res.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,690 @@
// Package screensaver is the X client API for the MIT-SCREEN-SAVER extension.
package screensaver
// This file is automatically generated from screensaver.xml. Edit at your peril!
import (
"github.com/jezek/xgb"
"github.com/jezek/xgb/xproto"
)
// Init must be called before using the MIT-SCREEN-SAVER extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 16, "MIT-SCREEN-SAVER").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named MIT-SCREEN-SAVER could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["MIT-SCREEN-SAVER"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["MIT-SCREEN-SAVER"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["MIT-SCREEN-SAVER"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["MIT-SCREEN-SAVER"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["MIT-SCREEN-SAVER"] = make(map[int]xgb.NewErrorFun)
}
const (
EventNotifyMask = 1
EventCycleMask = 2
)
const (
KindBlanked = 0
KindInternal = 1
KindExternal = 2
)
// Notify is the event number for a NotifyEvent.
const Notify = 0
type NotifyEvent struct {
Sequence uint16
State byte
Time xproto.Timestamp
Root xproto.Window
Window xproto.Window
Kind byte
Forced bool
// padding: 14 bytes
}
// NotifyEventNew constructs a NotifyEvent value that implements xgb.Event from a byte slice.
func NotifyEventNew(buf []byte) xgb.Event {
v := NotifyEvent{}
b := 1 // don't read event number
v.State = buf[b]
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Time = xproto.Timestamp(xgb.Get32(buf[b:]))
b += 4
v.Root = xproto.Window(xgb.Get32(buf[b:]))
b += 4
v.Window = xproto.Window(xgb.Get32(buf[b:]))
b += 4
v.Kind = buf[b]
b += 1
if buf[b] == 1 {
v.Forced = true
} else {
v.Forced = false
}
b += 1
b += 14 // padding
return v
}
// Bytes writes a NotifyEvent value to a byte slice.
func (v NotifyEvent) Bytes() []byte {
buf := make([]byte, 32)
b := 0
// write event number
buf[b] = 0
b += 1
buf[b] = v.State
b += 1
b += 2 // skip sequence number
xgb.Put32(buf[b:], uint32(v.Time))
b += 4
xgb.Put32(buf[b:], uint32(v.Root))
b += 4
xgb.Put32(buf[b:], uint32(v.Window))
b += 4
buf[b] = v.Kind
b += 1
if v.Forced {
buf[b] = 1
} else {
buf[b] = 0
}
b += 1
b += 14 // padding
return buf
}
// SequenceId returns the sequence id attached to the Notify event.
// Events without a sequence number (KeymapNotify) return 0.
// This is mostly used internally.
func (v NotifyEvent) SequenceId() uint16 {
return v.Sequence
}
// String is a rudimentary string representation of NotifyEvent.
func (v NotifyEvent) String() string {
fieldVals := make([]string, 0, 7)
fieldVals = append(fieldVals, xgb.Sprintf("Sequence: %d", v.Sequence))
fieldVals = append(fieldVals, xgb.Sprintf("State: %d", v.State))
fieldVals = append(fieldVals, xgb.Sprintf("Time: %d", v.Time))
fieldVals = append(fieldVals, xgb.Sprintf("Root: %d", v.Root))
fieldVals = append(fieldVals, xgb.Sprintf("Window: %d", v.Window))
fieldVals = append(fieldVals, xgb.Sprintf("Kind: %d", v.Kind))
fieldVals = append(fieldVals, xgb.Sprintf("Forced: %t", v.Forced))
return "Notify {" + xgb.StringsJoin(fieldVals, ", ") + "}"
}
func init() {
xgb.NewExtEventFuncs["MIT-SCREEN-SAVER"][0] = NotifyEventNew
}
const (
StateOff = 0
StateOn = 1
StateCycle = 2
StateDisabled = 3
)
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// QueryInfoCookie is a cookie used only for QueryInfo requests.
type QueryInfoCookie struct {
*xgb.Cookie
}
// QueryInfo sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryInfoCookie.Reply()
func QueryInfo(c *xgb.Conn, Drawable xproto.Drawable) QueryInfoCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'QueryInfo' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryInfoRequest(c, Drawable), cookie)
return QueryInfoCookie{cookie}
}
// QueryInfoUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryInfoUnchecked(c *xgb.Conn, Drawable xproto.Drawable) QueryInfoCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'QueryInfo' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryInfoRequest(c, Drawable), cookie)
return QueryInfoCookie{cookie}
}
// QueryInfoReply represents the data returned from a QueryInfo request.
type QueryInfoReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
State byte
SaverWindow xproto.Window
MsUntilServer uint32
MsSinceUserInput uint32
EventMask uint32
Kind byte
// padding: 7 bytes
}
// Reply blocks and returns the reply data for a QueryInfo request.
func (cook QueryInfoCookie) Reply() (*QueryInfoReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryInfoReply(buf), nil
}
// queryInfoReply reads a byte slice into a QueryInfoReply value.
func queryInfoReply(buf []byte) *QueryInfoReply {
v := new(QueryInfoReply)
b := 1 // skip reply determinant
v.State = buf[b]
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.SaverWindow = xproto.Window(xgb.Get32(buf[b:]))
b += 4
v.MsUntilServer = xgb.Get32(buf[b:])
b += 4
v.MsSinceUserInput = xgb.Get32(buf[b:])
b += 4
v.EventMask = xgb.Get32(buf[b:])
b += 4
v.Kind = buf[b]
b += 1
b += 7 // padding
return v
}
// Write request to wire for QueryInfo
// queryInfoRequest writes a QueryInfo request to a byte slice.
func queryInfoRequest(c *xgb.Conn, Drawable xproto.Drawable) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SCREEN-SAVER"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 1 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
return buf
}
// QueryVersionCookie is a cookie used only for QueryVersion requests.
type QueryVersionCookie struct {
*xgb.Cookie
}
// QueryVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryVersionCookie.Reply()
func QueryVersion(c *xgb.Conn, ClientMajorVersion byte, ClientMinorVersion byte) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryVersionUnchecked(c *xgb.Conn, ClientMajorVersion byte, ClientMinorVersion byte) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionReply represents the data returned from a QueryVersion request.
type QueryVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
ServerMajorVersion uint16
ServerMinorVersion uint16
// padding: 20 bytes
}
// Reply blocks and returns the reply data for a QueryVersion request.
func (cook QueryVersionCookie) Reply() (*QueryVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryVersionReply(buf), nil
}
// queryVersionReply reads a byte slice into a QueryVersionReply value.
func queryVersionReply(buf []byte) *QueryVersionReply {
v := new(QueryVersionReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.ServerMajorVersion = xgb.Get16(buf[b:])
b += 2
v.ServerMinorVersion = xgb.Get16(buf[b:])
b += 2
b += 20 // padding
return v
}
// Write request to wire for QueryVersion
// queryVersionRequest writes a QueryVersion request to a byte slice.
func queryVersionRequest(c *xgb.Conn, ClientMajorVersion byte, ClientMinorVersion byte) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SCREEN-SAVER"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
buf[b] = ClientMajorVersion
b += 1
buf[b] = ClientMinorVersion
b += 1
b += 2 // padding
return buf
}
// SelectInputCookie is a cookie used only for SelectInput requests.
type SelectInputCookie struct {
*xgb.Cookie
}
// SelectInput sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func SelectInput(c *xgb.Conn, Drawable xproto.Drawable, EventMask uint32) SelectInputCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'SelectInput' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(selectInputRequest(c, Drawable, EventMask), cookie)
return SelectInputCookie{cookie}
}
// SelectInputChecked sends a checked request.
// If an error occurs, it can be retrieved using SelectInputCookie.Check()
func SelectInputChecked(c *xgb.Conn, Drawable xproto.Drawable, EventMask uint32) SelectInputCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'SelectInput' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(selectInputRequest(c, Drawable, EventMask), cookie)
return SelectInputCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook SelectInputCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for SelectInput
// selectInputRequest writes a SelectInput request to a byte slice.
func selectInputRequest(c *xgb.Conn, Drawable xproto.Drawable, EventMask uint32) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SCREEN-SAVER"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 2 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
xgb.Put32(buf[b:], EventMask)
b += 4
return buf
}
// SetAttributesCookie is a cookie used only for SetAttributes requests.
type SetAttributesCookie struct {
*xgb.Cookie
}
// SetAttributes sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func SetAttributes(c *xgb.Conn, Drawable xproto.Drawable, X int16, Y int16, Width uint16, Height uint16, BorderWidth uint16, Class byte, Depth byte, Visual xproto.Visualid, ValueMask uint32, ValueList []uint32) SetAttributesCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'SetAttributes' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(setAttributesRequest(c, Drawable, X, Y, Width, Height, BorderWidth, Class, Depth, Visual, ValueMask, ValueList), cookie)
return SetAttributesCookie{cookie}
}
// SetAttributesChecked sends a checked request.
// If an error occurs, it can be retrieved using SetAttributesCookie.Check()
func SetAttributesChecked(c *xgb.Conn, Drawable xproto.Drawable, X int16, Y int16, Width uint16, Height uint16, BorderWidth uint16, Class byte, Depth byte, Visual xproto.Visualid, ValueMask uint32, ValueList []uint32) SetAttributesCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'SetAttributes' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(setAttributesRequest(c, Drawable, X, Y, Width, Height, BorderWidth, Class, Depth, Visual, ValueMask, ValueList), cookie)
return SetAttributesCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook SetAttributesCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for SetAttributes
// setAttributesRequest writes a SetAttributes request to a byte slice.
func setAttributesRequest(c *xgb.Conn, Drawable xproto.Drawable, X int16, Y int16, Width uint16, Height uint16, BorderWidth uint16, Class byte, Depth byte, Visual xproto.Visualid, ValueMask uint32, ValueList []uint32) []byte {
size := xgb.Pad((28 + xgb.Pad((4 * xgb.PopCount(int(ValueMask))))))
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SCREEN-SAVER"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 3 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
xgb.Put16(buf[b:], uint16(X))
b += 2
xgb.Put16(buf[b:], uint16(Y))
b += 2
xgb.Put16(buf[b:], Width)
b += 2
xgb.Put16(buf[b:], Height)
b += 2
xgb.Put16(buf[b:], BorderWidth)
b += 2
buf[b] = Class
b += 1
buf[b] = Depth
b += 1
xgb.Put32(buf[b:], uint32(Visual))
b += 4
xgb.Put32(buf[b:], ValueMask)
b += 4
for i := 0; i < xgb.PopCount(int(ValueMask)); i++ {
xgb.Put32(buf[b:], ValueList[i])
b += 4
}
b = xgb.Pad(b)
return buf
}
// SuspendCookie is a cookie used only for Suspend requests.
type SuspendCookie struct {
*xgb.Cookie
}
// Suspend sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Suspend(c *xgb.Conn, Suspend uint32) SuspendCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'Suspend' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(suspendRequest(c, Suspend), cookie)
return SuspendCookie{cookie}
}
// SuspendChecked sends a checked request.
// If an error occurs, it can be retrieved using SuspendCookie.Check()
func SuspendChecked(c *xgb.Conn, Suspend uint32) SuspendCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'Suspend' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(suspendRequest(c, Suspend), cookie)
return SuspendCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook SuspendCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for Suspend
// suspendRequest writes a Suspend request to a byte slice.
func suspendRequest(c *xgb.Conn, Suspend uint32) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SCREEN-SAVER"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 5 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], Suspend)
b += 4
return buf
}
// UnsetAttributesCookie is a cookie used only for UnsetAttributes requests.
type UnsetAttributesCookie struct {
*xgb.Cookie
}
// UnsetAttributes sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func UnsetAttributes(c *xgb.Conn, Drawable xproto.Drawable) UnsetAttributesCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'UnsetAttributes' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(unsetAttributesRequest(c, Drawable), cookie)
return UnsetAttributesCookie{cookie}
}
// UnsetAttributesChecked sends a checked request.
// If an error occurs, it can be retrieved using UnsetAttributesCookie.Check()
func UnsetAttributesChecked(c *xgb.Conn, Drawable xproto.Drawable) UnsetAttributesCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'UnsetAttributes' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(unsetAttributesRequest(c, Drawable), cookie)
return UnsetAttributesCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook UnsetAttributesCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for UnsetAttributes
// unsetAttributesRequest writes a UnsetAttributes request to a byte slice.
func unsetAttributesRequest(c *xgb.Conn, Drawable xproto.Drawable) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SCREEN-SAVER"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 4 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
return buf
}

1025
vend/xgb/shape/shape.go Normal file

File diff suppressed because it is too large Load Diff

945
vend/xgb/shm/shm.go Normal file
View File

@ -0,0 +1,945 @@
// Package shm is the X client API for the MIT-SHM extension.
package shm
// This file is automatically generated from shm.xml. Edit at your peril!
import (
"github.com/jezek/xgb"
"github.com/jezek/xgb/xproto"
)
// Init must be called before using the MIT-SHM extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 7, "MIT-SHM").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named MIT-SHM could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["MIT-SHM"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["MIT-SHM"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["MIT-SHM"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["MIT-SHM"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["MIT-SHM"] = make(map[int]xgb.NewErrorFun)
}
// BadBadSeg is the error number for a BadBadSeg.
const BadBadSeg = 0
type BadSegError xproto.ValueError
// BadSegErrorNew constructs a BadSegError value that implements xgb.Error from a byte slice.
func BadSegErrorNew(buf []byte) xgb.Error {
v := BadSegError(xproto.ValueErrorNew(buf).(xproto.ValueError))
v.NiceName = "BadSeg"
return v
}
// SequenceId returns the sequence id attached to the BadBadSeg error.
// This is mostly used internally.
func (err BadSegError) SequenceId() uint16 {
return err.Sequence
}
// BadId returns the 'BadValue' number if one exists for the BadBadSeg error. If no bad value exists, 0 is returned.
func (err BadSegError) BadId() uint32 {
return 0
}
// Error returns a rudimentary string representation of the BadBadSeg error.
func (err BadSegError) Error() string {
fieldVals := make([]string, 0, 4)
fieldVals = append(fieldVals, "NiceName: "+err.NiceName)
fieldVals = append(fieldVals, xgb.Sprintf("Sequence: %d", err.Sequence))
fieldVals = append(fieldVals, xgb.Sprintf("BadValue: %d", err.BadValue))
fieldVals = append(fieldVals, xgb.Sprintf("MinorOpcode: %d", err.MinorOpcode))
fieldVals = append(fieldVals, xgb.Sprintf("MajorOpcode: %d", err.MajorOpcode))
return "BadBadSeg {" + xgb.StringsJoin(fieldVals, ", ") + "}"
}
func init() {
xgb.NewExtErrorFuncs["MIT-SHM"][0] = BadSegErrorNew
}
// Completion is the event number for a CompletionEvent.
const Completion = 0
type CompletionEvent struct {
Sequence uint16
// padding: 1 bytes
Drawable xproto.Drawable
MinorEvent uint16
MajorEvent byte
// padding: 1 bytes
Shmseg Seg
Offset uint32
}
// CompletionEventNew constructs a CompletionEvent value that implements xgb.Event from a byte slice.
func CompletionEventNew(buf []byte) xgb.Event {
v := CompletionEvent{}
b := 1 // don't read event number
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Drawable = xproto.Drawable(xgb.Get32(buf[b:]))
b += 4
v.MinorEvent = xgb.Get16(buf[b:])
b += 2
v.MajorEvent = buf[b]
b += 1
b += 1 // padding
v.Shmseg = Seg(xgb.Get32(buf[b:]))
b += 4
v.Offset = xgb.Get32(buf[b:])
b += 4
return v
}
// Bytes writes a CompletionEvent value to a byte slice.
func (v CompletionEvent) Bytes() []byte {
buf := make([]byte, 32)
b := 0
// write event number
buf[b] = 0
b += 1
b += 1 // padding
b += 2 // skip sequence number
xgb.Put32(buf[b:], uint32(v.Drawable))
b += 4
xgb.Put16(buf[b:], v.MinorEvent)
b += 2
buf[b] = v.MajorEvent
b += 1
b += 1 // padding
xgb.Put32(buf[b:], uint32(v.Shmseg))
b += 4
xgb.Put32(buf[b:], v.Offset)
b += 4
return buf
}
// SequenceId returns the sequence id attached to the Completion event.
// Events without a sequence number (KeymapNotify) return 0.
// This is mostly used internally.
func (v CompletionEvent) SequenceId() uint16 {
return v.Sequence
}
// String is a rudimentary string representation of CompletionEvent.
func (v CompletionEvent) String() string {
fieldVals := make([]string, 0, 7)
fieldVals = append(fieldVals, xgb.Sprintf("Sequence: %d", v.Sequence))
fieldVals = append(fieldVals, xgb.Sprintf("Drawable: %d", v.Drawable))
fieldVals = append(fieldVals, xgb.Sprintf("MinorEvent: %d", v.MinorEvent))
fieldVals = append(fieldVals, xgb.Sprintf("MajorEvent: %d", v.MajorEvent))
fieldVals = append(fieldVals, xgb.Sprintf("Shmseg: %d", v.Shmseg))
fieldVals = append(fieldVals, xgb.Sprintf("Offset: %d", v.Offset))
return "Completion {" + xgb.StringsJoin(fieldVals, ", ") + "}"
}
func init() {
xgb.NewExtEventFuncs["MIT-SHM"][0] = CompletionEventNew
}
type Seg uint32
func NewSegId(c *xgb.Conn) (Seg, error) {
id, err := c.NewId()
if err != nil {
return 0, err
}
return Seg(id), nil
}
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// AttachCookie is a cookie used only for Attach requests.
type AttachCookie struct {
*xgb.Cookie
}
// Attach sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Attach(c *xgb.Conn, Shmseg Seg, Shmid uint32, ReadOnly bool) AttachCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'Attach' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(attachRequest(c, Shmseg, Shmid, ReadOnly), cookie)
return AttachCookie{cookie}
}
// AttachChecked sends a checked request.
// If an error occurs, it can be retrieved using AttachCookie.Check()
func AttachChecked(c *xgb.Conn, Shmseg Seg, Shmid uint32, ReadOnly bool) AttachCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'Attach' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(attachRequest(c, Shmseg, Shmid, ReadOnly), cookie)
return AttachCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook AttachCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for Attach
// attachRequest writes a Attach request to a byte slice.
func attachRequest(c *xgb.Conn, Shmseg Seg, Shmid uint32, ReadOnly bool) []byte {
size := 16
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SHM"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 1 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Shmseg))
b += 4
xgb.Put32(buf[b:], Shmid)
b += 4
if ReadOnly {
buf[b] = 1
} else {
buf[b] = 0
}
b += 1
b += 3 // padding
return buf
}
// AttachFdCookie is a cookie used only for AttachFd requests.
type AttachFdCookie struct {
*xgb.Cookie
}
// AttachFd sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func AttachFd(c *xgb.Conn, Shmseg Seg, ReadOnly bool) AttachFdCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'AttachFd' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(attachFdRequest(c, Shmseg, ReadOnly), cookie)
return AttachFdCookie{cookie}
}
// AttachFdChecked sends a checked request.
// If an error occurs, it can be retrieved using AttachFdCookie.Check()
func AttachFdChecked(c *xgb.Conn, Shmseg Seg, ReadOnly bool) AttachFdCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'AttachFd' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(attachFdRequest(c, Shmseg, ReadOnly), cookie)
return AttachFdCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook AttachFdCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for AttachFd
// attachFdRequest writes a AttachFd request to a byte slice.
func attachFdRequest(c *xgb.Conn, Shmseg Seg, ReadOnly bool) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SHM"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 6 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Shmseg))
b += 4
if ReadOnly {
buf[b] = 1
} else {
buf[b] = 0
}
b += 1
b += 3 // padding
return buf
}
// CreatePixmapCookie is a cookie used only for CreatePixmap requests.
type CreatePixmapCookie struct {
*xgb.Cookie
}
// CreatePixmap sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func CreatePixmap(c *xgb.Conn, Pid xproto.Pixmap, Drawable xproto.Drawable, Width uint16, Height uint16, Depth byte, Shmseg Seg, Offset uint32) CreatePixmapCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'CreatePixmap' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(createPixmapRequest(c, Pid, Drawable, Width, Height, Depth, Shmseg, Offset), cookie)
return CreatePixmapCookie{cookie}
}
// CreatePixmapChecked sends a checked request.
// If an error occurs, it can be retrieved using CreatePixmapCookie.Check()
func CreatePixmapChecked(c *xgb.Conn, Pid xproto.Pixmap, Drawable xproto.Drawable, Width uint16, Height uint16, Depth byte, Shmseg Seg, Offset uint32) CreatePixmapCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'CreatePixmap' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(createPixmapRequest(c, Pid, Drawable, Width, Height, Depth, Shmseg, Offset), cookie)
return CreatePixmapCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook CreatePixmapCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for CreatePixmap
// createPixmapRequest writes a CreatePixmap request to a byte slice.
func createPixmapRequest(c *xgb.Conn, Pid xproto.Pixmap, Drawable xproto.Drawable, Width uint16, Height uint16, Depth byte, Shmseg Seg, Offset uint32) []byte {
size := 28
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SHM"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 5 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Pid))
b += 4
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
xgb.Put16(buf[b:], Width)
b += 2
xgb.Put16(buf[b:], Height)
b += 2
buf[b] = Depth
b += 1
b += 3 // padding
xgb.Put32(buf[b:], uint32(Shmseg))
b += 4
xgb.Put32(buf[b:], Offset)
b += 4
return buf
}
// CreateSegmentCookie is a cookie used only for CreateSegment requests.
type CreateSegmentCookie struct {
*xgb.Cookie
}
// CreateSegment sends a checked request.
// If an error occurs, it will be returned with the reply by calling CreateSegmentCookie.Reply()
func CreateSegment(c *xgb.Conn, Shmseg Seg, Size uint32, ReadOnly bool) CreateSegmentCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'CreateSegment' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(createSegmentRequest(c, Shmseg, Size, ReadOnly), cookie)
return CreateSegmentCookie{cookie}
}
// CreateSegmentUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func CreateSegmentUnchecked(c *xgb.Conn, Shmseg Seg, Size uint32, ReadOnly bool) CreateSegmentCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'CreateSegment' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(createSegmentRequest(c, Shmseg, Size, ReadOnly), cookie)
return CreateSegmentCookie{cookie}
}
// CreateSegmentReply represents the data returned from a CreateSegment request.
type CreateSegmentReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
Nfd byte
// padding: 24 bytes
}
// Reply blocks and returns the reply data for a CreateSegment request.
func (cook CreateSegmentCookie) Reply() (*CreateSegmentReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return createSegmentReply(buf), nil
}
// createSegmentReply reads a byte slice into a CreateSegmentReply value.
func createSegmentReply(buf []byte) *CreateSegmentReply {
v := new(CreateSegmentReply)
b := 1 // skip reply determinant
v.Nfd = buf[b]
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
b += 24 // padding
return v
}
// Write request to wire for CreateSegment
// createSegmentRequest writes a CreateSegment request to a byte slice.
func createSegmentRequest(c *xgb.Conn, Shmseg Seg, Size uint32, ReadOnly bool) []byte {
size := 16
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SHM"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 7 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Shmseg))
b += 4
xgb.Put32(buf[b:], Size)
b += 4
if ReadOnly {
buf[b] = 1
} else {
buf[b] = 0
}
b += 1
b += 3 // padding
return buf
}
// DetachCookie is a cookie used only for Detach requests.
type DetachCookie struct {
*xgb.Cookie
}
// Detach sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Detach(c *xgb.Conn, Shmseg Seg) DetachCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'Detach' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(detachRequest(c, Shmseg), cookie)
return DetachCookie{cookie}
}
// DetachChecked sends a checked request.
// If an error occurs, it can be retrieved using DetachCookie.Check()
func DetachChecked(c *xgb.Conn, Shmseg Seg) DetachCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'Detach' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(detachRequest(c, Shmseg), cookie)
return DetachCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook DetachCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for Detach
// detachRequest writes a Detach request to a byte slice.
func detachRequest(c *xgb.Conn, Shmseg Seg) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SHM"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 2 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Shmseg))
b += 4
return buf
}
// GetImageCookie is a cookie used only for GetImage requests.
type GetImageCookie struct {
*xgb.Cookie
}
// GetImage sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetImageCookie.Reply()
func GetImage(c *xgb.Conn, Drawable xproto.Drawable, X int16, Y int16, Width uint16, Height uint16, PlaneMask uint32, Format byte, Shmseg Seg, Offset uint32) GetImageCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'GetImage' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getImageRequest(c, Drawable, X, Y, Width, Height, PlaneMask, Format, Shmseg, Offset), cookie)
return GetImageCookie{cookie}
}
// GetImageUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetImageUnchecked(c *xgb.Conn, Drawable xproto.Drawable, X int16, Y int16, Width uint16, Height uint16, PlaneMask uint32, Format byte, Shmseg Seg, Offset uint32) GetImageCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'GetImage' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getImageRequest(c, Drawable, X, Y, Width, Height, PlaneMask, Format, Shmseg, Offset), cookie)
return GetImageCookie{cookie}
}
// GetImageReply represents the data returned from a GetImage request.
type GetImageReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
Depth byte
Visual xproto.Visualid
Size uint32
}
// Reply blocks and returns the reply data for a GetImage request.
func (cook GetImageCookie) Reply() (*GetImageReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getImageReply(buf), nil
}
// getImageReply reads a byte slice into a GetImageReply value.
func getImageReply(buf []byte) *GetImageReply {
v := new(GetImageReply)
b := 1 // skip reply determinant
v.Depth = buf[b]
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.Visual = xproto.Visualid(xgb.Get32(buf[b:]))
b += 4
v.Size = xgb.Get32(buf[b:])
b += 4
return v
}
// Write request to wire for GetImage
// getImageRequest writes a GetImage request to a byte slice.
func getImageRequest(c *xgb.Conn, Drawable xproto.Drawable, X int16, Y int16, Width uint16, Height uint16, PlaneMask uint32, Format byte, Shmseg Seg, Offset uint32) []byte {
size := 32
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SHM"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 4 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
xgb.Put16(buf[b:], uint16(X))
b += 2
xgb.Put16(buf[b:], uint16(Y))
b += 2
xgb.Put16(buf[b:], Width)
b += 2
xgb.Put16(buf[b:], Height)
b += 2
xgb.Put32(buf[b:], PlaneMask)
b += 4
buf[b] = Format
b += 1
b += 3 // padding
xgb.Put32(buf[b:], uint32(Shmseg))
b += 4
xgb.Put32(buf[b:], Offset)
b += 4
return buf
}
// PutImageCookie is a cookie used only for PutImage requests.
type PutImageCookie struct {
*xgb.Cookie
}
// PutImage sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func PutImage(c *xgb.Conn, Drawable xproto.Drawable, Gc xproto.Gcontext, TotalWidth uint16, TotalHeight uint16, SrcX uint16, SrcY uint16, SrcWidth uint16, SrcHeight uint16, DstX int16, DstY int16, Depth byte, Format byte, SendEvent byte, Shmseg Seg, Offset uint32) PutImageCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'PutImage' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(putImageRequest(c, Drawable, Gc, TotalWidth, TotalHeight, SrcX, SrcY, SrcWidth, SrcHeight, DstX, DstY, Depth, Format, SendEvent, Shmseg, Offset), cookie)
return PutImageCookie{cookie}
}
// PutImageChecked sends a checked request.
// If an error occurs, it can be retrieved using PutImageCookie.Check()
func PutImageChecked(c *xgb.Conn, Drawable xproto.Drawable, Gc xproto.Gcontext, TotalWidth uint16, TotalHeight uint16, SrcX uint16, SrcY uint16, SrcWidth uint16, SrcHeight uint16, DstX int16, DstY int16, Depth byte, Format byte, SendEvent byte, Shmseg Seg, Offset uint32) PutImageCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'PutImage' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(putImageRequest(c, Drawable, Gc, TotalWidth, TotalHeight, SrcX, SrcY, SrcWidth, SrcHeight, DstX, DstY, Depth, Format, SendEvent, Shmseg, Offset), cookie)
return PutImageCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook PutImageCookie) Check() error {
return cook.Cookie.Check()
}
// Write request to wire for PutImage
// putImageRequest writes a PutImage request to a byte slice.
func putImageRequest(c *xgb.Conn, Drawable xproto.Drawable, Gc xproto.Gcontext, TotalWidth uint16, TotalHeight uint16, SrcX uint16, SrcY uint16, SrcWidth uint16, SrcHeight uint16, DstX int16, DstY int16, Depth byte, Format byte, SendEvent byte, Shmseg Seg, Offset uint32) []byte {
size := 40
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SHM"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 3 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
xgb.Put32(buf[b:], uint32(Gc))
b += 4
xgb.Put16(buf[b:], TotalWidth)
b += 2
xgb.Put16(buf[b:], TotalHeight)
b += 2
xgb.Put16(buf[b:], SrcX)
b += 2
xgb.Put16(buf[b:], SrcY)
b += 2
xgb.Put16(buf[b:], SrcWidth)
b += 2
xgb.Put16(buf[b:], SrcHeight)
b += 2
xgb.Put16(buf[b:], uint16(DstX))
b += 2
xgb.Put16(buf[b:], uint16(DstY))
b += 2
buf[b] = Depth
b += 1
buf[b] = Format
b += 1
buf[b] = SendEvent
b += 1
b += 1 // padding
xgb.Put32(buf[b:], uint32(Shmseg))
b += 4
xgb.Put32(buf[b:], Offset)
b += 4
return buf
}
// QueryVersionCookie is a cookie used only for QueryVersion requests.
type QueryVersionCookie struct {
*xgb.Cookie
}
// QueryVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryVersionCookie.Reply()
func QueryVersion(c *xgb.Conn) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryVersionRequest(c), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryVersionUnchecked(c *xgb.Conn) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryVersionRequest(c), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionReply represents the data returned from a QueryVersion request.
type QueryVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
SharedPixmaps bool
MajorVersion uint16
MinorVersion uint16
Uid uint16
Gid uint16
PixmapFormat byte
// padding: 15 bytes
}
// Reply blocks and returns the reply data for a QueryVersion request.
func (cook QueryVersionCookie) Reply() (*QueryVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryVersionReply(buf), nil
}
// queryVersionReply reads a byte slice into a QueryVersionReply value.
func queryVersionReply(buf []byte) *QueryVersionReply {
v := new(QueryVersionReply)
b := 1 // skip reply determinant
if buf[b] == 1 {
v.SharedPixmaps = true
} else {
v.SharedPixmaps = false
}
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.MajorVersion = xgb.Get16(buf[b:])
b += 2
v.MinorVersion = xgb.Get16(buf[b:])
b += 2
v.Uid = xgb.Get16(buf[b:])
b += 2
v.Gid = xgb.Get16(buf[b:])
b += 2
v.PixmapFormat = buf[b]
b += 1
b += 15 // padding
return v
}
// Write request to wire for QueryVersion
// queryVersionRequest writes a QueryVersion request to a byte slice.
func queryVersionRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SHM"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}

29
vend/xgb/sync.go Normal file
View File

@ -0,0 +1,29 @@
package xgb
// Sync sends a round trip request and waits for the response.
// This forces all pending cookies to be dealt with.
// You actually shouldn't need to use this like you might with Xlib. Namely,
// buffers are automatically flushed using Go's channels and round trip requests
// are forced where appropriate automatically.
func (c *Conn) Sync() {
cookie := c.NewCookie(true, true)
c.NewRequest(c.getInputFocusRequest(), cookie)
cookie.Reply() // wait for the buffer to clear
}
// getInputFocusRequest writes the raw bytes to a buffer.
// It is duplicated from xproto/xproto.go.
func (c *Conn) getInputFocusRequest() []byte {
size := 4
b := 0
buf := make([]byte, size)
buf[b] = 43 // request opcode
b += 1
b += 1 // padding
Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}

426
vend/xgb/testingTools.go Normal file
View File

@ -0,0 +1,426 @@
package xgb
import (
"bytes"
"errors"
"io"
"net"
"regexp"
"runtime"
"strconv"
"strings"
"testing"
"time"
)
// Leaks monitor
type goroutine struct {
id int
name string
stack []byte
}
type leaks struct {
name string
goroutines map[int]goroutine
report []*leaks
}
func leaksMonitor(name string, monitors ...*leaks) *leaks {
return &leaks{
name,
leaks{}.collectGoroutines(),
monitors,
}
}
// ispired by https://golang.org/src/runtime/debug/stack.go?s=587:606#L21
// stack returns a formatted stack trace of all goroutines.
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
func (_ leaks) stack() []byte {
buf := make([]byte, 1024)
for {
n := runtime.Stack(buf, true)
if n < len(buf) {
return buf[:n]
}
buf = make([]byte, 2*len(buf))
}
}
func (l leaks) collectGoroutines() map[int]goroutine {
res := make(map[int]goroutine)
stacks := bytes.Split(l.stack(), []byte{'\n', '\n'})
regexpId := regexp.MustCompile(`^\s*goroutine\s*(\d+)`)
for _, st := range stacks {
lines := bytes.Split(st, []byte{'\n'})
if len(lines) < 2 {
panic("routine stach has less tnan two lines: " + string(st))
}
idMatches := regexpId.FindSubmatch(lines[0])
if len(idMatches) < 2 {
panic("no id found in goroutine stack's first line: " + string(lines[0]))
}
id, err := strconv.Atoi(string(idMatches[1]))
if err != nil {
panic("converting goroutine id to number error: " + err.Error())
}
if _, ok := res[id]; ok {
panic("2 goroutines with same id: " + strconv.Itoa(id))
}
name := strings.TrimSpace(string(lines[1]))
//filter out our stack routine
if strings.Contains(name, "xgb.leaks.stack") {
continue
}
res[id] = goroutine{id, name, st}
}
return res
}
func (l leaks) leakingGoroutines() []goroutine {
goroutines := l.collectGoroutines()
res := []goroutine{}
for id, gr := range goroutines {
if _, ok := l.goroutines[id]; ok {
continue
}
res = append(res, gr)
}
return res
}
func (l leaks) checkTesting(t *testing.T) {
if len(l.leakingGoroutines()) == 0 {
return
}
leakTimeout := 10 * time.Millisecond
time.Sleep(leakTimeout)
//t.Logf("possible goroutine leakage, waiting %v", leakTimeout)
grs := l.leakingGoroutines()
for _, gr := range grs {
t.Errorf("%s: %s is leaking", l.name, gr.name)
//t.Errorf("%s: %s is leaking\n%v", l.name, gr.name, string(gr.stack))
}
for _, rl := range l.report {
rl.ignoreLeak(grs...)
}
}
func (l *leaks) ignoreLeak(grs ...goroutine) {
for _, gr := range grs {
l.goroutines[gr.id] = gr
}
}
// dummy net.Conn
type dAddr struct {
s string
}
func (_ dAddr) Network() string { return "dummy" }
func (a dAddr) String() string { return a.s }
var (
dNCErrNotImplemented = errors.New("command not implemented")
dNCErrClosed = errors.New("server closed")
dNCErrWrite = errors.New("server write failed")
dNCErrRead = errors.New("server read failed")
dNCErrResponse = errors.New("server response error")
)
type dNCIoResult struct {
n int
err error
}
type dNCIo struct {
b []byte
result chan dNCIoResult
}
type dNCCWriteLock struct{}
type dNCCWriteUnlock struct{}
type dNCCWriteError struct{}
type dNCCWriteSuccess struct{}
type dNCCReadLock struct{}
type dNCCReadUnlock struct{}
type dNCCReadError struct{}
type dNCCReadSuccess struct{}
// dummy net.Conn interface. Needs to be constructed via newDummyNetConn([...]) function.
type dNC struct {
reply func([]byte) []byte
addr dAddr
in, out chan dNCIo
control chan interface{}
done chan struct{}
}
// Results running dummy server, satisfying net.Conn interface for test purposes.
// 'name' parameter will be returned via (*dNC).Local/RemoteAddr().String()
// 'reply' parameter function will be runned only on successful (*dNC).Write(b) with 'b' as parameter to 'reply'. The result will be stored in internal buffer and can be retrieved later via (*dNC).Read([...]) method.
// It is users responsibility to stop and clean up resources with (*dNC).Close, if not needed anymore.
// By default, the (*dNC).Write([...]) and (*dNC).Read([...]) methods are unlocked and will not result in error.
//TODO make (*dNC).SetDeadline, (*dNC).SetReadDeadline, (*dNC).SetWriteDeadline work proprely.
func newDummyNetConn(name string, reply func([]byte) []byte) *dNC {
s := &dNC{
reply,
dAddr{name},
make(chan dNCIo), make(chan dNCIo),
make(chan interface{}),
make(chan struct{}),
}
in, out := s.in, chan dNCIo(nil)
buf := &bytes.Buffer{}
errorRead, errorWrite := false, false
lockRead := false
go func() {
defer close(s.done)
for {
select {
case dxsio := <-in:
if errorWrite {
dxsio.result <- dNCIoResult{0, dNCErrWrite}
break
}
response := s.reply(dxsio.b)
buf.Write(response)
dxsio.result <- dNCIoResult{len(dxsio.b), nil}
if !lockRead && buf.Len() > 0 && out == nil {
out = s.out
}
case dxsio := <-out:
if errorRead {
dxsio.result <- dNCIoResult{0, dNCErrRead}
break
}
n, err := buf.Read(dxsio.b)
dxsio.result <- dNCIoResult{n, err}
if buf.Len() == 0 {
out = nil
}
case ci := <-s.control:
if ci == nil {
return
}
switch ci.(type) {
case dNCCWriteLock:
in = nil
case dNCCWriteUnlock:
in = s.in
case dNCCWriteError:
errorWrite = true
case dNCCWriteSuccess:
errorWrite = false
case dNCCReadLock:
out = nil
lockRead = true
case dNCCReadUnlock:
lockRead = false
if buf.Len() > 0 && out == nil {
out = s.out
}
case dNCCReadError:
errorRead = true
case dNCCReadSuccess:
errorRead = false
default:
}
}
}
}()
return s
}
// Shuts down dummy net.Conn server. Every blocking or future method calls will do nothing and result in error.
// Result will be dNCErrClosed if server was allready closed.
// Server can not be unclosed.
func (s *dNC) Close() error {
select {
case s.control <- nil:
<-s.done
return nil
case <-s.done:
}
return dNCErrClosed
}
// Performs a write action to server.
// If not locked by (*dNC).WriteLock, it results in error or success. If locked, this method will block until unlocked, or closed.
//
// This method can be set to result in error or success, via (*dNC).WriteError() or (*dNC).WriteSuccess() methods.
//
// If setted to result in error, the 'reply' function will NOT be called and internal buffer will NOT increasethe.
// Result will be (0, dNCErrWrite).
//
// If setted to result in success, the 'reply' function will be called and its result will be writen to internal buffer.
// If there is something in the internal buffer, the (*dNC).Read([...]) will be unblocked (if not previously locked with (*dNC).ReadLock).
// Result will be (len(b), nil)
//
// If server was closed previously, result will be (0, dNCErrClosed).
func (s *dNC) Write(b []byte) (int, error) {
resChan := make(chan dNCIoResult)
select {
case s.in <- dNCIo{b, resChan}:
res := <-resChan
return res.n, res.err
case <-s.done:
}
return 0, dNCErrClosed
}
// Performs a read action from server.
// If locked by (*dNC).ReadLock(), this method will block until unlocked with (*dNC).ReadUnlock(), or server closes.
//
// If not locked, this method can be setted to result imidiatly in error, will block if internal buffer is empty or will perform an read operation from internal buffer.
//
// If setted to result in error via (*dNC).ReadError(), the result will be (0, dNCErrWrite).
//
// If not locked and not setted to result in error via (*dNC).ReadSuccess(), this method will block until internall buffer is not empty, than it returns the result of the buffer read operation via (*bytes.Buffer).Read([...]).
// If the internal buffer is empty after this method, all follwing (*dNC).Read([...]), requests will block until internall buffer is filled after successful write requests.
//
// If server was closed previously, result will be (0, io.EOF).
func (s *dNC) Read(b []byte) (int, error) {
resChan := make(chan dNCIoResult)
select {
case s.out <- dNCIo{b, resChan}:
res := <-resChan
return res.n, res.err
case <-s.done:
}
return 0, io.EOF
}
func (s *dNC) LocalAddr() net.Addr { return s.addr }
func (s *dNC) RemoteAddr() net.Addr { return s.addr }
func (s *dNC) SetDeadline(t time.Time) error { return dNCErrNotImplemented }
func (s *dNC) SetReadDeadline(t time.Time) error { return dNCErrNotImplemented }
func (s *dNC) SetWriteDeadline(t time.Time) error { return dNCErrNotImplemented }
func (s *dNC) Control(i interface{}) error {
select {
case s.control <- i:
return nil
case <-s.done:
}
return dNCErrClosed
}
// Locks writing. All write requests will be blocked until write is unlocked with (*dNC).WriteUnlock, or server closes.
func (s *dNC) WriteLock() error {
return s.Control(dNCCWriteLock{})
}
// Unlocks writing. All blocked write requests until now will be accepted.
func (s *dNC) WriteUnlock() error {
return s.Control(dNCCWriteUnlock{})
}
// Unlocks writing and makes (*dNC).Write to result (0, dNCErrWrite).
func (s *dNC) WriteError() error {
if err := s.WriteUnlock(); err != nil {
return err
}
return s.Control(dNCCWriteError{})
}
// Unlocks writing and makes (*dNC).Write([...]) not result in error. See (*dNC).Write for details.
func (s *dNC) WriteSuccess() error {
if err := s.WriteUnlock(); err != nil {
return err
}
return s.Control(dNCCWriteSuccess{})
}
// Locks reading. All read requests will be blocked until read is unlocked with (*dNC).ReadUnlock, or server closes.
// (*dNC).Read([...]) wil block even after successful write.
func (s *dNC) ReadLock() error {
return s.Control(dNCCReadLock{})
}
// Unlocks reading. If the internall buffer is not empty, next read will not block.
func (s *dNC) ReadUnlock() error {
return s.Control(dNCCReadUnlock{})
}
// Unlocks read and makes every blocked and following (*dNC).Read([...]) imidiatly result in error. See (*dNC).Read for details.
func (s *dNC) ReadError() error {
if err := s.ReadUnlock(); err != nil {
return err
}
return s.Control(dNCCReadError{})
}
// Unlocks read and makes every blocked and following (*dNC).Read([...]) requests be handled, if according to internal buffer. See (*dNC).Read for details.
func (s *dNC) ReadSuccess() error {
if err := s.ReadUnlock(); err != nil {
return err
}
return s.Control(dNCCReadSuccess{})
}
// dummy X server replier for dummy net.Conn
type dXSEvent struct{}
func (_ dXSEvent) Bytes() []byte { return nil }
func (_ dXSEvent) String() string { return "dummy X server event" }
type dXSError struct {
seqId uint16
}
func (e dXSError) SequenceId() uint16 { return e.seqId }
func (_ dXSError) BadId() uint32 { return 0 }
func (_ dXSError) Error() string { return "dummy X server error reply" }
func newDummyXServerReplier() func([]byte) []byte {
// register xgb error & event replies
NewErrorFuncs[255] = func(buf []byte) Error {
return dXSError{Get16(buf[2:])}
}
NewEventFuncs[128&127] = func(buf []byte) Event {
return dXSEvent{}
}
// sequence number generator
seqId := uint16(1)
incrementSequenceId := func() {
// this has to be the same algorithm as in (*Conn).generateSeqIds
if seqId == uint16((1<<16)-1) {
seqId = 0
} else {
seqId++
}
}
return func(request []byte) []byte {
res := make([]byte, 32)
switch string(request) {
case "event":
res[0] = 128
return res
case "error":
res[0] = 0 // error
res[1] = 255 // error function
default:
res[0] = 1 // reply
}
Put16(res[2:], seqId) // sequence number
incrementSequenceId()
if string(request) == "noreply" {
return nil
}
return res
}
}

View File

@ -0,0 +1,350 @@
package xgb
import (
"bytes"
"errors"
"fmt"
"io"
"reflect"
"sync"
"testing"
"time"
)
func TestLeaks(t *testing.T) {
lm := leaksMonitor("lm")
if lgrs := lm.leakingGoroutines(); len(lgrs) != 0 {
t.Errorf("leakingGoroutines returned %d leaking goroutines, want 0", len(lgrs))
}
done := make(chan struct{})
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
<-done
wg.Done()
}()
if lgrs := lm.leakingGoroutines(); len(lgrs) != 1 {
t.Errorf("leakingGoroutines returned %d leaking goroutines, want 1", len(lgrs))
}
wg.Add(1)
go func() {
<-done
wg.Done()
}()
if lgrs := lm.leakingGoroutines(); len(lgrs) != 2 {
t.Errorf("leakingGoroutines returned %d leaking goroutines, want 2", len(lgrs))
}
close(done)
wg.Wait()
if lgrs := lm.leakingGoroutines(); len(lgrs) != 0 {
t.Errorf("leakingGoroutines returned %d leaking goroutines, want 0", len(lgrs))
}
lm.checkTesting(t)
//TODO multiple leak monitors with report ignore tests
}
func TestDummyNetConn(t *testing.T) {
ioStatesPairGenerator := func(writeStates, readStates []string) []func() (*dNC, error) {
writeSetters := map[string]func(*dNC) error{
"lock": (*dNC).WriteLock,
"error": (*dNC).WriteError,
"success": (*dNC).WriteSuccess,
}
readSetters := map[string]func(*dNC) error{
"lock": (*dNC).ReadLock,
"error": (*dNC).ReadError,
"success": (*dNC).ReadSuccess,
}
res := []func() (*dNC, error){}
for _, writeState := range writeStates {
writeState, writeSetter := writeState, writeSetters[writeState]
if writeSetter == nil {
panic("unknown write state: " + writeState)
continue
}
for _, readState := range readStates {
readState, readSetter := readState, readSetters[readState]
if readSetter == nil {
panic("unknown read state: " + readState)
continue
}
res = append(res, func() (*dNC, error) {
// loopback server
s := newDummyNetConn("w:"+writeState+";r:"+readState, func(b []byte) []byte { return b })
if err := readSetter(s); err != nil {
s.Close()
return nil, errors.New("set read " + readState + " error: " + err.Error())
}
if err := writeSetter(s); err != nil {
s.Close()
return nil, errors.New("set write " + writeState + " error: " + err.Error())
}
return s, nil
})
}
}
return res
}
timeout := 10 * time.Millisecond
wantResponse := func(action func(*dNC) error, want, block error) func(*dNC) error {
return func(s *dNC) error {
actionResult := make(chan error)
timedOut := make(chan struct{})
go func() {
err := action(s)
select {
case <-timedOut:
if err != block {
t.Errorf("after unblocking, action result=%v, want %v", err, block)
}
case actionResult <- err:
}
}()
select {
case err := <-actionResult:
if err != want {
return errors.New(fmt.Sprintf("action result=%v, want %v", err, want))
}
case <-time.After(timeout):
close(timedOut)
return errors.New(fmt.Sprintf("action did not respond for %v, result want %v", timeout, want))
}
return nil
}
}
wantBlock := func(action func(*dNC) error, unblock error) func(*dNC) error {
return func(s *dNC) error {
actionResult := make(chan error)
timedOut := make(chan struct{})
go func() {
err := action(s)
select {
case <-timedOut:
if err != unblock {
t.Errorf("after unblocking, action result=%v, want %v", err, unblock)
}
case actionResult <- err:
}
}()
select {
case err := <-actionResult:
return errors.New(fmt.Sprintf("action result=%v, want to be blocked", err))
case <-time.After(timeout):
close(timedOut)
}
return nil
}
}
write := func(b string) func(*dNC) error {
return func(s *dNC) error {
n, err := s.Write([]byte(b))
if err == nil && n != len(b) {
return errors.New("Write returned nil error, but not everything was written")
}
return err
}
}
read := func(b string) func(*dNC) error {
return func(s *dNC) error {
r := make([]byte, len(b))
n, err := s.Read(r)
if err == nil {
if n != len(b) {
return errors.New("Read returned nil error, but not everything was read")
}
if !reflect.DeepEqual(r, []byte(b)) {
return errors.New("Read=\"" + string(r) + "\", want \"" + string(b) + "\"")
}
}
return err
}
}
testCases := []struct {
description string
servers []func() (*dNC, error)
actions []func(*dNC) error // actions per server
}{
{"close,close",
ioStatesPairGenerator(
[]string{"lock", "error", "success"},
[]string{"lock", "error", "success"},
),
[]func(*dNC) error{
wantResponse((*dNC).Close, nil, dNCErrClosed),
wantResponse((*dNC).Close, dNCErrClosed, dNCErrClosed),
},
},
{"write,close,write",
ioStatesPairGenerator(
[]string{"lock"},
[]string{"lock", "error", "success"},
),
[]func(*dNC) error{
wantBlock(write(""), dNCErrClosed),
wantResponse((*dNC).Close, nil, dNCErrClosed),
wantResponse(write(""), dNCErrClosed, dNCErrClosed),
},
},
{"write,close,write",
ioStatesPairGenerator(
[]string{"error"},
[]string{"lock", "error", "success"},
),
[]func(*dNC) error{
wantResponse(write(""), dNCErrWrite, dNCErrClosed),
wantResponse((*dNC).Close, nil, dNCErrClosed),
wantResponse(write(""), dNCErrClosed, dNCErrClosed),
},
},
{"write,close,write",
ioStatesPairGenerator(
[]string{"success"},
[]string{"lock", "error", "success"},
),
[]func(*dNC) error{
wantResponse(write(""), nil, dNCErrClosed),
wantResponse((*dNC).Close, nil, dNCErrClosed),
wantResponse(write(""), dNCErrClosed, dNCErrClosed),
},
},
{"read,close,read",
ioStatesPairGenerator(
[]string{"lock", "error", "success"},
[]string{"lock", "error", "success"},
),
[]func(*dNC) error{
wantBlock(read(""), io.EOF),
wantResponse((*dNC).Close, nil, dNCErrClosed),
wantResponse(read(""), io.EOF, io.EOF),
},
},
{"write,read",
ioStatesPairGenerator(
[]string{"lock"},
[]string{"lock", "error", "success"},
),
[]func(*dNC) error{
wantBlock(write("1"), dNCErrClosed),
wantBlock(read("1"), io.EOF),
},
},
{"write,read",
ioStatesPairGenerator(
[]string{"error"},
[]string{"lock", "error", "success"},
),
[]func(*dNC) error{
wantResponse(write("1"), dNCErrWrite, dNCErrClosed),
wantBlock(read("1"), io.EOF),
},
},
{"write,read",
ioStatesPairGenerator(
[]string{"success"},
[]string{"lock"},
),
[]func(*dNC) error{
wantResponse(write("1"), nil, dNCErrClosed),
wantBlock(read("1"), io.EOF),
},
},
{"write,read",
ioStatesPairGenerator(
[]string{"success"},
[]string{"error"},
),
[]func(*dNC) error{
wantResponse(write("1"), nil, dNCErrClosed),
wantResponse(read("1"), dNCErrRead, io.EOF),
},
},
{"write,read",
ioStatesPairGenerator(
[]string{"success"},
[]string{"success"},
),
[]func(*dNC) error{
wantResponse(write("1"), nil, dNCErrClosed),
wantResponse(read("1"), nil, io.EOF),
},
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
defer leaksMonitor(tc.description).checkTesting(t)
for _, server := range tc.servers {
s, err := server()
if err != nil {
t.Error(err)
continue
}
if s == nil {
t.Error("nil server in testcase")
continue
}
t.Run(s.LocalAddr().String(), func(t *testing.T) {
defer leaksMonitor(s.LocalAddr().String()).checkTesting(t)
for _, action := range tc.actions {
if err := action(s); err != nil {
t.Error(err)
break
}
}
s.Close()
})
}
})
}
}
func TestDummyXServerReplier(t *testing.T) {
testCases := [][][2][]byte{
{
[2][]byte{[]byte("reply"), []byte{1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
[2][]byte{[]byte("eply"), []byte{1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
[2][]byte{[]byte("ply"), []byte{1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
[2][]byte{[]byte("event"), []byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
[2][]byte{[]byte("ly"), []byte{1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
[2][]byte{[]byte("y"), []byte{1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
[2][]byte{[]byte(""), []byte{1, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
[2][]byte{[]byte("event"), []byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
[2][]byte{[]byte("reply"), []byte{1, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
[2][]byte{[]byte("error"), []byte{0, 255, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
[2][]byte{[]byte("ply"), []byte{1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
[2][]byte{[]byte("event"), []byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
[2][]byte{[]byte("ly"), []byte{1, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
[2][]byte{[]byte("noreply"), nil},
[2][]byte{[]byte("error"), []byte{0, 255, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
[2][]byte{[]byte("noreply"), nil},
[2][]byte{[]byte(""), []byte{1, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
},
}
for tci, tc := range testCases {
replier := newDummyXServerReplier()
for ai, ioPair := range tc {
in, want := ioPair[0], ioPair[1]
if out := replier(in); !bytes.Equal(out, want) {
t.Errorf("testCase %d, action %d, replier(%s) = %v, want %v", tci, ai, string(in), out, want)
break
}
}
}
}

361
vend/xgb/xcmisc/xcmisc.go Normal file
View File

@ -0,0 +1,361 @@
// Package xcmisc is the X client API for the XC-MISC extension.
package xcmisc
// This file is automatically generated from xc_misc.xml. Edit at your peril!
import (
"github.com/jezek/xgb"
"github.com/jezek/xgb/xproto"
)
// Init must be called before using the XC-MISC extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 7, "XC-MISC").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named XC-MISC could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["XC-MISC"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["XC-MISC"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["XC-MISC"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["XC-MISC"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["XC-MISC"] = make(map[int]xgb.NewErrorFun)
}
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// GetVersionCookie is a cookie used only for GetVersion requests.
type GetVersionCookie struct {
*xgb.Cookie
}
// GetVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetVersionCookie.Reply()
func GetVersion(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) GetVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XC-MISC"]; !ok {
panic("Cannot issue request 'GetVersion' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return GetVersionCookie{cookie}
}
// GetVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetVersionUnchecked(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) GetVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XC-MISC"]; !ok {
panic("Cannot issue request 'GetVersion' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return GetVersionCookie{cookie}
}
// GetVersionReply represents the data returned from a GetVersion request.
type GetVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
ServerMajorVersion uint16
ServerMinorVersion uint16
}
// Reply blocks and returns the reply data for a GetVersion request.
func (cook GetVersionCookie) Reply() (*GetVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getVersionReply(buf), nil
}
// getVersionReply reads a byte slice into a GetVersionReply value.
func getVersionReply(buf []byte) *GetVersionReply {
v := new(GetVersionReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.ServerMajorVersion = xgb.Get16(buf[b:])
b += 2
v.ServerMinorVersion = xgb.Get16(buf[b:])
b += 2
return v
}
// Write request to wire for GetVersion
// getVersionRequest writes a GetVersion request to a byte slice.
func getVersionRequest(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XC-MISC"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put16(buf[b:], ClientMajorVersion)
b += 2
xgb.Put16(buf[b:], ClientMinorVersion)
b += 2
return buf
}
// GetXIDListCookie is a cookie used only for GetXIDList requests.
type GetXIDListCookie struct {
*xgb.Cookie
}
// GetXIDList sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetXIDListCookie.Reply()
func GetXIDList(c *xgb.Conn, Count uint32) GetXIDListCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XC-MISC"]; !ok {
panic("Cannot issue request 'GetXIDList' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getXIDListRequest(c, Count), cookie)
return GetXIDListCookie{cookie}
}
// GetXIDListUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetXIDListUnchecked(c *xgb.Conn, Count uint32) GetXIDListCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XC-MISC"]; !ok {
panic("Cannot issue request 'GetXIDList' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getXIDListRequest(c, Count), cookie)
return GetXIDListCookie{cookie}
}
// GetXIDListReply represents the data returned from a GetXIDList request.
type GetXIDListReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
IdsLen uint32
// padding: 20 bytes
Ids []uint32 // size: xgb.Pad((int(IdsLen) * 4))
}
// Reply blocks and returns the reply data for a GetXIDList request.
func (cook GetXIDListCookie) Reply() (*GetXIDListReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getXIDListReply(buf), nil
}
// getXIDListReply reads a byte slice into a GetXIDListReply value.
func getXIDListReply(buf []byte) *GetXIDListReply {
v := new(GetXIDListReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.IdsLen = xgb.Get32(buf[b:])
b += 4
b += 20 // padding
v.Ids = make([]uint32, v.IdsLen)
for i := 0; i < int(v.IdsLen); i++ {
v.Ids[i] = xgb.Get32(buf[b:])
b += 4
}
return v
}
// Write request to wire for GetXIDList
// getXIDListRequest writes a GetXIDList request to a byte slice.
func getXIDListRequest(c *xgb.Conn, Count uint32) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XC-MISC"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 2 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], Count)
b += 4
return buf
}
// GetXIDRangeCookie is a cookie used only for GetXIDRange requests.
type GetXIDRangeCookie struct {
*xgb.Cookie
}
// GetXIDRange sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetXIDRangeCookie.Reply()
func GetXIDRange(c *xgb.Conn) GetXIDRangeCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XC-MISC"]; !ok {
panic("Cannot issue request 'GetXIDRange' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getXIDRangeRequest(c), cookie)
return GetXIDRangeCookie{cookie}
}
// GetXIDRangeUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetXIDRangeUnchecked(c *xgb.Conn) GetXIDRangeCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XC-MISC"]; !ok {
panic("Cannot issue request 'GetXIDRange' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getXIDRangeRequest(c), cookie)
return GetXIDRangeCookie{cookie}
}
// GetXIDRangeReply represents the data returned from a GetXIDRange request.
type GetXIDRangeReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
StartId uint32
Count uint32
}
// Reply blocks and returns the reply data for a GetXIDRange request.
func (cook GetXIDRangeCookie) Reply() (*GetXIDRangeReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getXIDRangeReply(buf), nil
}
// getXIDRangeReply reads a byte slice into a GetXIDRangeReply value.
func getXIDRangeReply(buf []byte) *GetXIDRangeReply {
v := new(GetXIDRangeReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.StartId = xgb.Get32(buf[b:])
b += 4
v.Count = xgb.Get32(buf[b:])
b += 4
return v
}
// Write request to wire for GetXIDRange
// getXIDRangeRequest writes a GetXIDRange request to a byte slice.
func getXIDRangeRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XC-MISC"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 1 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}

595
vend/xgb/xevie/xevie.go Normal file
View File

@ -0,0 +1,595 @@
// Package xevie is the X client API for the XEVIE extension.
package xevie
// This file is automatically generated from xevie.xml. Edit at your peril!
import (
"github.com/jezek/xgb"
"github.com/jezek/xgb/xproto"
)
// Init must be called before using the XEVIE extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 5, "XEVIE").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named XEVIE could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["XEVIE"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["XEVIE"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["XEVIE"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["XEVIE"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["XEVIE"] = make(map[int]xgb.NewErrorFun)
}
const (
DatatypeUnmodified = 0
DatatypeModified = 1
)
type Event struct {
// padding: 32 bytes
}
// EventRead reads a byte slice into a Event value.
func EventRead(buf []byte, v *Event) int {
b := 0
b += 32 // padding
return b
}
// EventReadList reads a byte slice into a list of Event values.
func EventReadList(buf []byte, dest []Event) int {
b := 0
for i := 0; i < len(dest); i++ {
dest[i] = Event{}
b += EventRead(buf[b:], &dest[i])
}
return xgb.Pad(b)
}
// Bytes writes a Event value to a byte slice.
func (v Event) Bytes() []byte {
buf := make([]byte, 32)
b := 0
b += 32 // padding
return buf[:b]
}
// EventListBytes writes a list of Event values to a byte slice.
func EventListBytes(buf []byte, list []Event) int {
b := 0
var structBytes []byte
for _, item := range list {
structBytes = item.Bytes()
copy(buf[b:], structBytes)
b += len(structBytes)
}
return xgb.Pad(b)
}
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// EndCookie is a cookie used only for End requests.
type EndCookie struct {
*xgb.Cookie
}
// End sends a checked request.
// If an error occurs, it will be returned with the reply by calling EndCookie.Reply()
func End(c *xgb.Conn, Cmap uint32) EndCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'End' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(endRequest(c, Cmap), cookie)
return EndCookie{cookie}
}
// EndUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func EndUnchecked(c *xgb.Conn, Cmap uint32) EndCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'End' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(endRequest(c, Cmap), cookie)
return EndCookie{cookie}
}
// EndReply represents the data returned from a End request.
type EndReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
// padding: 24 bytes
}
// Reply blocks and returns the reply data for a End request.
func (cook EndCookie) Reply() (*EndReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return endReply(buf), nil
}
// endReply reads a byte slice into a EndReply value.
func endReply(buf []byte) *EndReply {
v := new(EndReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
b += 24 // padding
return v
}
// Write request to wire for End
// endRequest writes a End request to a byte slice.
func endRequest(c *xgb.Conn, Cmap uint32) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XEVIE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 2 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], Cmap)
b += 4
return buf
}
// QueryVersionCookie is a cookie used only for QueryVersion requests.
type QueryVersionCookie struct {
*xgb.Cookie
}
// QueryVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryVersionCookie.Reply()
func QueryVersion(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryVersionUnchecked(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionReply represents the data returned from a QueryVersion request.
type QueryVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
ServerMajorVersion uint16
ServerMinorVersion uint16
// padding: 20 bytes
}
// Reply blocks and returns the reply data for a QueryVersion request.
func (cook QueryVersionCookie) Reply() (*QueryVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryVersionReply(buf), nil
}
// queryVersionReply reads a byte slice into a QueryVersionReply value.
func queryVersionReply(buf []byte) *QueryVersionReply {
v := new(QueryVersionReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.ServerMajorVersion = xgb.Get16(buf[b:])
b += 2
v.ServerMinorVersion = xgb.Get16(buf[b:])
b += 2
b += 20 // padding
return v
}
// Write request to wire for QueryVersion
// queryVersionRequest writes a QueryVersion request to a byte slice.
func queryVersionRequest(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XEVIE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put16(buf[b:], ClientMajorVersion)
b += 2
xgb.Put16(buf[b:], ClientMinorVersion)
b += 2
return buf
}
// SelectInputCookie is a cookie used only for SelectInput requests.
type SelectInputCookie struct {
*xgb.Cookie
}
// SelectInput sends a checked request.
// If an error occurs, it will be returned with the reply by calling SelectInputCookie.Reply()
func SelectInput(c *xgb.Conn, EventMask uint32) SelectInputCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'SelectInput' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(selectInputRequest(c, EventMask), cookie)
return SelectInputCookie{cookie}
}
// SelectInputUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func SelectInputUnchecked(c *xgb.Conn, EventMask uint32) SelectInputCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'SelectInput' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(selectInputRequest(c, EventMask), cookie)
return SelectInputCookie{cookie}
}
// SelectInputReply represents the data returned from a SelectInput request.
type SelectInputReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
// padding: 24 bytes
}
// Reply blocks and returns the reply data for a SelectInput request.
func (cook SelectInputCookie) Reply() (*SelectInputReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return selectInputReply(buf), nil
}
// selectInputReply reads a byte slice into a SelectInputReply value.
func selectInputReply(buf []byte) *SelectInputReply {
v := new(SelectInputReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
b += 24 // padding
return v
}
// Write request to wire for SelectInput
// selectInputRequest writes a SelectInput request to a byte slice.
func selectInputRequest(c *xgb.Conn, EventMask uint32) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XEVIE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 4 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], EventMask)
b += 4
return buf
}
// SendCookie is a cookie used only for Send requests.
type SendCookie struct {
*xgb.Cookie
}
// Send sends a checked request.
// If an error occurs, it will be returned with the reply by calling SendCookie.Reply()
func Send(c *xgb.Conn, Event Event, DataType uint32) SendCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'Send' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(sendRequest(c, Event, DataType), cookie)
return SendCookie{cookie}
}
// SendUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func SendUnchecked(c *xgb.Conn, Event Event, DataType uint32) SendCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'Send' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(sendRequest(c, Event, DataType), cookie)
return SendCookie{cookie}
}
// SendReply represents the data returned from a Send request.
type SendReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
// padding: 24 bytes
}
// Reply blocks and returns the reply data for a Send request.
func (cook SendCookie) Reply() (*SendReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return sendReply(buf), nil
}
// sendReply reads a byte slice into a SendReply value.
func sendReply(buf []byte) *SendReply {
v := new(SendReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
b += 24 // padding
return v
}
// Write request to wire for Send
// sendRequest writes a Send request to a byte slice.
func sendRequest(c *xgb.Conn, Event Event, DataType uint32) []byte {
size := 104
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XEVIE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 3 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
{
structBytes := Event.Bytes()
copy(buf[b:], structBytes)
b += len(structBytes)
}
xgb.Put32(buf[b:], DataType)
b += 4
b += 64 // padding
return buf
}
// StartCookie is a cookie used only for Start requests.
type StartCookie struct {
*xgb.Cookie
}
// Start sends a checked request.
// If an error occurs, it will be returned with the reply by calling StartCookie.Reply()
func Start(c *xgb.Conn, Screen uint32) StartCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'Start' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(startRequest(c, Screen), cookie)
return StartCookie{cookie}
}
// StartUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func StartUnchecked(c *xgb.Conn, Screen uint32) StartCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'Start' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(startRequest(c, Screen), cookie)
return StartCookie{cookie}
}
// StartReply represents the data returned from a Start request.
type StartReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
// padding: 24 bytes
}
// Reply blocks and returns the reply data for a Start request.
func (cook StartCookie) Reply() (*StartReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return startReply(buf), nil
}
// startReply reads a byte slice into a StartReply value.
func startReply(buf []byte) *StartReply {
v := new(StartReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
b += 24 // padding
return v
}
// Write request to wire for Start
// startRequest writes a Start request to a byte slice.
func startRequest(c *xgb.Conn, Screen uint32) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XEVIE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 1 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], Screen)
b += 4
return buf
}

1301
vend/xgb/xf86dri/xf86dri.go Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2995
vend/xgb/xfixes/xfixes.go Normal file

File diff suppressed because it is too large Load Diff

608
vend/xgb/xgb.go Normal file
View File

@ -0,0 +1,608 @@
package xgb
import (
"errors"
"io"
"log"
"net"
"os"
"sync"
)
var (
// Where to log error-messages. Defaults to stderr.
// To disable logging, just set this to log.New(ioutil.Discard, "", 0)
Logger = log.New(os.Stderr, "XGB: ", log.Lshortfile)
)
const (
// cookieBuffer represents the queue size of cookies existing at any
// point in time. The size of the buffer is really only important when
// there are many requests without replies made in sequence. Once the
// buffer fills, a round trip request is made to clear the buffer.
cookieBuffer = 1000
// xidBuffer represents the queue size of the xid channel.
// I don't think this value matters much, since xid generation is not
// that expensive.
xidBuffer = 5
// seqBuffer represents the queue size of the sequence number channel.
// I don't think this value matters much, since sequence number generation
// is not that expensive.
seqBuffer = 5
// reqBuffer represents the queue size of the number of requests that
// can be made until new ones block. This value seems OK.
reqBuffer = 100
// eventBuffer represents the queue size of the number of events or errors
// that can be loaded off the wire and not grabbed with WaitForEvent
// until reading an event blocks. This value should be big enough to handle
// bursts of events.
eventBuffer = 5000
)
// A Conn represents a connection to an X server.
type Conn struct {
host string
conn net.Conn
display string
DisplayNumber int
DefaultScreen int
SetupBytes []byte
setupResourceIdBase uint32
setupResourceIdMask uint32
eventChan chan eventOrError
cookieChan chan *Cookie
xidChan chan xid
seqChan chan uint16
reqChan chan *request
doneSend chan struct{}
doneRead chan struct{}
// ExtLock is a lock used whenever new extensions are initialized.
// It should not be used. It is exported for use in the extension
// sub-packages.
ExtLock sync.RWMutex
// Extensions is a map from extension name to major opcode. It should
// not be used. It is exported for use in the extension sub-packages.
Extensions map[string]byte
}
// NewConn creates a new connection instance. It initializes locks, data
// structures, and performs the initial handshake. (The code for the handshake
// has been relegated to conn.go.)
// It is up to user to close connection with Close() method to finish all unfinished requests and clean up spawned goroutines.
// If the connection unexpectedly closes itself and WaitForEvent() returns "nil, nil", everything is cleaned by that moment, but nothing bad happens if you call Close() after.
func NewConn() (*Conn, error) {
return NewConnDisplay("")
}
// NewConnDisplay is just like NewConn (see closing instructions), but allows a specific DISPLAY
// string to be used.
// If 'display' is empty it will be taken from os.Getenv("DISPLAY").
//
// Examples:
// NewConn(":1") -> net.Dial("unix", "", "/tmp/.X11-unix/X1")
// NewConn("/tmp/launch-12/:0") -> net.Dial("unix", "", "/tmp/launch-12/:0")
// NewConn("hostname:2.1") -> net.Dial("tcp", "", "hostname:6002")
// NewConn("tcp/hostname:1.0") -> net.Dial("tcp", "", "hostname:6001")
func NewConnDisplay(display string) (*Conn, error) {
c := &Conn{}
// First connect. This reads authority, checks DISPLAY environment
// variable, and loads the initial Setup info.
err := c.connect(display)
if err != nil {
return nil, err
}
return postNewConn(c)
}
// NewConnNet is just like NewConn (see closing instructions), but allows a specific net.Conn
// to be used.
func NewConnNet(netConn net.Conn) (*Conn, error) {
c := &Conn{}
// First connect. This reads authority, checks DISPLAY environment
// variable, and loads the initial Setup info.
err := c.connectNet(netConn)
if err != nil {
return nil, err
}
return postNewConn(c)
}
func postNewConn(c *Conn) (*Conn, error) {
c.Extensions = make(map[string]byte)
c.cookieChan = make(chan *Cookie, cookieBuffer)
c.xidChan = make(chan xid, xidBuffer)
c.seqChan = make(chan uint16, seqBuffer)
c.reqChan = make(chan *request, reqBuffer)
c.eventChan = make(chan eventOrError, eventBuffer)
c.doneSend = make(chan struct{})
c.doneRead = make(chan struct{})
go c.generateXIds()
go c.generateSeqIds()
go c.sendRequests()
go c.readResponses()
return c, nil
}
// Close gracefully closes the connection to the X server.
// When everything is cleaned up, the WaitForEvent method will return (nil, nil)
func (c *Conn) Close() {
select {
case c.reqChan <- nil:
case <-c.doneSend:
}
}
// Event is an interface that can contain any of the events returned by the
// server. Use a type assertion switch to extract the Event structs.
type Event interface {
Bytes() []byte
String() string
}
// NewEventFun is the type of function use to construct events from raw bytes.
// It should not be used. It is exported for use in the extension sub-packages.
type NewEventFun func(buf []byte) Event
// NewEventFuncs is a map from event numbers to functions that create
// the corresponding event. It should not be used. It is exported for use
// in the extension sub-packages.
var NewEventFuncs = make(map[int]NewEventFun)
// NewExtEventFuncs is a temporary map that stores event constructor functions
// for each extension. When an extension is initialized, each event for that
// extension is added to the 'NewEventFuncs' map. It should not be used. It is
// exported for use in the extension sub-packages.
var NewExtEventFuncs = make(map[string]map[int]NewEventFun)
// Error is an interface that can contain any of the errors returned by
// the server. Use a type assertion switch to extract the Error structs.
type Error interface {
SequenceId() uint16
BadId() uint32
Error() string
}
// NewErrorFun is the type of function use to construct errors from raw bytes.
// It should not be used. It is exported for use in the extension sub-packages.
type NewErrorFun func(buf []byte) Error
// NewErrorFuncs is a map from error numbers to functions that create
// the corresponding error. It should not be used. It is exported for use in
// the extension sub-packages.
var NewErrorFuncs = make(map[int]NewErrorFun)
// NewExtErrorFuncs is a temporary map that stores error constructor functions
// for each extension. When an extension is initialized, each error for that
// extension is added to the 'NewErrorFuncs' map. It should not be used. It is
// exported for use in the extension sub-packages.
var NewExtErrorFuncs = make(map[string]map[int]NewErrorFun)
// eventOrError corresponds to values that can be either an event or an
// error.
type eventOrError interface{}
// NewId generates a new unused ID for use with requests like CreateWindow.
// If no new ids can be generated, the id returned is 0 and error is non-nil.
// This shouldn't be used directly, and is exported for use in the extension
// sub-packages.
// If you need identifiers, use the appropriate constructor.
// e.g., For a window id, use xproto.NewWindowId. For
// a new pixmap id, use xproto.NewPixmapId. And so on.
// Returns (0, io.EOF) when the connection is closed.
func (c *Conn) NewId() (uint32, error) {
xid, ok := <-c.xidChan
if !ok {
return 0, io.EOF
}
if xid.err != nil {
return 0, xid.err
}
return xid.id, nil
}
// xid encapsulates a resource identifier being sent over the Conn.xidChan
// channel. If no new resource id can be generated, id is set to 0 and a
// non-nil error is set in xid.err.
type xid struct {
id uint32
err error
}
// generateXids sends new Ids down the channel for NewId to use.
// generateXids should be run in its own goroutine.
// This needs to be updated to use the XC Misc extension once we run out of
// new ids.
// Thanks to libxcb/src/xcb_xid.c. This code is greatly inspired by it.
func (c *Conn) generateXIds() {
defer close(c.xidChan)
// This requires some explanation. From the horse's mouth:
// "The resource-id-mask contains a single contiguous set of bits (at least
// 18). The client allocates resource IDs for types WINDOW, PIXMAP,
// CURSOR, FONT, GCONTEXT, and COLORMAP by choosing a value with only some
// subset of these bits set and ORing it with resource-id-base. Only values
// constructed in this way can be used to name newly created resources over
// this connection."
// So for example (using 8 bit integers), the mask might look like:
// 00111000
// So that valid values would be 00101000, 00110000, 00001000, and so on.
// Thus, the idea is to increment it by the place of the last least
// significant '1'. In this case, that value would be 00001000. To get
// that value, we can AND the original mask with its two's complement:
// 00111000 & 11001000 = 00001000.
// And we use that value to increment the last resource id to get a new one.
// (And then, of course, we OR it with resource-id-base.)
inc := c.setupResourceIdMask & -c.setupResourceIdMask
max := c.setupResourceIdMask
last := uint32(0)
for {
id := xid{}
if last > 0 && last >= max-inc+1 {
// TODO: Use the XC Misc extension to look for released ids.
id = xid{
id: 0,
err: errors.New("There are no more available resource identifiers."),
}
} else {
last += inc
id = xid{
id: last | c.setupResourceIdBase,
err: nil,
}
}
select {
case c.xidChan <- id:
case <-c.doneSend:
// c.sendRequests is down and since this id is used by requests, we don't need this goroutine running anymore.
return
}
}
}
// newSeqId fetches the next sequence id from the Conn.seqChan channel.
func (c *Conn) newSequenceId() uint16 {
return <-c.seqChan
}
// generateSeqIds returns new sequence ids. It is meant to be run in its
// own goroutine.
// A sequence id is generated for *every* request. It's the identifier used
// to match up replies with requests.
// Since sequence ids can only be 16 bit integers we start over at zero when it
// comes time to wrap.
// N.B. As long as the cookie buffer is less than 2^16, there are no limitations
// on the number (or kind) of requests made in sequence.
func (c *Conn) generateSeqIds() {
defer close(c.seqChan)
seqid := uint16(1)
for {
select {
case c.seqChan <- seqid:
if seqid == uint16((1<<16)-1) {
seqid = 0
} else {
seqid++
}
case <-c.doneSend:
// c.sendRequests is down and since only that function uses sequence ids (via newSequenceId method), we don't need this goroutine running anymore.
return
}
}
}
// request encapsulates a buffer of raw bytes (containing the request data)
// and a cookie, which when combined represents a single request.
// The cookie is used to match up the reply/error.
type request struct {
buf []byte
cookie *Cookie
// seq is closed when the request (cookie) has been sequenced by the Conn.
seq chan struct{}
}
// NewRequest takes the bytes and a cookie of a particular request, constructs
// a request type, and sends it over the Conn.reqChan channel.
// Note that the sequence number is added to the cookie after it is sent
// over the request channel, but before it is sent to X.
//
// Note that you may safely use NewRequest to send arbitrary byte requests
// to X. The resulting cookie can be used just like any normal cookie and
// abides by the same rules, except that for replies, you'll get back the
// raw byte data. This may be useful for performance critical sections where
// every allocation counts, since all X requests in XGB allocate a new byte
// slice. In contrast, NewRequest allocates one small request struct and
// nothing else. (Except when the cookie buffer is full and has to be flushed.)
//
// If you're using NewRequest manually, you'll need to use NewCookie to create
// a new cookie.
//
// In all likelihood, you should be able to copy and paste with some minor
// edits the generated code for the request you want to issue.
func (c *Conn) NewRequest(buf []byte, cookie *Cookie) {
seq := make(chan struct{})
select {
case c.reqChan <- &request{buf: buf, cookie: cookie, seq: seq}:
// request is in buffer
// wait until request is processed or connection is closed
select {
case <-seq:
// request was successfully sent to X server
case <-c.doneSend:
// c.sendRequests is down, your request was not handled
}
case <-c.doneSend:
// c.sendRequests is down, nobody is listening to your requests
}
}
// sendRequests is run as a single goroutine that takes requests and writes
// the bytes to the wire and adds the cookie to the cookie queue.
// It is meant to be run as its own goroutine.
func (c *Conn) sendRequests() {
defer close(c.cookieChan)
defer c.conn.Close()
defer close(c.doneSend)
for {
select {
case req := <-c.reqChan:
if req == nil {
// a request by c.Close() to gracefully exit
// Flush the response reading goroutine.
if err := c.noop(); err != nil {
c.conn.Close()
<-c.doneRead
}
return
}
// ho there! if the cookie channel is nearly full, force a round
// trip to clear out the cookie buffer.
// Note that we circumvent the request channel, because we're *in*
// the request channel.
if len(c.cookieChan) == cookieBuffer-1 {
if err := c.noop(); err != nil {
// Shut everything down.
c.conn.Close()
<-c.doneRead
return
}
}
req.cookie.Sequence = c.newSequenceId()
c.cookieChan <- req.cookie
if err := c.writeBuffer(req.buf); err != nil {
c.conn.Close()
<-c.doneRead
return
}
close(req.seq)
case <-c.doneRead:
return
}
}
}
// noop circumvents the usual request sending goroutines and forces a round
// trip request manually.
func (c *Conn) noop() error {
cookie := c.NewCookie(true, true)
cookie.Sequence = c.newSequenceId()
c.cookieChan <- cookie
if err := c.writeBuffer(c.getInputFocusRequest()); err != nil {
return err
}
cookie.Reply() // wait for the buffer to clear
return nil
}
// writeBuffer is a convenience function for writing a byte slice to the wire.
func (c *Conn) writeBuffer(buf []byte) error {
if _, err := c.conn.Write(buf); err != nil {
Logger.Printf("A write error is unrecoverable: %s", err)
return err
}
return nil
}
// readResponses is a goroutine that reads events, errors and
// replies off the wire.
// When an event is read, it is always added to the event channel.
// When an error is read, if it corresponds to an existing checked cookie,
// it is sent to that cookie's error channel. Otherwise it is added to the
// event channel.
// When a reply is read, it is added to the corresponding cookie's reply
// channel. (It is an error if no such cookie exists in this case.)
// Finally, cookies that came "before" this reply are always cleaned up.
func (c *Conn) readResponses() {
defer close(c.eventChan)
defer c.conn.Close()
defer close(c.doneRead)
var (
err Error
seq uint16
replyBytes []byte
)
for {
buf := make([]byte, 32)
err, seq = nil, 0
if _, err := io.ReadFull(c.conn, buf); err != nil {
select {
case <-c.doneSend:
// gracefully closing
return
default:
}
Logger.Printf("A read error is unrecoverable: %s", err)
c.eventChan <- err
return
}
switch buf[0] {
case 0: // This is an error
// Use the constructor function for this error (that is auto
// generated) by looking it up by the error number.
newErrFun, ok := NewErrorFuncs[int(buf[1])]
if !ok {
Logger.Printf("BUG: Could not find error constructor function "+
"for error with number %d.", buf[1])
continue
}
err = newErrFun(buf)
seq = err.SequenceId()
// This error is either sent to the event channel or a specific
// cookie's error channel below.
case 1: // This is a reply
seq = Get16(buf[2:])
// check to see if this reply has more bytes to be read
size := Get32(buf[4:])
if size > 0 {
byteCount := 32 + size*4
biggerBuf := make([]byte, byteCount)
copy(biggerBuf[:32], buf)
if _, err := io.ReadFull(c.conn, biggerBuf[32:]); err != nil {
Logger.Printf("A read error is unrecoverable: %s", err)
c.eventChan <- err
return
}
replyBytes = biggerBuf
} else {
replyBytes = buf
}
// This reply is sent to its corresponding cookie below.
default: // This is an event
// Use the constructor function for this event (like for errors,
// and is also auto generated) by looking it up by the event number.
// Note that we AND the event number with 127 so that we ignore
// the most significant bit (which is set when it was sent from
// a SendEvent request).
evNum := int(buf[0] & 127)
newEventFun, ok := NewEventFuncs[evNum]
if !ok {
Logger.Printf("BUG: Could not find event construct function "+
"for event with number %d.", evNum)
continue
}
c.eventChan <- newEventFun(buf)
continue
}
// At this point, we have a sequence number and we're either
// processing an error or a reply, which are both responses to
// requests. So all we have to do is find the cookie corresponding
// to this error/reply, and send the appropriate data to it.
// In doing so, we make sure that any cookies that came before it
// are marked as successful if they are void and checked.
// If there's a cookie that requires a reply that is before this
// reply, then something is wrong.
for cookie := range c.cookieChan {
// This is the cookie we're looking for. Process and break.
if cookie.Sequence == seq {
if err != nil { // this is an error to a request
// synchronous processing
if cookie.errorChan != nil {
cookie.errorChan <- err
} else { // asynchronous processing
c.eventChan <- err
// if this is an unchecked reply, ping the cookie too
if cookie.pingChan != nil {
cookie.pingChan <- true
}
}
} else { // this is a reply
if cookie.replyChan == nil {
Logger.Printf("Reply with sequence id %d does not "+
"have a cookie with a valid reply channel.", seq)
continue
} else {
cookie.replyChan <- replyBytes
}
}
break
}
switch {
// Checked requests with replies
case cookie.replyChan != nil && cookie.errorChan != nil:
Logger.Printf("Found cookie with sequence id %d that is "+
"expecting a reply but will never get it. Currently "+
"on sequence number %d", cookie.Sequence, seq)
// Unchecked requests with replies
case cookie.replyChan != nil && cookie.pingChan != nil:
Logger.Printf("Found cookie with sequence id %d that is "+
"expecting a reply (and not an error) but will never "+
"get it. Currently on sequence number %d",
cookie.Sequence, seq)
// Checked requests without replies
case cookie.pingChan != nil && cookie.errorChan != nil:
cookie.pingChan <- true
// Unchecked requests without replies don't have any channels,
// so we can't do anything with them except let them pass by.
}
}
}
}
// processEventOrError takes an eventOrError, type switches on it,
// and returns it in Go idiomatic style.
func processEventOrError(everr eventOrError) (Event, Error) {
switch ee := everr.(type) {
case Event:
return ee, nil
case Error:
return nil, ee
case error:
// c.conn read error
case nil:
// c.eventChan is closed
default:
Logger.Printf("Invalid event/error type: %T", everr)
}
return nil, nil
}
// WaitForEvent returns the next event from the server.
// It will block until an event is available.
// WaitForEvent returns either an Event or an Error. (Returning both
// is a bug.) Note than an Error here is an X error and not an XGB error. That
// is, X errors are sometimes completely expected (and you may want to ignore
// them in some cases).
//
// If both the event and error are nil, then the connection has been closed.
func (c *Conn) WaitForEvent() (Event, Error) {
return processEventOrError(<-c.eventChan)
}
// PollForEvent returns the next event from the server if one is available in
// the internal queue without blocking. Note that unlike WaitForEvent, both
// Event and Error could be nil. Indeed, they are both nil when the event queue
// is empty.
func (c *Conn) PollForEvent() (Event, Error) {
select {
case everr := <-c.eventChan:
return processEventOrError(everr)
default:
return nil, nil
}
}

225
vend/xgb/xgb_test.go Normal file
View File

@ -0,0 +1,225 @@
package xgb
import (
"errors"
"fmt"
"testing"
"time"
)
func TestConnOnNonBlockingDummyXServer(t *testing.T) {
timeout := 10 * time.Millisecond
checkedReply := func(wantError bool) func(*Conn) error {
request := "reply"
if wantError {
request = "error"
}
return func(c *Conn) error {
cookie := c.NewCookie(true, true)
c.NewRequest([]byte(request), cookie)
_, err := cookie.Reply()
if wantError && err == nil {
return errors.New(fmt.Sprintf("checked request \"%v\" with reply resulted in nil error, want some error", request))
}
if !wantError && err != nil {
return errors.New(fmt.Sprintf("checked request \"%v\" with reply resulted in error %v, want nil error", request, err))
}
return nil
}
}
checkedNoreply := func(wantError bool) func(*Conn) error {
request := "noreply"
if wantError {
request = "error"
}
return func(c *Conn) error {
cookie := c.NewCookie(true, false)
c.NewRequest([]byte(request), cookie)
err := cookie.Check()
if wantError && err == nil {
return errors.New(fmt.Sprintf("checked request \"%v\" with no reply resulted in nil error, want some error", request))
}
if !wantError && err != nil {
return errors.New(fmt.Sprintf("checked request \"%v\" with no reply resulted in error %v, want nil error", request, err))
}
return nil
}
}
uncheckedReply := func(wantError bool) func(*Conn) error {
request := "reply"
if wantError {
request = "error"
}
return func(c *Conn) error {
cookie := c.NewCookie(false, true)
c.NewRequest([]byte(request), cookie)
_, err := cookie.Reply()
if err != nil {
return errors.New(fmt.Sprintf("unchecked request \"%v\" with reply resulted in %v, want nil", request, err))
}
return nil
}
}
uncheckedNoreply := func(wantError bool) func(*Conn) error {
request := "noreply"
if wantError {
request = "error"
}
return func(c *Conn) error {
cookie := c.NewCookie(false, false)
c.NewRequest([]byte(request), cookie)
return nil
}
}
event := func() func(*Conn) error {
return func(c *Conn) error {
_, err := c.conn.Write([]byte("event"))
if err != nil {
return errors.New(fmt.Sprintf("asked dummy server to send event, but resulted in error: %v\n", err))
}
return err
}
}
waitEvent := func(wantError bool) func(*Conn) error {
return func(c *Conn) error {
_, err := c.WaitForEvent()
if wantError && err == nil {
return errors.New(fmt.Sprintf("wait for event resulted in nil error, want some error"))
}
if !wantError && err != nil {
return errors.New(fmt.Sprintf("wait for event resulted in error %v, want nil error", err))
}
return nil
}
}
checkClosed := func(c *Conn) error {
select {
case eoe, ok := <-c.eventChan:
if ok {
return fmt.Errorf("(*Conn).eventChan should be closed, but is not and returns %v", eoe)
}
case <-time.After(timeout):
return fmt.Errorf("(*Conn).eventChan should be closed, but is not and was blocking for %v", timeout)
}
return nil
}
testCases := []struct {
description string
actions []func(*Conn) error
}{
{"close",
[]func(*Conn) error{},
},
{"double close",
[]func(*Conn) error{
func(c *Conn) error {
c.Close()
return nil
},
},
},
{"checked requests with reply",
[]func(*Conn) error{
checkedReply(false),
checkedReply(true),
checkedReply(false),
checkedReply(true),
},
},
{"checked requests no reply",
[]func(*Conn) error{
checkedNoreply(false),
checkedNoreply(true),
checkedNoreply(false),
checkedNoreply(true),
},
},
{"unchecked requests with reply",
[]func(*Conn) error{
uncheckedReply(false),
uncheckedReply(true),
waitEvent(true),
uncheckedReply(false),
event(),
waitEvent(false),
},
},
{"unchecked requests no reply",
[]func(*Conn) error{
uncheckedNoreply(false),
uncheckedNoreply(true),
waitEvent(true),
uncheckedNoreply(false),
event(),
waitEvent(false),
},
},
{"close with pending requests",
[]func(*Conn) error{
func(c *Conn) error {
c.conn.(*dNC).ReadLock()
defer c.conn.(*dNC).ReadUnlock()
c.NewRequest([]byte("reply"), c.NewCookie(false, true))
c.Close()
return nil
},
checkClosed,
},
},
{"unexpected conn close",
[]func(*Conn) error{
func(c *Conn) error {
c.conn.Close()
if ev, err := c.WaitForEvent(); ev != nil || err != nil {
return fmt.Errorf("WaitForEvent() = (%v, %v), want (nil, nil)", ev, err)
}
return nil
},
checkClosed,
},
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
sclm := leaksMonitor("after server close, before testcase exit")
defer sclm.checkTesting(t)
s := newDummyNetConn("dummyX", newDummyXServerReplier())
defer s.Close()
c, err := postNewConn(&Conn{conn: s})
if err != nil {
t.Errorf("connect to dummy server error: %v", err)
return
}
defer leaksMonitor("after actions end", sclm).checkTesting(t)
for _, action := range tc.actions {
if err := action(c); err != nil {
t.Error(err)
break
}
}
recovered := false
func() {
defer func() {
if err := recover(); err != nil {
t.Errorf("(*Conn).Close() panic recover: %v", err)
recovered = true
}
}()
c.Close()
}()
if !recovered {
if err := checkClosed(c); err != nil {
t.Error(err)
}
}
})
}
}

13
vend/xgb/xgbgen/COPYING Normal file
View File

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

168
vend/xgb/xgbgen/context.go Normal file
View File

@ -0,0 +1,168 @@
package main
import (
"bytes"
"encoding/xml"
"fmt"
"log"
"sort"
)
// Context represents the protocol we're converting to Go, and a writer
// buffer to write the Go source to.
type Context struct {
protocol *Protocol
out *bytes.Buffer
}
func newContext() *Context {
return &Context{
out: bytes.NewBuffer([]byte{}),
}
}
// Putln calls put and adds a new line to the end of 'format'.
func (c *Context) Putln(format string, v ...interface{}) {
c.Put(format+"\n", v...)
}
// Put is a short alias to write to 'out'.
func (c *Context) Put(format string, v ...interface{}) {
_, err := c.out.WriteString(fmt.Sprintf(format, v...))
if err != nil {
log.Fatalf("There was an error writing to context buffer: %s", err)
}
}
// Morph is the big daddy of them all. It takes in an XML byte slice,
// parse it, transforms the XML types into more usable types,
// and writes Go code to the 'out' buffer.
func (c *Context) Morph(xmlBytes []byte) {
parsedXml := &XML{}
err := xml.Unmarshal(xmlBytes, parsedXml)
if err != nil {
log.Fatal(err)
}
// Parse all imports
parsedXml.Imports.Eval()
// Translate XML types to nice types
c.protocol = parsedXml.Translate(nil)
// For backwards compatibility we patch the type of the send_event field of
// PutImage to be byte
if c.protocol.Name == "shm" {
for _, req := range c.protocol.Requests {
if req.xmlName != "PutImage" {
continue
}
for _, ifield := range req.Fields {
field, ok := ifield.(*SingleField)
if !ok || field.xmlName != "send_event" {
continue
}
field.Type = &Base{ srcName: "byte", xmlName: "CARD8", size: newFixedSize(1, true) }
}
}
}
// Start with Go header.
c.Putln("// Package %s is the X client API for the %s extension.",
c.protocol.PkgName(), c.protocol.ExtXName)
c.Putln("package %s", c.protocol.PkgName())
c.Putln("")
c.Putln("// This file is automatically generated from %s.xml. "+
"Edit at your peril!", c.protocol.Name)
c.Putln("")
// Write imports. We always need to import at least xgb.
// We also need to import xproto if it's an extension.
c.Putln("import (")
c.Putln("\"github.com/jezek/xgb\"")
c.Putln("")
if c.protocol.isExt() {
c.Putln("\"github.com/jezek/xgb/xproto\"")
}
sort.Sort(Protocols(c.protocol.Imports))
for _, imp := range c.protocol.Imports {
// We always import xproto, so skip it if it's explicitly imported
if imp.Name == "xproto" {
continue
}
c.Putln("\"github.com/jezek/xgb/%s\"", imp.Name)
}
c.Putln(")")
c.Putln("")
// If this is an extension, create a function to initialize the extension
// before it can be used.
if c.protocol.isExt() {
xname := c.protocol.ExtXName
c.Putln("// Init must be called before using the %s extension.",
xname)
c.Putln("func Init(c *xgb.Conn) error {")
c.Putln("reply, err := xproto.QueryExtension(c, %d, \"%s\").Reply()",
len(xname), xname)
c.Putln("switch {")
c.Putln("case err != nil:")
c.Putln("return err")
c.Putln("case !reply.Present:")
c.Putln("return xgb.Errorf(\"No extension named %s could be found on "+
"on the server.\")", xname)
c.Putln("}")
c.Putln("")
c.Putln("c.ExtLock.Lock()")
c.Putln("c.Extensions[\"%s\"] = reply.MajorOpcode", xname)
c.Putln("c.ExtLock.Unlock()")
c.Putln("for evNum, fun := range xgb.NewExtEventFuncs[\"%s\"] {",
xname)
c.Putln("xgb.NewEventFuncs[int(reply.FirstEvent) + evNum] = fun")
c.Putln("}")
c.Putln("for errNum, fun := range xgb.NewExtErrorFuncs[\"%s\"] {",
xname)
c.Putln("xgb.NewErrorFuncs[int(reply.FirstError) + errNum] = fun")
c.Putln("}")
c.Putln("return nil")
c.Putln("}")
c.Putln("")
// Make sure newExtEventFuncs["EXT_NAME"] map is initialized.
// Same deal for newExtErrorFuncs["EXT_NAME"]
c.Putln("func init() {")
c.Putln("xgb.NewExtEventFuncs[\"%s\"] = make(map[int]xgb.NewEventFun)",
xname)
c.Putln("xgb.NewExtErrorFuncs[\"%s\"] = make(map[int]xgb.NewErrorFun)",
xname)
c.Putln("}")
c.Putln("")
} else {
// In the xproto package, we must provide a Setup function that uses
// SetupBytes in xgb.Conn to return a SetupInfo structure.
c.Putln("// Setup parses the setup bytes retrieved when")
c.Putln("// connecting into a SetupInfo struct.")
c.Putln("func Setup(c *xgb.Conn) *SetupInfo {")
c.Putln("setup := new(SetupInfo)")
c.Putln("SetupInfoRead(c.SetupBytes, setup)")
c.Putln("return setup")
c.Putln("}")
c.Putln("")
c.Putln("// DefaultScreen gets the default screen info from SetupInfo.")
c.Putln("func (s *SetupInfo) DefaultScreen(c *xgb.Conn) *ScreenInfo {")
c.Putln("return &s.Roots[c.DefaultScreen]")
c.Putln("}")
c.Putln("")
}
// Now write Go source code
sort.Sort(Types(c.protocol.Types))
sort.Sort(Requests(c.protocol.Requests))
for _, typ := range c.protocol.Types {
typ.Define(c)
}
for _, req := range c.protocol.Requests {
req.Define(c)
}
}

74
vend/xgb/xgbgen/doc.go Normal file
View File

@ -0,0 +1,74 @@
/*
xgbgen constructs Go source files from xproto XML description files. xgbgen
accomplishes the same task as the Python code generator for XCB and xpyb.
Usage:
xgbgen [flags] some-protocol.xml
The flags are:
--proto-path path
The path to a directory containing xproto XML description files.
This is only necessary when 'some-protocol.xml' imports other
protocol files.
--gofmt=true
When false, the outputted Go code will not be gofmt'd. And it won't
be very pretty at all. This is typically useful if there are syntax
errors that need to be debugged in code generation. gofmt will hiccup;
this will allow you to see the raw code.
How it works
xgbgen works by parsing the input XML file using Go's encoding/xml package.
The majority of this work is done in xml.go and xml_fields.go, where the
appropriate types are declared.
Due to the nature of the XML in the protocol description files, the types
required to parse the XML are not well suited to reasoning about code
generation. Because of this, all data parsed in the XML types is translated
into more reasonable types. This translation is done in translation.go,
and is mainly grunt work. (The only interesting tidbits are the translation
of XML names to Go source names, and connecting fields with their
appropriate types.)
The organization of these types is greatly
inspired by the description of the XML found here:
http://cgit.freedesktop.org/xcb/proto/tree/doc/xml-xcb.txt
These types come with a lot of supporting methods to make their use in
code generation easier. They can be found in expression.go, field.go,
protocol.go, request_reply.go and type.go. Of particular interest are
expression evaluation and size calculation (in bytes).
These types also come with supporting methods that convert their
representation into Go source code. I've quartered such methods in
go.go, go_error.go, go_event.go, go_list.go, go_request_reply.go,
go_single_field.go, go_struct.go and go_union.go. The idea is to keep
as much of the Go specific code generation in one area as possible. Namely,
while not *all* Go related code is found in the 'go*.go' files, *most*
of it is. (If there's any interest in using xgbgen for other languages,
I'd be happy to try and make xgbgen a little more friendly in this regard.
I did, however, design xgbgen with this in mind, so it shouldn't involve
anything as serious as a re-design.)
Why
I wrote xgbgen because I found the existing code generator that was written in
Python to be unwieldy. In particular, static and strong typing greatly helped
me reason better about the code generation task.
What does not work
The core X protocol should be completely working. As far as I know, most
extensions should work too, although I've only tested (and not much) the
Xinerama and RandR extensions.
XKB does not work. I don't have any real plans of working on this unless there
is demand and I have some test cases to work with. (i.e., even if I could get
something generated for XKB, I don't have the inclination to understand it
enough to verify that it works.) XKB poses several extremely difficult
problems that XCB also has trouble with. More info on that can be found at
http://cgit.freedesktop.org/xcb/libxcb/tree/doc/xkb_issues and
http://cgit.freedesktop.org/xcb/libxcb/tree/doc/xkb_internals.
*/
package main

View File

@ -0,0 +1,436 @@
package main
import (
"fmt"
"log"
)
// Expression represents all the different forms of expressions possible in
// side an XML protocol description file. It's also received a few custom
// addendums to make applying special functions (like padding) easier.
type Expression interface {
// Concrete determines whether this particular expression can be computed
// to some constant value inside xgbgen. (The alternative is that the
// expression can only be computed with values at run time of the
// generated code.)
Concrete() bool
// Eval evaluates a concrete expression. It is an error to call Eval
// on any expression that is not concrete (or contains any sub-expression
// that is not concrete).
Eval() int
// Reduce attempts to evaluate any concrete sub-expressions.
// i.e., (1 + 2 * (5 + 1 + someSizeOfStruct) reduces to
// (3 * (6 + someSizeOfStruct)).
// 'prefix' is used preprended to any field reference name.
Reduce(prefix string) string
// String is an alias for Reduce("")
String() string
// Initialize makes sure all names in this expression and any subexpressions
// have been translated to Go source names.
Initialize(p *Protocol)
// Makes all field references relative to path
Specialize(path string) Expression
}
// Function is a custom expression not found in the XML. It's simply used
// to apply a function named in 'Name' to the Expr expression.
type Function struct {
Name string
Expr Expression
}
func (e *Function) Concrete() bool {
return false
}
func (e *Function) Eval() int {
log.Fatalf("Cannot evaluate a 'Function'. It is not concrete.")
panic("unreachable")
}
func (e *Function) Reduce(prefix string) string {
return fmt.Sprintf("%s(%s)", e.Name, e.Expr.Reduce(prefix))
}
func (e *Function) String() string {
return e.Reduce("")
}
func (e *Function) Initialize(p *Protocol) {
e.Expr.Initialize(p)
}
func (e *Function) Specialize(path string) Expression {
r := *e
r.Expr = r.Expr.Specialize(path)
return &r
}
// BinaryOp is an expression that performs some operation (defined in the XML
// file) with Expr1 and Expr2 as operands.
type BinaryOp struct {
Op string
Expr1 Expression
Expr2 Expression
}
// newBinaryOp constructs a new binary expression when both expr1 and expr2
// are not nil. If one or both are nil, then the non-nil expression is
// returned unchanged or nil is returned.
func newBinaryOp(op string, expr1, expr2 Expression) Expression {
switch {
case expr1 != nil && expr2 != nil:
return &BinaryOp{
Op: op,
Expr1: expr1,
Expr2: expr2,
}
case expr1 != nil && expr2 == nil:
return expr1
case expr1 == nil && expr2 != nil:
return expr2
case expr1 == nil && expr2 == nil:
return nil
}
panic("unreachable")
}
func (e *BinaryOp) Concrete() bool {
return e.Expr1.Concrete() && e.Expr2.Concrete()
}
func (e *BinaryOp) Eval() int {
switch e.Op {
case "+":
return e.Expr1.Eval() + e.Expr2.Eval()
case "-":
return e.Expr1.Eval() - e.Expr2.Eval()
case "*":
return e.Expr1.Eval() * e.Expr2.Eval()
case "/":
return e.Expr1.Eval() / e.Expr2.Eval()
case "&amp;":
return e.Expr1.Eval() & e.Expr2.Eval()
case "&lt;&lt;":
return int(uint(e.Expr1.Eval()) << uint(e.Expr2.Eval()))
}
log.Fatalf("Invalid binary operator '%s' for expression.", e.Op)
panic("unreachable")
}
func (e *BinaryOp) Reduce(prefix string) string {
if e.Concrete() {
return fmt.Sprintf("%d", e.Eval())
}
// An incredibly dirty hack to make sure any time we perform an operation
// on a field, we're dealing with ints...
expr1, expr2 := e.Expr1, e.Expr2
switch expr1.(type) {
case *FieldRef:
expr1 = &Function{
Name: "int",
Expr: expr1,
}
}
switch expr2.(type) {
case *FieldRef:
expr2 = &Function{
Name: "int",
Expr: expr2,
}
}
return fmt.Sprintf("(%s %s %s)",
expr1.Reduce(prefix), e.Op, expr2.Reduce(prefix))
}
func (e *BinaryOp) String() string {
return e.Reduce("")
}
func (e *BinaryOp) Initialize(p *Protocol) {
e.Expr1.Initialize(p)
e.Expr2.Initialize(p)
}
func (e *BinaryOp) Specialize(path string) Expression {
r := *e
r.Expr1 = r.Expr1.Specialize(path)
r.Expr2 = r.Expr2.Specialize(path)
return &r
}
// UnaryOp is the same as BinaryOp, except it's a unary operator with only
// one sub-expression.
type UnaryOp struct {
Op string
Expr Expression
}
func (e *UnaryOp) Concrete() bool {
return e.Expr.Concrete()
}
func (e *UnaryOp) Eval() int {
switch e.Op {
case "~":
return ^e.Expr.Eval()
}
log.Fatalf("Invalid unary operator '%s' for expression.", e.Op)
panic("unreachable")
}
func (e *UnaryOp) Reduce(prefix string) string {
if e.Concrete() {
return fmt.Sprintf("%d", e.Eval())
}
return fmt.Sprintf("(%s (%s))", e.Op, e.Expr.Reduce(prefix))
}
func (e *UnaryOp) String() string {
return e.Reduce("")
}
func (e *UnaryOp) Initialize(p *Protocol) {
e.Expr.Initialize(p)
}
func (e *UnaryOp) Specialize(path string) Expression {
r := *e
r.Expr = r.Expr.Specialize(path)
return &r
}
// Padding represents the application of the 'pad' function to some
// sub-expression.
type Padding struct {
Expr Expression
}
func (e *Padding) Concrete() bool {
return e.Expr.Concrete()
}
func (e *Padding) Eval() int {
return pad(e.Expr.Eval())
}
func (e *Padding) Reduce(prefix string) string {
if e.Concrete() {
return fmt.Sprintf("%d", e.Eval())
}
return fmt.Sprintf("xgb.Pad(%s)", e.Expr.Reduce(prefix))
}
func (e *Padding) String() string {
return e.Reduce("")
}
func (e *Padding) Initialize(p *Protocol) {
e.Expr.Initialize(p)
}
func (e *Padding) Specialize(path string) Expression {
r := *e
r.Expr = r.Expr.Specialize(path)
return &r
}
// PopCount represents the application of the 'PopCount' function to
// some sub-expression.
type PopCount struct {
Expr Expression
}
func (e *PopCount) Concrete() bool {
return e.Expr.Concrete()
}
func (e *PopCount) Eval() int {
return int(popCount(uint(e.Expr.Eval())))
}
func (e *PopCount) Reduce(prefix string) string {
if e.Concrete() {
return fmt.Sprintf("%d", e.Eval())
}
return fmt.Sprintf("xgb.PopCount(%s)", e.Expr.Reduce(prefix))
}
func (e *PopCount) String() string {
return e.Reduce("")
}
func (e *PopCount) Initialize(p *Protocol) {
e.Expr.Initialize(p)
}
func (e *PopCount) Specialize(path string) Expression {
r := *e
r.Expr = r.Expr.Specialize(path)
return &r
}
// Value represents some constant integer.
type Value struct {
v int
}
func (e *Value) Concrete() bool {
return true
}
func (e *Value) Eval() int {
return e.v
}
func (e *Value) Reduce(prefix string) string {
return fmt.Sprintf("%d", e.v)
}
func (e *Value) String() string {
return e.Reduce("")
}
func (e *Value) Initialize(p *Protocol) {}
func (e *Value) Specialize(path string) Expression {
return e
}
// Bit represents some bit whose value is computed by '1 << bit'.
type Bit struct {
b int
}
func (e *Bit) Concrete() bool {
return true
}
func (e *Bit) Eval() int {
return int(1 << uint(e.b))
}
func (e *Bit) Reduce(prefix string) string {
return fmt.Sprintf("%d", e.Eval())
}
func (e *Bit) String() string {
return e.Reduce("")
}
func (e *Bit) Initialize(p *Protocol) {}
func (e *Bit) Specialize(path string) Expression {
return e
}
// FieldRef represents a reference to some variable in the generated code
// with name Name.
type FieldRef struct {
Name string
}
func (e *FieldRef) Concrete() bool {
return false
}
func (e *FieldRef) Eval() int {
log.Fatalf("Cannot evaluate a 'FieldRef'. It is not concrete.")
panic("unreachable")
}
func (e *FieldRef) Reduce(prefix string) string {
val := e.Name
if len(prefix) > 0 {
val = fmt.Sprintf("%s%s", prefix, val)
}
return val
}
func (e *FieldRef) String() string {
return e.Reduce("")
}
func (e *FieldRef) Initialize(p *Protocol) {
e.Name = SrcName(p, e.Name)
}
func (e *FieldRef) Specialize(path string) Expression {
return &FieldRef{Name: path + "." + e.Name}
}
// EnumRef represents a reference to some enumeration field.
// EnumKind is the "group" an EnumItem is the name of the specific enumeration
// value inside that group.
type EnumRef struct {
EnumKind Type
EnumItem string
}
func (e *EnumRef) Concrete() bool {
return false
}
func (e *EnumRef) Eval() int {
log.Fatalf("Cannot evaluate an 'EnumRef'. It is not concrete.")
panic("unreachable")
}
func (e *EnumRef) Reduce(prefix string) string {
return fmt.Sprintf("%s%s", e.EnumKind, e.EnumItem)
}
func (e *EnumRef) String() string {
return e.Reduce("")
}
func (e *EnumRef) Initialize(p *Protocol) {
e.EnumKind = e.EnumKind.(*Translation).RealType(p)
e.EnumItem = SrcName(p, e.EnumItem)
}
func (e *EnumRef) Specialize(path string) Expression {
return e
}
// SumOf represents a summation of the variable in the generated code named by
// Name. It is not currently used. (It's XKB voodoo.)
type SumOf struct {
Name string
}
func (e *SumOf) Concrete() bool {
return false
}
func (e *SumOf) Eval() int {
log.Fatalf("Cannot evaluate a 'SumOf'. It is not concrete.")
panic("unreachable")
}
func (e *SumOf) Reduce(prefix string) string {
if len(prefix) > 0 {
return fmt.Sprintf("sum(%s%s)", prefix, e.Name)
}
return fmt.Sprintf("sum(%s)", e.Name)
}
func (e *SumOf) String() string {
return e.Reduce("")
}
func (e *SumOf) Initialize(p *Protocol) {
e.Name = SrcName(p, e.Name)
}
func (e *SumOf) Specialize(path string) Expression {
return e
}

397
vend/xgb/xgbgen/field.go Normal file
View File

@ -0,0 +1,397 @@
package main
import (
"fmt"
"log"
"strings"
)
// Field corresponds to any field described in an XML protocol description
// file. This includes struct fields, union fields, request fields,
// reply fields and so on.
// To make code generation easier, fields that have types are also stored.
// Note that not all fields support all methods defined in this interface.
// For instance, a padding field does not have a source name.
type Field interface {
// Initialize sets up the source name of this field.
Initialize(p *Protocol)
// SrcName is the Go source name of this field.
SrcName() string
// XmlName is the name of this field from the XML file.
XmlName() string
// SrcType is the Go source type name of this field.
SrcType() string
// Size returns an expression that computes the size (in bytes)
// of this field.
Size() Size
// Define writes the Go code to declare this field (in a struct definition).
Define(c *Context)
// Read writes the Go code to convert a byte slice to a Go value
// of this field.
// 'prefix' is the prefix of the name of the Go value.
Read(c *Context, prefix string)
// Write writes the Go code to convert a Go value to a byte slice of
// this field.
// 'prefix' is the prefix of the name of the Go value.
Write(c *Context, prefix string)
}
func (pad *PadField) Initialize(p *Protocol) {}
// PadField represents any type of padding. It is omitted from
// definitions, but is used in Read/Write to increment the buffer index.
// It is also used in size calculation.
type PadField struct {
Bytes uint
Align uint16
}
func (p *PadField) SrcName() string {
panic("illegal to take source name of a pad field")
}
func (p *PadField) XmlName() string {
panic("illegal to take XML name of a pad field")
}
func (f *PadField) SrcType() string {
panic("it is illegal to call SrcType on a PadField field")
}
func (p *PadField) Size() Size {
if p.Align > 0 {
return newFixedSize(uint(p.Align), false)
} else {
return newFixedSize(p.Bytes, true)
}
}
type RequiredStartAlign struct {
}
func (f *RequiredStartAlign) Initialize(p *Protocol) { }
func (f *RequiredStartAlign) SrcName() string {
panic("illegal to take source name of a required_start_align field")
}
func (f *RequiredStartAlign) XmlName() string {
panic("illegal to take XML name of a required_start_align field")
}
func (f *RequiredStartAlign) SrcType() string {
panic("it is illegal to call SrcType on a required_start_align field")
}
func (f *RequiredStartAlign) Size() Size {
return newFixedSize(0, true)
}
func (f *RequiredStartAlign) Define(c *Context) { }
func (f *RequiredStartAlign) Read(c *Context, prefix string) { }
func (f *RequiredStartAlign) Write(c *Context, prefix string) { }
// SingleField represents most of the fields in an XML protocol description.
// It corresponds to any single value.
type SingleField struct {
srcName string
xmlName string
Type Type
}
func (f *SingleField) Initialize(p *Protocol) {
f.srcName = SrcName(p, f.XmlName())
f.Type = f.Type.(*Translation).RealType(p)
}
func (f *SingleField) SrcName() string {
if f.srcName == "Bytes" {
return "Bytes_"
}
return f.srcName
}
func (f *SingleField) XmlName() string {
return f.xmlName
}
func (f *SingleField) SrcType() string {
return f.Type.SrcName()
}
func (f *SingleField) Size() Size {
return f.Type.Size()
}
// ListField represents a list of values.
type ListField struct {
srcName string
xmlName string
Type Type
LengthExpr Expression
}
func (f *ListField) SrcName() string {
return f.srcName
}
func (f *ListField) XmlName() string {
return f.xmlName
}
func (f *ListField) SrcType() string {
if strings.ToLower(f.Type.XmlName()) == "char" {
return fmt.Sprintf("string")
}
return fmt.Sprintf("[]%s", f.Type.SrcName())
}
// Length computes the *number* of values in a list.
// If this ListField does not have any length expression, we throw our hands
// up and simply compute the 'len' of the field name of this list.
func (f *ListField) Length() Size {
if f.LengthExpr == nil {
return newExpressionSize(&Function{
Name: "len",
Expr: &FieldRef{
Name: f.SrcName(),
},
}, true)
}
return newExpressionSize(f.LengthExpr, true)
}
// Size computes the *size* of a list (in bytes).
// It it typically a simple matter of multiplying the length of the list by
// the size of the type of the list.
// But if it's a list of struct where the struct has a list field, we use a
// special function written in go_struct.go to compute the size (since the
// size in this case can only be computed recursively).
func (f *ListField) Size() Size {
elsz := f.Type.Size()
simpleLen := &Padding{
Expr: newBinaryOp("*", f.Length().Expression, elsz.Expression),
}
switch field := f.Type.(type) {
case *Struct:
if field.HasList() {
sizeFun := &Function{
Name: fmt.Sprintf("%sListSize", f.Type.SrcName()),
Expr: &FieldRef{Name: f.SrcName()},
}
return newExpressionSize(sizeFun, elsz.exact)
} else {
return newExpressionSize(simpleLen, elsz.exact)
}
case *Union:
return newExpressionSize(simpleLen, elsz.exact)
case *Base:
return newExpressionSize(simpleLen, elsz.exact)
case *Resource:
return newExpressionSize(simpleLen, elsz.exact)
case *TypeDef:
return newExpressionSize(simpleLen, elsz.exact)
default:
log.Panicf("Cannot compute list size with type '%T'.", f.Type)
}
panic("unreachable")
}
func (f *ListField) Initialize(p *Protocol) {
f.srcName = SrcName(p, f.XmlName())
f.Type = f.Type.(*Translation).RealType(p)
if f.LengthExpr != nil {
f.LengthExpr.Initialize(p)
}
}
// LocalField is exactly the same as a regular SingleField, except it isn't
// sent over the wire. (i.e., it's probably used to compute an ExprField).
type LocalField struct {
*SingleField
}
// ExprField is a field that is not parameterized, but is computed from values
// of other fields.
type ExprField struct {
srcName string
xmlName string
Type Type
Expr Expression
}
func (f *ExprField) SrcName() string {
return f.srcName
}
func (f *ExprField) XmlName() string {
return f.xmlName
}
func (f *ExprField) SrcType() string {
return f.Type.SrcName()
}
func (f *ExprField) Size() Size {
return f.Type.Size()
}
func (f *ExprField) Initialize(p *Protocol) {
f.srcName = SrcName(p, f.XmlName())
f.Type = f.Type.(*Translation).RealType(p)
f.Expr.Initialize(p)
}
// ValueField represents two fields in one: a mask and a list of 4-byte
// integers. The mask specifies which kinds of values are in the list.
// (i.e., See ConfigureWindow, CreateWindow, ChangeWindowAttributes, etc.)
type ValueField struct {
Parent interface{}
MaskType Type
MaskName string
ListName string
}
func (f *ValueField) SrcName() string {
panic("it is illegal to call SrcName on a ValueField field")
}
func (f *ValueField) XmlName() string {
panic("it is illegal to call XmlName on a ValueField field")
}
func (f *ValueField) SrcType() string {
return f.MaskType.SrcName()
}
// Size computes the size in bytes of the combination of the mask and list
// in this value field.
// The expression to compute this looks complicated, but it's really just
// the number of bits set in the mask multiplied 4 (and padded of course).
func (f *ValueField) Size() Size {
maskSize := f.MaskType.Size()
listSize := newExpressionSize(&Function{
Name: "xgb.Pad",
Expr: &BinaryOp{
Op: "*",
Expr1: &Value{v: 4},
Expr2: &PopCount{
Expr: &Function{
Name: "int",
Expr: &FieldRef{
Name: f.MaskName,
},
},
},
},
}, true)
return maskSize.Add(listSize)
}
func (f *ValueField) ListLength() Size {
return newExpressionSize(&PopCount{
Expr: &Function{
Name: "int",
Expr: &FieldRef{
Name: f.MaskName,
},
},
}, true)
}
func (f *ValueField) Initialize(p *Protocol) {
f.MaskType = f.MaskType.(*Translation).RealType(p)
f.MaskName = SrcName(p, f.MaskName)
f.ListName = SrcName(p, f.ListName)
}
// SwitchField represents a 'switch' element in the XML protocol description
// file.
// Currently we translate this to a slice of uint32 and let the user sort
// through it.
type SwitchField struct {
xmlName string
Name string
MaskName string
Expr Expression
Bitcases []*Bitcase
}
func (f *SwitchField) SrcName() string {
return f.Name
}
func (f *SwitchField) XmlName() string {
return f.xmlName
}
func (f *SwitchField) SrcType() string {
return "[]uint32"
}
func (f *SwitchField) Size() Size {
// TODO: size expression used here is not correct unless every element of
// the switch is 32 bit long. This assumption holds for xproto but may not
// hold for other protocols (xkb?)
listSize := newExpressionSize(&Function{
Name: "xgb.Pad",
Expr: &BinaryOp{
Op: "*",
Expr1: &Value{v: 4},
Expr2: &PopCount{
Expr: &Function{
Name: "int",
Expr: &FieldRef{
Name: f.MaskName,
},
},
},
},
}, true)
return listSize
}
func (f *SwitchField) ListLength() Size {
return newExpressionSize(&PopCount{
Expr: &Function{
Name: "int",
Expr: &FieldRef{
Name: f.MaskName,
},
},
}, true)
}
func (f *SwitchField) Initialize(p *Protocol) {
f.xmlName = f.Name
f.Name = SrcName(p, f.Name)
f.Expr.Initialize(p)
fieldref, ok := f.Expr.(*FieldRef)
if !ok {
panic("switch field's expression not a fieldref")
}
f.MaskName = SrcName(p, fieldref.Name)
for _, bitcase := range f.Bitcases {
bitcase.Expr.Initialize(p)
for _, field := range bitcase.Fields {
field.Initialize(p)
}
}
}
// Bitcase represents a single bitcase inside a switch expression.
// It is not currently used. (i.e., it's XKB voodoo.)
type Bitcase struct {
Fields []Field
Expr Expression
}

220
vend/xgb/xgbgen/go.go Normal file
View File

@ -0,0 +1,220 @@
package main
import (
"fmt"
)
// BaseTypeMap is a map from X base types to Go types.
// X base types should correspond to the smallest set of X types
// that can be used to rewrite ALL X types in terms of Go types.
// That is, if you remove any of the following types, at least one
// XML protocol description will produce an invalid Go program.
// The types on the left *never* show themselves in the source.
var BaseTypeMap = map[string]string{
"CARD8": "byte",
"CARD16": "uint16",
"CARD32": "uint32",
"INT8": "int8",
"INT16": "int16",
"INT32": "int32",
"BYTE": "byte",
"BOOL": "bool",
"float": "float64",
"double": "float64",
"char": "byte",
"void": "byte",
}
// BaseTypeSizes should have precisely the same keys as in BaseTypeMap,
// and the values should correspond to the size of the type in bytes.
var BaseTypeSizes = map[string]uint{
"CARD8": 1,
"CARD16": 2,
"CARD32": 4,
"INT8": 1,
"INT16": 2,
"INT32": 4,
"BYTE": 1,
"BOOL": 1,
"float": 4,
"double": 8,
"char": 1,
"void": 1,
// Id is a special type used to determine the size of all Xid types.
// "Id" is not actually written in the source.
"Id": 4,
}
// TypeMap is a map from types in the XML to type names that is used
// in the functions that follow. Basically, every occurrence of the key
// type is replaced with the value type.
var TypeMap = map[string]string{
"VISUALTYPE": "VisualInfo",
"DEPTH": "DepthInfo",
"SCREEN": "ScreenInfo",
"Setup": "SetupInfo",
}
// NameMap is the same as TypeMap, but for names.
var NameMap = map[string]string{}
// Reading, writing and defining...
// Base types
func (b *Base) Define(c *Context) {
c.Putln("// Skipping definition for base type '%s'",
SrcName(c.protocol, b.XmlName()))
c.Putln("")
}
// Enum types
func (enum *Enum) Define(c *Context) {
c.Putln("const (")
for _, item := range enum.Items {
c.Putln("%s%s = %d", enum.SrcName(), item.srcName, item.Expr.Eval())
}
c.Putln(")")
c.Putln("")
}
// Resource types
func (res *Resource) Define(c *Context) {
c.Putln("type %s uint32", res.SrcName())
c.Putln("")
c.Putln("func New%sId(c *xgb.Conn) (%s, error) {",
res.SrcName(), res.SrcName())
c.Putln("id, err := c.NewId()")
c.Putln("if err != nil {")
c.Putln("return 0, err")
c.Putln("}")
c.Putln("return %s(id), nil", res.SrcName())
c.Putln("}")
c.Putln("")
}
// TypeDef types
func (td *TypeDef) Define(c *Context) {
c.Putln("type %s %s", td.srcName, td.Old.SrcName())
c.Putln("")
}
// Field definitions, reads and writes.
// Pad fields
func (f *PadField) Define(c *Context) {
if f.Align > 0 {
c.Putln("// alignment gap to multiple of %d", f.Align)
} else {
c.Putln("// padding: %d bytes", f.Bytes)
}
}
func (f *PadField) Read(c *Context, prefix string) {
if f.Align > 0 {
c.Putln("b = (b + %d) & ^%d // alignment gap", f.Align-1, f.Align-1)
} else {
c.Putln("b += %s // padding", f.Size())
}
}
func (f *PadField) Write(c *Context, prefix string) {
if f.Align > 0 {
c.Putln("b = (b + %d) & ^%d // alignment gap", f.Align-1, f.Align-1)
} else {
c.Putln("b += %s // padding", f.Size())
}
}
// Local fields
func (f *LocalField) Define(c *Context) {
c.Putln("// local field: %s %s", f.SrcName(), f.Type.SrcName())
panic("unreachable")
}
func (f *LocalField) Read(c *Context, prefix string) {
c.Putln("// reading local field: %s (%s) :: %s",
f.SrcName(), f.Size(), f.Type.SrcName())
panic("unreachable")
}
func (f *LocalField) Write(c *Context, prefix string) {
c.Putln("// skip writing local field: %s (%s) :: %s",
f.SrcName(), f.Size(), f.Type.SrcName())
}
// Expr fields
func (f *ExprField) Define(c *Context) {
c.Putln("// expression field: %s %s (%s)",
f.SrcName(), f.Type.SrcName(), f.Expr)
panic("unreachable")
}
func (f *ExprField) Read(c *Context, prefix string) {
c.Putln("// reading expression field: %s (%s) (%s) :: %s",
f.SrcName(), f.Size(), f.Expr, f.Type.SrcName())
panic("unreachable")
}
func (f *ExprField) Write(c *Context, prefix string) {
// Special case for bools, grrr.
if f.Type.SrcName() == "bool" {
c.Putln("buf[b] = byte(%s)", f.Expr.Reduce(prefix))
c.Putln("b += 1")
} else {
WriteSimpleSingleField(c, f.Expr.Reduce(prefix), f.Type)
}
}
// Value field
func (f *ValueField) Define(c *Context) {
c.Putln("%s %s", f.MaskName, f.SrcType())
c.Putln("%s []uint32", f.ListName)
}
func (f *ValueField) Read(c *Context, prefix string) {
ReadSimpleSingleField(c,
fmt.Sprintf("%s%s", prefix, f.MaskName), f.MaskType)
c.Putln("")
c.Putln("%s%s = make([]uint32, %s)",
prefix, f.ListName, f.ListLength().Reduce(prefix))
c.Putln("for i := 0; i < %s; i++ {", f.ListLength().Reduce(prefix))
c.Putln("%s%s[i] = xgb.Get32(buf[b:])", prefix, f.ListName)
c.Putln("b += 4")
c.Putln("}")
c.Putln("b = xgb.Pad(b)")
}
func (f *ValueField) Write(c *Context, prefix string) {
WriteSimpleSingleField(c,
fmt.Sprintf("%s%s", prefix, f.MaskName), f.MaskType)
c.Putln("for i := 0; i < %s; i++ {", f.ListLength().Reduce(prefix))
c.Putln("xgb.Put32(buf[b:], %s%s[i])", prefix, f.ListName)
c.Putln("b += 4")
c.Putln("}")
c.Putln("b = xgb.Pad(b)")
}
// Switch field
func (f *SwitchField) Define(c *Context) {
c.Putln("%s []uint32", f.Name)
}
func (f *SwitchField) Read(c *Context, prefix string) {
c.Putln("")
c.Putln("%s%s = make([]uint32, %s)",
prefix, f.Name, f.ListLength().Reduce(prefix))
c.Putln("for i := 0; i < %s; i++ {", f.ListLength().Reduce(prefix))
c.Putln("%s%s[i] = xgb.Get32(buf[b:])", prefix, f.Name)
c.Putln("b += 4")
c.Putln("}")
c.Putln("b = xgb.Pad(b)")
}
func (f *SwitchField) Write(c *Context, prefix string) {
c.Putln("for i := 0; i < %s; i++ {", f.ListLength().Reduce(prefix))
c.Putln("xgb.Put32(buf[b:], %s%s[i])", prefix, f.Name)
c.Putln("b += 4")
c.Putln("}")
c.Putln("b = xgb.Pad(b)")
}

179
vend/xgb/xgbgen/go_error.go Normal file
View File

@ -0,0 +1,179 @@
package main
import (
"fmt"
)
// Error types
func (e *Error) Define(c *Context) {
c.Putln("// %s is the error number for a %s.", e.ErrConst(), e.ErrConst())
c.Putln("const %s = %d", e.ErrConst(), e.Number)
c.Putln("")
c.Putln("type %s struct {", e.ErrType())
c.Putln("Sequence uint16")
c.Putln("NiceName string")
for _, field := range e.Fields {
field.Define(c)
}
c.Putln("}")
c.Putln("")
// Read defines a function that transforms a byte slice into this
// error struct.
e.Read(c)
// Makes sure this error type implements the xgb.Error interface.
e.ImplementsError(c)
// Let's the XGB event loop read this error.
c.Putln("func init() {")
if c.protocol.isExt() {
c.Putln("xgb.NewExtErrorFuncs[\"%s\"][%d] = %sNew",
c.protocol.ExtXName, e.Number, e.ErrType())
} else {
c.Putln("xgb.NewErrorFuncs[%d] = %sNew", e.Number, e.ErrType())
}
c.Putln("}")
c.Putln("")
}
func (e *Error) Read(c *Context) {
c.Putln("// %sNew constructs a %s value that implements xgb.Error from "+
"a byte slice.", e.ErrType(), e.ErrType())
c.Putln("func %sNew(buf []byte) xgb.Error {", e.ErrType())
c.Putln("v := %s{}", e.ErrType())
c.Putln("v.NiceName = \"%s\"", e.SrcName())
c.Putln("")
c.Putln("b := 1 // skip error determinant")
c.Putln("b += 1 // don't read error number")
c.Putln("")
c.Putln("v.Sequence = xgb.Get16(buf[b:])")
c.Putln("b += 2")
c.Putln("")
for _, field := range e.Fields {
field.Read(c, "v.")
c.Putln("")
}
c.Putln("return v")
c.Putln("}")
c.Putln("")
}
// ImplementsError writes functions to implement the XGB Error interface.
func (e *Error) ImplementsError(c *Context) {
c.Putln("// SequenceId returns the sequence id attached to the %s error.",
e.ErrConst())
c.Putln("// This is mostly used internally.")
c.Putln("func (err %s) SequenceId() uint16 {", e.ErrType())
c.Putln("return err.Sequence")
c.Putln("}")
c.Putln("")
c.Putln("// BadId returns the 'BadValue' number if one exists for the "+
"%s error. If no bad value exists, 0 is returned.", e.ErrConst())
c.Putln("func (err %s) BadId() uint32 {", e.ErrType())
if !c.protocol.isExt() {
c.Putln("return err.BadValue")
} else {
c.Putln("return 0")
}
c.Putln("}")
c.Putln("// Error returns a rudimentary string representation of the %s "+
"error.", e.ErrConst())
c.Putln("")
c.Putln("func (err %s) Error() string {", e.ErrType())
ErrorFieldString(c, e.Fields, e.ErrConst())
c.Putln("}")
c.Putln("")
}
// ErrorCopy types
func (e *ErrorCopy) Define(c *Context) {
c.Putln("// %s is the error number for a %s.", e.ErrConst(), e.ErrConst())
c.Putln("const %s = %d", e.ErrConst(), e.Number)
c.Putln("")
c.Putln("type %s %s", e.ErrType(), e.Old.(*Error).ErrType())
c.Putln("")
// Read defines a function that transforms a byte slice into this
// error struct.
e.Read(c)
// Makes sure this error type implements the xgb.Error interface.
e.ImplementsError(c)
// Let's the XGB know how to read this error.
c.Putln("func init() {")
if c.protocol.isExt() {
c.Putln("xgb.NewExtErrorFuncs[\"%s\"][%d] = %sNew",
c.protocol.ExtXName, e.Number, e.ErrType())
} else {
c.Putln("xgb.NewErrorFuncs[%d] = %sNew", e.Number, e.ErrType())
}
c.Putln("}")
c.Putln("")
}
func (e *ErrorCopy) Read(c *Context) {
c.Putln("// %sNew constructs a %s value that implements xgb.Error from "+
"a byte slice.", e.ErrType(), e.ErrType())
c.Putln("func %sNew(buf []byte) xgb.Error {", e.ErrType())
c.Putln("v := %s(%sNew(buf).(%s))",
e.ErrType(), e.Old.(*Error).ErrType(), e.Old.(*Error).ErrType())
c.Putln("v.NiceName = \"%s\"", e.SrcName())
c.Putln("return v")
c.Putln("}")
c.Putln("")
}
// ImplementsError writes functions to implement the XGB Error interface.
func (e *ErrorCopy) ImplementsError(c *Context) {
c.Putln("// SequenceId returns the sequence id attached to the %s error.",
e.ErrConst())
c.Putln("// This is mostly used internally.")
c.Putln("func (err %s) SequenceId() uint16 {", e.ErrType())
c.Putln("return err.Sequence")
c.Putln("}")
c.Putln("")
c.Putln("// BadId returns the 'BadValue' number if one exists for the "+
"%s error. If no bad value exists, 0 is returned.", e.ErrConst())
c.Putln("func (err %s) BadId() uint32 {", e.ErrType())
if !c.protocol.isExt() {
c.Putln("return err.BadValue")
} else {
c.Putln("return 0")
}
c.Putln("}")
c.Putln("")
c.Putln("// Error returns a rudimentary string representation of the %s "+
"error.", e.ErrConst())
c.Putln("func (err %s) Error() string {", e.ErrType())
ErrorFieldString(c, e.Old.(*Error).Fields, e.ErrConst())
c.Putln("}")
c.Putln("")
}
// ErrorFieldString works for both Error and ErrorCopy. It assembles all of the
// fields in an error and formats them into a single string.
func ErrorFieldString(c *Context, fields []Field, errName string) {
c.Putln("fieldVals := make([]string, 0, %d)", len(fields))
c.Putln("fieldVals = append(fieldVals, \"NiceName: \" + err.NiceName)")
c.Putln("fieldVals = append(fieldVals, "+
"xgb.Sprintf(\"Sequence: %s\", err.Sequence))", "%d")
for _, field := range fields {
switch field.(type) {
case *PadField:
continue
default:
if field.SrcType() == "string" {
c.Putln("fieldVals = append(fieldVals, \"%s: \" + err.%s)",
field.SrcName(), field.SrcName())
} else {
format := fmt.Sprintf("xgb.Sprintf(\"%s: %s\", err.%s)",
field.SrcName(), "%d", field.SrcName())
c.Putln("fieldVals = append(fieldVals, %s)", format)
}
}
}
c.Putln("return \"%s {\" + xgb.StringsJoin(fieldVals, \", \") + \"}\"",
errName)
}

211
vend/xgb/xgbgen/go_event.go Normal file
View File

@ -0,0 +1,211 @@
package main
import (
"fmt"
)
// Event types
func (e *Event) Define(c *Context) {
c.Putln("// %s is the event number for a %s.", e.SrcName(), e.EvType())
c.Putln("const %s = %d", e.SrcName(), e.Number)
c.Putln("")
c.Putln("type %s struct {", e.EvType())
if !e.NoSequence {
c.Putln("Sequence uint16")
}
for _, field := range e.Fields {
field.Define(c)
}
c.Putln("}")
c.Putln("")
// Read defines a function that transforms a byte slice into this
// event struct.
e.Read(c)
// Write defines a function that transforms this event struct into
// a byte slice.
e.Write(c)
// Makes sure that this event type is an Event interface.
c.Putln("// SequenceId returns the sequence id attached to the %s event.",
e.SrcName())
c.Putln("// Events without a sequence number (KeymapNotify) return 0.")
c.Putln("// This is mostly used internally.")
c.Putln("func (v %s) SequenceId() uint16 {", e.EvType())
if e.NoSequence {
c.Putln("return uint16(0)")
} else {
c.Putln("return v.Sequence")
}
c.Putln("}")
c.Putln("")
c.Putln("// String is a rudimentary string representation of %s.",
e.EvType())
c.Putln("func (v %s) String() string {", e.EvType())
EventFieldString(c, e.Fields, e.SrcName())
c.Putln("}")
c.Putln("")
// Let's the XGB event loop read this event.
c.Putln("func init() {")
if c.protocol.isExt() {
c.Putln("xgb.NewExtEventFuncs[\"%s\"][%d] = %sNew",
c.protocol.ExtXName, e.Number, e.EvType())
} else {
c.Putln("xgb.NewEventFuncs[%d] = %sNew", e.Number, e.EvType())
}
c.Putln("}")
c.Putln("")
}
func (e *Event) Read(c *Context) {
c.Putln("// %sNew constructs a %s value that implements xgb.Event from "+
"a byte slice.", e.EvType(), e.EvType())
c.Putln("func %sNew(buf []byte) xgb.Event {", e.EvType())
c.Putln("v := %s{}", e.EvType())
c.Putln("b := 1 // don't read event number")
c.Putln("")
for i, field := range e.Fields {
if i == 1 && !e.NoSequence {
c.Putln("v.Sequence = xgb.Get16(buf[b:])")
c.Putln("b += 2")
c.Putln("")
}
field.Read(c, "v.")
c.Putln("")
}
c.Putln("return v")
c.Putln("}")
c.Putln("")
}
func (e *Event) Write(c *Context) {
c.Putln("// Bytes writes a %s value to a byte slice.", e.EvType())
c.Putln("func (v %s) Bytes() []byte {", e.EvType())
c.Putln("buf := make([]byte, %s)", e.Size())
c.Putln("b := 0")
c.Putln("")
c.Putln("// write event number")
c.Putln("buf[b] = %d", e.Number)
c.Putln("b += 1")
c.Putln("")
for i, field := range e.Fields {
if i == 1 && !e.NoSequence {
c.Putln("b += 2 // skip sequence number")
c.Putln("")
}
field.Write(c, "v.")
c.Putln("")
}
c.Putln("return buf")
c.Putln("}")
c.Putln("")
}
// EventCopy types
func (e *EventCopy) Define(c *Context) {
c.Putln("// %s is the event number for a %s.", e.SrcName(), e.EvType())
c.Putln("const %s = %d", e.SrcName(), e.Number)
c.Putln("")
c.Putln("type %s %s", e.EvType(), e.Old.(*Event).EvType())
c.Putln("")
// Read defines a function that transforms a byte slice into this
// event struct.
e.Read(c)
// Write defines a function that transoforms this event struct into
// a byte slice.
e.Write(c)
// Makes sure that this event type is an Event interface.
c.Putln("// SequenceId returns the sequence id attached to the %s event.",
e.SrcName())
c.Putln("// Events without a sequence number (KeymapNotify) return 0.")
c.Putln("// This is mostly used internally.")
c.Putln("func (v %s) SequenceId() uint16 {", e.EvType())
if e.Old.(*Event).NoSequence {
c.Putln("return uint16(0)")
} else {
c.Putln("return v.Sequence")
}
c.Putln("}")
c.Putln("")
c.Putln("func (v %s) String() string {", e.EvType())
EventFieldString(c, e.Old.(*Event).Fields, e.SrcName())
c.Putln("}")
c.Putln("")
// Let's the XGB event loop read this event.
c.Putln("func init() {")
if c.protocol.isExt() {
c.Putln("xgb.NewExtEventFuncs[\"%s\"][%d] = %sNew",
c.protocol.ExtXName, e.Number, e.EvType())
} else {
c.Putln("xgb.NewEventFuncs[%d] = %sNew", e.Number, e.EvType())
}
c.Putln("}")
c.Putln("")
}
func (e *EventCopy) Read(c *Context) {
c.Putln("// %sNew constructs a %s value that implements xgb.Event from "+
"a byte slice.", e.EvType(), e.EvType())
c.Putln("func %sNew(buf []byte) xgb.Event {", e.EvType())
c.Putln("return %s(%sNew(buf).(%s))",
e.EvType(), e.Old.(*Event).EvType(), e.Old.(*Event).EvType())
c.Putln("}")
c.Putln("")
}
func (e *EventCopy) Write(c *Context) {
c.Putln("// Bytes writes a %s value to a byte slice.", e.EvType())
c.Putln("func (v %s) Bytes() []byte {", e.EvType())
c.Putln("buf := %s(v).Bytes()", e.Old.(*Event).EvType())
c.Putln("buf[0] = %d", e.Number)
c.Putln("return buf")
c.Putln("}")
c.Putln("")
}
// EventFieldString works for both Event and EventCopy. It assembles all of the
// fields in an event and formats them into a single string.
func EventFieldString(c *Context, fields []Field, evName string) {
c.Putln("fieldVals := make([]string, 0, %d)", len(fields))
if evName != "KeymapNotify" {
c.Putln("fieldVals = append(fieldVals, "+
"xgb.Sprintf(\"Sequence: %s\", v.Sequence))", "%d")
}
for _, field := range fields {
switch f := field.(type) {
case *PadField:
continue
case *SingleField:
switch f.Type.(type) {
case *Base:
case *Resource:
case *TypeDef:
default:
continue
}
switch field.SrcType() {
case "string":
format := fmt.Sprintf("xgb.Sprintf(\"%s: %s\", v.%s)",
field.SrcName(), "%s", field.SrcName())
c.Putln("fieldVals = append(fieldVals, %s)", format)
case "bool":
format := fmt.Sprintf("xgb.Sprintf(\"%s: %s\", v.%s)",
field.SrcName(), "%t", field.SrcName())
c.Putln("fieldVals = append(fieldVals, %s)", format)
default:
format := fmt.Sprintf("xgb.Sprintf(\"%s: %s\", v.%s)",
field.SrcName(), "%d", field.SrcName())
c.Putln("fieldVals = append(fieldVals, %s)", format)
}
}
}
c.Putln("return \"%s {\" + xgb.StringsJoin(fieldVals, \", \") + \"}\"",
evName)
}

107
vend/xgb/xgbgen/go_list.go Normal file
View File

@ -0,0 +1,107 @@
package main
import (
"fmt"
"log"
"strings"
)
// List fields
func (f *ListField) Define(c *Context) {
c.Putln("%s %s // size: %s",
f.SrcName(), f.SrcType(), f.Size())
}
func (f *ListField) Read(c *Context, prefix string) {
switch t := f.Type.(type) {
case *Resource:
length := f.LengthExpr.Reduce(prefix)
c.Putln("%s%s = make([]%s, %s)",
prefix, f.SrcName(), t.SrcName(), length)
c.Putln("for i := 0; i < int(%s); i++ {", length)
ReadSimpleSingleField(c, fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t)
c.Putln("}")
case *Base:
length := f.LengthExpr.Reduce(prefix)
if strings.ToLower(t.XmlName()) == "char" {
c.Putln("{")
c.Putln("byteString := make([]%s, %s)", t.SrcName(), length)
c.Putln("copy(byteString[:%s], buf[b:])", length)
c.Putln("%s%s = string(byteString)", prefix, f.SrcName())
// This is apparently a special case. The "Str" type itself
// doesn't specify any padding. I suppose it's up to the
// request/reply spec that uses it to get the padding right?
c.Putln("b += int(%s)", length)
c.Putln("}")
} else if t.SrcName() == "byte" {
c.Putln("%s%s = make([]%s, %s)",
prefix, f.SrcName(), t.SrcName(), length)
c.Putln("copy(%s%s[:%s], buf[b:])", prefix, f.SrcName(), length)
c.Putln("b += int(%s)", length)
} else {
c.Putln("%s%s = make([]%s, %s)",
prefix, f.SrcName(), t.SrcName(), length)
c.Putln("for i := 0; i < int(%s); i++ {", length)
ReadSimpleSingleField(c,
fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t)
c.Putln("}")
}
case *TypeDef:
length := f.LengthExpr.Reduce(prefix)
c.Putln("%s%s = make([]%s, %s)",
prefix, f.SrcName(), t.SrcName(), length)
c.Putln("for i := 0; i < int(%s); i++ {", length)
ReadSimpleSingleField(c, fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t)
c.Putln("}")
case *Union:
c.Putln("%s%s = make([]%s, %s)",
prefix, f.SrcName(), t.SrcName(), f.LengthExpr.Reduce(prefix))
c.Putln("b += %sReadList(buf[b:], %s%s)",
t.SrcName(), prefix, f.SrcName())
case *Struct:
c.Putln("%s%s = make([]%s, %s)",
prefix, f.SrcName(), t.SrcName(), f.LengthExpr.Reduce(prefix))
c.Putln("b += %sReadList(buf[b:], %s%s)",
t.SrcName(), prefix, f.SrcName())
default:
log.Panicf("Cannot read list field '%s' with %T type.",
f.XmlName(), f.Type)
}
}
func (f *ListField) Write(c *Context, prefix string) {
switch t := f.Type.(type) {
case *Resource:
length := f.Length().Reduce(prefix)
c.Putln("for i := 0; i < int(%s); i++ {", length)
WriteSimpleSingleField(c,
fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t)
c.Putln("}")
case *Base:
length := f.Length().Reduce(prefix)
if t.SrcName() == "byte" {
c.Putln("copy(buf[b:], %s%s[:%s])", prefix, f.SrcName(), length)
c.Putln("b += int(%s)", length)
} else {
c.Putln("for i := 0; i < int(%s); i++ {", length)
WriteSimpleSingleField(c,
fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t)
c.Putln("}")
}
case *TypeDef:
length := f.Length().Reduce(prefix)
c.Putln("for i := 0; i < int(%s); i++ {", length)
WriteSimpleSingleField(c,
fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t)
c.Putln("}")
case *Union:
c.Putln("b += %sListBytes(buf[b:], %s%s)",
t.SrcName(), prefix, f.SrcName())
case *Struct:
c.Putln("b += %sListBytes(buf[b:], %s%s)",
t.SrcName(), prefix, f.SrcName())
default:
log.Panicf("Cannot write list field '%s' with %T type.",
f.XmlName(), f.Type)
}
}

View File

@ -0,0 +1,242 @@
package main
import (
"fmt"
"strings"
)
func (r *Request) Define(c *Context) {
c.Putln("// %s is a cookie used only for %s requests.",
r.CookieName(), r.SrcName())
c.Putln("type %s struct {", r.CookieName())
c.Putln("*xgb.Cookie")
c.Putln("}")
c.Putln("")
if r.Reply != nil {
c.Putln("// %s sends a checked request.", r.SrcName())
c.Putln("// If an error occurs, it will be returned with the reply "+
"by calling %s.Reply()", r.CookieName())
c.Putln("func %s(c *xgb.Conn, %s) %s {",
r.SrcName(), r.ParamNameTypes(), r.CookieName())
r.CheckExt(c)
c.Putln("cookie := c.NewCookie(true, true)")
c.Putln("c.NewRequest(%s(c, %s), cookie)", r.ReqName(), r.ParamNames())
c.Putln("return %s{cookie}", r.CookieName())
c.Putln("}")
c.Putln("")
c.Putln("// %sUnchecked sends an unchecked request.", r.SrcName())
c.Putln("// If an error occurs, it can only be retrieved using " +
"xgb.WaitForEvent or xgb.PollForEvent.")
c.Putln("func %sUnchecked(c *xgb.Conn, %s) %s {",
r.SrcName(), r.ParamNameTypes(), r.CookieName())
r.CheckExt(c)
c.Putln("cookie := c.NewCookie(false, true)")
c.Putln("c.NewRequest(%s(c, %s), cookie)", r.ReqName(), r.ParamNames())
c.Putln("return %s{cookie}", r.CookieName())
c.Putln("}")
c.Putln("")
r.ReadReply(c)
} else {
c.Putln("// %s sends an unchecked request.", r.SrcName())
c.Putln("// If an error occurs, it can only be retrieved using " +
"xgb.WaitForEvent or xgb.PollForEvent.")
c.Putln("func %s(c *xgb.Conn, %s) %s {",
r.SrcName(), r.ParamNameTypes(), r.CookieName())
r.CheckExt(c)
c.Putln("cookie := c.NewCookie(false, false)")
c.Putln("c.NewRequest(%s(c, %s), cookie)", r.ReqName(), r.ParamNames())
c.Putln("return %s{cookie}", r.CookieName())
c.Putln("}")
c.Putln("")
c.Putln("// %sChecked sends a checked request.", r.SrcName())
c.Putln("// If an error occurs, it can be retrieved using "+
"%s.Check()", r.CookieName())
c.Putln("func %sChecked(c *xgb.Conn, %s) %s {",
r.SrcName(), r.ParamNameTypes(), r.CookieName())
r.CheckExt(c)
c.Putln("cookie := c.NewCookie(true, false)")
c.Putln("c.NewRequest(%s(c, %s), cookie)", r.ReqName(), r.ParamNames())
c.Putln("return %s{cookie}", r.CookieName())
c.Putln("}")
c.Putln("")
c.Putln("// Check returns an error if one occurred for checked " +
"requests that are not expecting a reply.")
c.Putln("// This cannot be called for requests expecting a reply, " +
"nor for unchecked requests.")
c.Putln("func (cook %s) Check() error {", r.CookieName())
c.Putln("return cook.Cookie.Check()")
c.Putln("}")
c.Putln("")
}
r.WriteRequest(c)
}
func (r *Request) CheckExt(c *Context) {
if !c.protocol.isExt() {
return
}
c.Putln("c.ExtLock.RLock()")
c.Putln("defer c.ExtLock.RUnlock()")
c.Putln("if _, ok := c.Extensions[\"%s\"]; !ok {", c.protocol.ExtXName)
c.Putln("panic(\"Cannot issue request '%s' using the uninitialized "+
"extension '%s'. %s.Init(connObj) must be called first.\")",
r.SrcName(), c.protocol.ExtXName, c.protocol.PkgName())
c.Putln("}")
}
func (r *Request) ReadReply(c *Context) {
c.Putln("// %s represents the data returned from a %s request.",
r.ReplyTypeName(), r.SrcName())
c.Putln("type %s struct {", r.ReplyTypeName())
c.Putln("Sequence uint16 // sequence number of the request for this reply")
c.Putln("Length uint32 // number of bytes in this reply")
for _, field := range r.Reply.Fields {
field.Define(c)
}
c.Putln("}")
c.Putln("")
c.Putln("// Reply blocks and returns the reply data for a %s request.",
r.SrcName())
c.Putln("func (cook %s) Reply() (*%s, error) {",
r.CookieName(), r.ReplyTypeName())
c.Putln("buf, err := cook.Cookie.Reply()")
c.Putln("if err != nil {")
c.Putln("return nil, err")
c.Putln("}")
c.Putln("if buf == nil {")
c.Putln("return nil, nil")
c.Putln("}")
c.Putln("return %s(buf), nil", r.ReplyName())
c.Putln("}")
c.Putln("")
c.Putln("// %s reads a byte slice into a %s value.",
r.ReplyName(), r.ReplyTypeName())
c.Putln("func %s(buf []byte) *%s {",
r.ReplyName(), r.ReplyTypeName())
c.Putln("v := new(%s)", r.ReplyTypeName())
c.Putln("b := 1 // skip reply determinant")
c.Putln("")
for i, field := range r.Reply.Fields {
field.Read(c, "v.")
c.Putln("")
if i == 0 {
c.Putln("v.Sequence = xgb.Get16(buf[b:])")
c.Putln("b += 2")
c.Putln("")
c.Putln("v.Length = xgb.Get32(buf[b:]) // 4-byte units")
c.Putln("b += 4")
c.Putln("")
}
}
c.Putln("return v")
c.Putln("}")
c.Putln("")
}
func (r *Request) WriteRequest(c *Context) {
sz := r.Size(c)
writeSize1 := func() {
if sz.exact {
c.Putln("xgb.Put16(buf[b:], uint16(size / 4)) " +
"// write request size in 4-byte units")
} else {
c.Putln("blen := b")
}
c.Putln("b += 2")
c.Putln("")
}
writeSize2 := func() {
if sz.exact {
c.Putln("return buf")
return
}
c.Putln("b = xgb.Pad(b)")
c.Putln("xgb.Put16(buf[blen:], uint16(b / 4)) " +
"// write request size in 4-byte units")
c.Putln("return buf[:b]")
}
c.Putln("// Write request to wire for %s", r.SrcName())
c.Putln("// %s writes a %s request to a byte slice.",
r.ReqName(), r.SrcName())
c.Putln("func %s(c *xgb.Conn, %s) []byte {",
r.ReqName(), r.ParamNameTypes())
c.Putln("size := %s", sz)
c.Putln("b := 0")
c.Putln("buf := make([]byte, size)")
c.Putln("")
if c.protocol.isExt() {
c.Putln("c.ExtLock.RLock()")
c.Putln("buf[b] = c.Extensions[\"%s\"]", c.protocol.ExtXName)
c.Putln("c.ExtLock.RUnlock()")
c.Putln("b += 1")
c.Putln("")
}
c.Putln("buf[b] = %d // request opcode", r.Opcode)
c.Putln("b += 1")
c.Putln("")
if len(r.Fields) == 0 {
if !c.protocol.isExt() {
c.Putln("b += 1 // padding")
}
writeSize1()
} else if c.protocol.isExt() {
writeSize1()
}
for i, field := range r.Fields {
field.Write(c, "")
c.Putln("")
if i == 0 && !c.protocol.isExt() {
writeSize1()
}
}
writeSize2()
c.Putln("}")
c.Putln("")
}
func (r *Request) ParamNames() string {
names := make([]string, 0, len(r.Fields))
for _, field := range r.Fields {
switch f := field.(type) {
case *ValueField:
names = append(names, f.MaskName)
names = append(names, f.ListName)
case *PadField:
continue
case *ExprField:
continue
default:
names = append(names, fmt.Sprintf("%s", field.SrcName()))
}
}
return strings.Join(names, ", ")
}
func (r *Request) ParamNameTypes() string {
nameTypes := make([]string, 0, len(r.Fields))
for _, field := range r.Fields {
switch f := field.(type) {
case *ValueField:
nameTypes = append(nameTypes,
fmt.Sprintf("%s %s", f.MaskName, f.MaskType.SrcName()))
nameTypes = append(nameTypes,
fmt.Sprintf("%s []uint32", f.ListName))
case *PadField:
continue
case *ExprField:
continue
case *RequiredStartAlign:
continue
default:
nameTypes = append(nameTypes,
fmt.Sprintf("%s %s", field.SrcName(), field.SrcType()))
}
}
return strings.Join(nameTypes, ", ")
}

View File

@ -0,0 +1,166 @@
package main
import (
"fmt"
"log"
)
func (f *SingleField) Define(c *Context) {
c.Putln("%s %s", f.SrcName(), f.Type.SrcName())
}
func ReadSimpleSingleField(c *Context, name string, typ Type) {
switch t := typ.(type) {
case *Resource:
c.Putln("%s = %s(xgb.Get32(buf[b:]))", name, t.SrcName())
case *TypeDef:
switch t.Size().Eval() {
case 1:
c.Putln("%s = %s(buf[b])", name, t.SrcName())
case 2:
c.Putln("%s = %s(xgb.Get16(buf[b:]))", name, t.SrcName())
case 4:
c.Putln("%s = %s(xgb.Get32(buf[b:]))", name, t.SrcName())
case 8:
c.Putln("%s = %s(xgb.Get64(buf[b:]))", name, t.SrcName())
}
case *Base:
// If this is a bool, stop short and do something special.
if t.SrcName() == "bool" {
c.Putln("if buf[b] == 1 {")
c.Putln("%s = true", name)
c.Putln("} else {")
c.Putln("%s = false", name)
c.Putln("}")
break
}
var val string
switch t.Size().Eval() {
case 1:
val = fmt.Sprintf("buf[b]")
case 2:
val = fmt.Sprintf("xgb.Get16(buf[b:])")
case 4:
val = fmt.Sprintf("xgb.Get32(buf[b:])")
case 8:
val = fmt.Sprintf("xgb.Get64(buf[b:])")
}
// We need to convert base types if they aren't uintXX or byte
ty := t.SrcName()
if ty != "byte" && ty != "uint16" && ty != "uint32" && ty != "uint64" {
val = fmt.Sprintf("%s(%s)", ty, val)
}
c.Putln("%s = %s", name, val)
default:
log.Panicf("Cannot read field '%s' as a simple field with %T type.",
name, typ)
}
c.Putln("b += %s", typ.Size())
}
func (f *SingleField) Read(c *Context, prefix string) {
switch t := f.Type.(type) {
case *Resource:
ReadSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t)
case *TypeDef:
ReadSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t)
case *Base:
ReadSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t)
case *Struct:
c.Putln("%s%s = %s{}", prefix, f.SrcName(), t.SrcName())
c.Putln("b += %sRead(buf[b:], &%s%s)", t.SrcName(), prefix, f.SrcName())
case *Union:
c.Putln("%s%s = %s{}", prefix, f.SrcName(), t.SrcName())
c.Putln("b += %sRead(buf[b:], &%s%s)", t.SrcName(), prefix, f.SrcName())
default:
log.Panicf("Cannot read field '%s' with %T type.", f.XmlName(), f.Type)
}
}
func WriteSimpleSingleField(c *Context, name string, typ Type) {
switch t := typ.(type) {
case *Resource:
c.Putln("xgb.Put32(buf[b:], uint32(%s))", name)
case *TypeDef:
switch t.Size().Eval() {
case 1:
c.Putln("buf[b] = byte(%s)", name)
case 2:
c.Putln("xgb.Put16(buf[b:], uint16(%s))", name)
case 4:
c.Putln("xgb.Put32(buf[b:], uint32(%s))", name)
case 8:
c.Putln("xgb.Put64(buf[b:], uint64(%s))", name)
}
case *Base:
// If this is a bool, stop short and do something special.
if t.SrcName() == "bool" {
c.Putln("if %s {", name)
c.Putln("buf[b] = 1")
c.Putln("} else {")
c.Putln("buf[b] = 0")
c.Putln("}")
break
}
switch t.Size().Eval() {
case 1:
if t.SrcName() != "byte" {
c.Putln("buf[b] = byte(%s)", name)
} else {
c.Putln("buf[b] = %s", name)
}
case 2:
if t.SrcName() != "uint16" {
c.Putln("xgb.Put16(buf[b:], uint16(%s))", name)
} else {
c.Putln("xgb.Put16(buf[b:], %s)", name)
}
case 4:
if t.SrcName() != "uint32" {
c.Putln("xgb.Put32(buf[b:], uint32(%s))", name)
} else {
c.Putln("xgb.Put32(buf[b:], %s)", name)
}
case 8:
if t.SrcName() != "uint64" {
c.Putln("xgb.Put64(buf[b:], uint64(%s))", name)
} else {
c.Putln("xgb.Put64(buf[b:], %s)", name)
}
}
default:
log.Fatalf("Cannot read field '%s' as a simple field with %T type.",
name, typ)
}
c.Putln("b += %s", typ.Size())
}
func (f *SingleField) Write(c *Context, prefix string) {
switch t := f.Type.(type) {
case *Resource:
WriteSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t)
case *TypeDef:
WriteSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t)
case *Base:
WriteSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t)
case *Union:
c.Putln("{")
c.Putln("unionBytes := %s%s.Bytes()", prefix, f.SrcName())
c.Putln("copy(buf[b:], unionBytes)")
c.Putln("b += len(unionBytes)")
c.Putln("}")
case *Struct:
c.Putln("{")
c.Putln("structBytes := %s%s.Bytes()", prefix, f.SrcName())
c.Putln("copy(buf[b:], structBytes)")
c.Putln("b += len(structBytes)")
c.Putln("}")
default:
log.Fatalf("Cannot read field '%s' with %T type.", f.XmlName(), f.Type)
}
}

View File

@ -0,0 +1,118 @@
package main
func (s *Struct) Define(c *Context) {
c.Putln("type %s struct {", s.SrcName())
for _, field := range s.Fields {
field.Define(c)
}
c.Putln("}")
c.Putln("")
// Write function that reads bytes and produces this struct.
s.Read(c)
// Write function that reads bytes and produces a list of this struct.
s.ReadList(c)
// Write function that writes bytes given this struct.
s.Write(c)
// Write function that writes a list of this struct.
s.WriteList(c)
// Write function that computes the size of a list of these structs,
// IF there is a list field in this struct.
if s.HasList() {
s.WriteListSize(c)
}
}
// Read for a struct creates a function 'ReadStructName' that takes a source
// byte slice (i.e., the buffer) and a destination struct, and returns
// the number of bytes read off the buffer.
// 'ReadStructName' should only be used to read raw reply data from the wire.
func (s *Struct) Read(c *Context) {
c.Putln("// %sRead reads a byte slice into a %s value.",
s.SrcName(), s.SrcName())
c.Putln("func %sRead(buf []byte, v *%s) int {", s.SrcName(), s.SrcName())
c.Putln("b := 0")
c.Putln("")
for _, field := range s.Fields {
field.Read(c, "v.")
c.Putln("")
}
c.Putln("return b")
c.Putln("}")
c.Putln("")
}
// ReadList for a struct creates a function 'ReadStructNameList' that takes
// a source (i.e., the buffer) byte slice, and a destination slice and returns
// the number of bytes read from the byte slice.
func (s *Struct) ReadList(c *Context) {
c.Putln("// %sReadList reads a byte slice into a list of %s values.",
s.SrcName(), s.SrcName())
c.Putln("func %sReadList(buf []byte, dest []%s) int {",
s.SrcName(), s.SrcName())
c.Putln("b := 0")
c.Putln("for i := 0; i < len(dest); i++ {")
c.Putln("dest[i] = %s{}", s.SrcName())
c.Putln("b += %sRead(buf[b:], &dest[i])", s.SrcName())
c.Putln("}")
c.Putln("return xgb.Pad(b)")
c.Putln("}")
c.Putln("")
}
func (s *Struct) Write(c *Context) {
c.Putln("// Bytes writes a %s value to a byte slice.", s.SrcName())
c.Putln("func (v %s) Bytes() []byte {", s.SrcName())
c.Putln("buf := make([]byte, %s)", s.Size().Reduce("v."))
c.Putln("b := 0")
c.Putln("")
for _, field := range s.Fields {
field.Write(c, "v.")
c.Putln("")
}
c.Putln("return buf[:b]")
c.Putln("}")
c.Putln("")
}
func (s *Struct) WriteList(c *Context) {
c.Putln("// %sListBytes writes a list of %s values to a byte slice.",
s.SrcName(), s.SrcName())
c.Putln("func %sListBytes(buf []byte, list []%s) int {",
s.SrcName(), s.SrcName())
c.Putln("b := 0")
c.Putln("var structBytes []byte")
c.Putln("for _, item := range list {")
c.Putln("structBytes = item.Bytes()")
c.Putln("copy(buf[b:], structBytes)")
c.Putln("b += len(structBytes)")
c.Putln("}")
c.Putln("return xgb.Pad(b)")
c.Putln("}")
c.Putln("")
}
func (s *Struct) WriteListSize(c *Context) {
c.Putln("// %sListSize computes the size (bytes) of a list of %s values.",
s.SrcName(), s.SrcName())
c.Putln("func %sListSize(list []%s) int {", s.SrcName(), s.SrcName())
c.Putln("size := 0")
if s.Size().Expression.Concrete() {
c.Putln("for _ = range list {")
} else {
c.Putln("for _, item := range list {")
}
c.Putln("size += %s", s.Size().Reduce("item."))
c.Putln("}")
c.Putln("return size")
c.Putln("}")
c.Putln("")
}

Some files were not shown because too many files have changed in this diff Show More