From 37692a873acd7cebc790292be080946474da9d4c Mon Sep 17 00:00:00 2001 From: lawl Date: Sun, 13 Dec 2020 08:27:28 +0100 Subject: [PATCH] Implement output filtering --- README.md | 6 +- config.go | 18 +- go.mod | 1 + go.sum | 2 + main.go | 200 ++- module.go | 240 +++- ui.go | 198 ++- update_noop.go | 16 +- .../github.com/BurntSushi/xgb/shape/shape.go | 1025 +++++++++++++++ .../BurntSushi/xgb/xinerama/xinerama.go | 718 ++++++++++ .../github.com/BurntSushi/xgbutil/.gitignore | 6 + vendor/github.com/BurntSushi/xgbutil/COPYING | 13 + vendor/github.com/BurntSushi/xgbutil/Makefile | 36 + vendor/github.com/BurntSushi/xgbutil/README | 55 + vendor/github.com/BurntSushi/xgbutil/STYLE | 29 + vendor/github.com/BurntSushi/xgbutil/doc.go | 67 + .../github.com/BurntSushi/xgbutil/ewmh/doc.go | 56 + .../BurntSushi/xgbutil/ewmh/ewmh.go | 1159 +++++++++++++++++ .../BurntSushi/xgbutil/ewmh/winman.go | 31 + .../BurntSushi/xgbutil/icccm/doc.go | 61 + .../BurntSushi/xgbutil/icccm/icccm.go | 358 +++++ .../BurntSushi/xgbutil/icccm/protocols.go | 64 + .../github.com/BurntSushi/xgbutil/session.vim | 1 + vendor/github.com/BurntSushi/xgbutil/types.go | 167 +++ .../BurntSushi/xgbutil/xevent/callback.go | 389 ++++++ .../BurntSushi/xgbutil/xevent/doc.go | 76 ++ .../BurntSushi/xgbutil/xevent/eventloop.go | 291 +++++ .../BurntSushi/xgbutil/xevent/types_auto.go | 336 +++++ .../BurntSushi/xgbutil/xevent/types_manual.go | 90 ++ .../BurntSushi/xgbutil/xevent/xevent.go | 237 ++++ .../github.com/BurntSushi/xgbutil/xgbutil.go | 348 +++++ .../BurntSushi/xgbutil/xprop/atom.go | 103 ++ .../BurntSushi/xgbutil/xprop/doc.go | 40 + .../BurntSushi/xgbutil/xprop/xprop.go | 270 ++++ vendor/modules.txt | 9 + 35 files changed, 6545 insertions(+), 171 deletions(-) create mode 100644 vendor/github.com/BurntSushi/xgb/shape/shape.go create mode 100644 vendor/github.com/BurntSushi/xgb/xinerama/xinerama.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/.gitignore create mode 100644 vendor/github.com/BurntSushi/xgbutil/COPYING create mode 100644 vendor/github.com/BurntSushi/xgbutil/Makefile create mode 100644 vendor/github.com/BurntSushi/xgbutil/README create mode 100644 vendor/github.com/BurntSushi/xgbutil/STYLE create mode 100644 vendor/github.com/BurntSushi/xgbutil/doc.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/ewmh/doc.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/ewmh/ewmh.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/ewmh/winman.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/icccm/doc.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/icccm/icccm.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/icccm/protocols.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/session.vim create mode 100644 vendor/github.com/BurntSushi/xgbutil/types.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/xevent/callback.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/xevent/doc.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/xevent/eventloop.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/xevent/types_auto.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/xevent/types_manual.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/xevent/xevent.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/xgbutil.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/xprop/atom.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/xprop/doc.go create mode 100644 vendor/github.com/BurntSushi/xgbutil/xprop/xprop.go diff --git a/README.md b/README.md index 08761fa..ffb6126 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ If you have been maintaining a third party package for a while and would like it ## Usage -Select the microphone you want to denoise, and click "Load NoiseTorch", NoiseTorch will create a virtual microphone called "NoiseTorch Microphone" that you can select in any application. +Select the microphone you want to denoise, and click "Load NoiseTorch", NoiseTorch will create a virtual microphone called "NoiseTorch Microphone" that you can select in any application. Output filtering works the same way, simply output the applications you want to filter to "NoiseTorch Headphones". When you're done using it, simply click "Unload NoiseTorch" to remove it again, until you need it next time. @@ -80,7 +80,9 @@ Please see the [Troubleshooting](https://github.com/lawl/NoiseTorch/wiki/Trouble ## Latency -NoiseTorch may introduce a small amount of latency. The amount of inherent latency introduced by noise supression is 10ms, this is very low and should not be a problem. Additionally PulseAudio currently introduces a variable amount of latency that depends on your system. Lowering this latency [requires a change in PulseAudio](https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/120). +NoiseTorch may introduce a small amount of latency for microphone filtering. The amount of inherent latency introduced by noise supression is 10ms, this is very low and should not be a problem. Additionally PulseAudio currently introduces a variable amount of latency that depends on your system. Lowering this latency [requires a change in PulseAudio](https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/120). + +Output filtering currently introduces something on the order of ~100ms with pulseaudio. This should still be fine for regular conferences, VOIPing and gaming. Maybe not for competitive gaming teams. ## Building from source diff --git a/config.go b/config.go index d717b11..86b9e18 100644 --- a/config.go +++ b/config.go @@ -14,16 +14,30 @@ type config struct { Threshold int DisplayMonitorSources bool EnableUpdates bool + GuiltTripped bool + FilterInput bool + FilterOutput bool LastUsedInput string + LastUsedOutput string } const configFile = "config.toml" func initializeConfigIfNot() { log.Println("Checking if config needs to be initialized") - conf := config{Threshold: 95, DisplayMonitorSources: false, EnableUpdates: true, LastUsedInput: ""} // if you're a package maintainer and you mess with this, we have a problem. - // Unless you set -tags release on the build the updater is *not* compiled in any. DO NOT MESS WITH THIS! + + // if you're a package maintainer and you mess with this, we have a problem. + // Unless you set -tags release on the build the updater is *not* compiled in anymore. DO NOT MESS WITH THIS! // This isn't and never was the proper location to disable the updater. + conf := config{ + Threshold: 95, + DisplayMonitorSources: false, + EnableUpdates: true, + GuiltTripped: false, + FilterInput: true, + FilterOutput: false, + LastUsedInput: "", + LastUsedOutput: ""} configdir := configDir() ok, err := exists(configdir) diff --git a/go.mod b/go.mod index d5cbcbd..17626b4 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.14 require ( gioui.org v0.0.0-20200630184602-223f8fd40ae4 // indirect github.com/BurntSushi/toml v0.3.1 + github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 github.com/aarzilli/nucular v0.0.0-20200615134801-81910c722bba github.com/lawl/pulseaudio v0.0.0-20200802093727-ab0735955fd0 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 diff --git a/go.sum b/go.sum index 175d3dc..6ac48b0 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 h1:O/r2Sj+8QcMF7V5IcmiE2sMFV2q3J47BEirxbXJAdzA= +github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k= github.com/aarzilli/nucular v0.0.0-20200615134801-81910c722bba h1:7OBB0+T/f0gGMdqTwoXF872nDKosq4dIK/H2cRlrnWI= github.com/aarzilli/nucular v0.0.0-20200615134801-81910c722bba/go.mod h1:TsFEH0qn2Uu3C3guJjfIaoCqgpoCvU+laq0SSK2TOyY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= diff --git a/main.go b/main.go index d83002b..568c780 100644 --- a/main.go +++ b/main.go @@ -8,9 +8,13 @@ import ( "log" "os" "path/filepath" + "strings" "syscall" "time" + "github.com/BurntSushi/xgbutil" + "github.com/BurntSushi/xgbutil/ewmh" + "github.com/BurntSushi/xgbutil/icccm" "github.com/aarzilli/nucular/font" "github.com/lawl/pulseaudio" @@ -24,31 +28,36 @@ import ( //go:generate go run scripts/embedversion.go //go:generate go run scripts/embedlicenses.go -type input struct { +type device struct { ID string Name string isMonitor bool checked bool dynamicLatency bool + rate uint32 } +const appName = "NoiseTorch" + func main() { var pulsepid int var setcap bool - var sourceName string + var sinkName string var unload bool - var load bool + var loadInput bool + var loadOutput bool var threshold int var list bool flag.IntVar(&pulsepid, "removerlimit", -1, "for internal use only") flag.BoolVar(&setcap, "setcap", false, "for internal use only") - flag.StringVar(&sourceName, "s", "", "Use the specified source device ID") - flag.BoolVar(&load, "i", false, "Load supressor for input. If no source device ID is specified the default pulse audio source is used.") + flag.StringVar(&sinkName, "s", "", "Use the specified source/sink device ID") + flag.BoolVar(&loadInput, "i", false, "Load supressor for input. If no source device ID is specified the default pulse audio source is used.") + flag.BoolVar(&loadOutput, "o", false, "Load supressor for output. If no source device ID is specified the default pulse audio source is used.") flag.BoolVar(&unload, "u", false, "Unload supressor") flag.IntVar(&threshold, "t", -1, "Voice activation threshold") - flag.BoolVar(&list, "l", false, "List available PulseAudio sources") + flag.BoolVar(&list, "l", false, "List available PulseAudio devices") flag.Parse() // we also execute this opportunistically on pulsepid since that's also called as root, but need to do so silently, so no os.Exit()'s @@ -101,9 +110,16 @@ func main() { } if list { + fmt.Println("Sources:") sources := getSources(paClient) for i := range sources { - fmt.Printf("Device Name: %s\nDevice ID: %s\n\n", sources[i].Name, sources[i].ID) + fmt.Printf("\tDevice Name: %s\n\tDevice ID: %s\n\n", sources[i].Name, sources[i].ID) + } + + fmt.Println("Sinks:") + sinks := getSinks(paClient) + for i := range sinks { + fmt.Printf("\tDevice Name: %s\n\tDevice ID: %s\n\n", sinks[i].Name, sinks[i].ID) } os.Exit(0) @@ -119,30 +135,53 @@ func main() { } if unload { - unloadSupressor(paClient) + unloadSupressor(&ctx) os.Exit(0) } - if load { + if loadInput { ctx.paClient = paClient sources := getSources(paClient) - if supressorState(paClient) != unloaded { - fmt.Fprintf(os.Stderr, "Supressor is already loaded.\n") - os.Exit(1) - } - - if sourceName == "" { + if sinkName == "" { defaultSource, err := getDefaultSourceID(paClient) if err != nil { fmt.Fprintf(os.Stderr, "No source specified to load and failed to load default source: %+v\n", err) os.Exit(1) } - sourceName = defaultSource + sinkName = defaultSource } for i := range sources { - if sources[i].ID == sourceName { - err := loadSupressor(&ctx, sources[i]) + if sources[i].ID == sinkName { + sources[i].checked = true + err := loadSupressor(&ctx, &sources[i], &device{}) + if err != nil { + fmt.Fprintf(os.Stderr, "Error loading PulseAudio Module: %+v\n", err) + os.Exit(1) + } + os.Exit(0) + } + } + fmt.Fprintf(os.Stderr, "PulseAudio source not found: %s\n", sinkName) + os.Exit(1) + + } + if loadOutput { + ctx.paClient = paClient + sinks := getSinks(paClient) + + if sinkName == "" { + defaultSink, err := getDefaultSinkID(paClient) + if err != nil { + fmt.Fprintf(os.Stderr, "No sink specified to load and failed to load default sink: %+v\n", err) + os.Exit(1) + } + sinkName = defaultSink + } + for i := range sinks { + if sinks[i].ID == sinkName { + sinks[i].checked = true + err := loadSupressor(&ctx, &device{}, &sinks[i]) if err != nil { fmt.Fprintf(os.Stderr, "Error loading PulseAudio Module: %+v\n", err) os.Exit(1) @@ -150,7 +189,7 @@ func main() { os.Exit(0) } } - fmt.Fprintf(os.Stderr, "PulseAudio source not found: %s\n", sourceName) + fmt.Fprintf(os.Stderr, "PulseAudio sink not found: %s\n", sinkName) os.Exit(1) } @@ -161,13 +200,19 @@ func main() { go paConnectionWatchdog(&ctx) - wnd := nucular.NewMasterWindowSize(0, "NoiseTorch", image.Point{550, 300}, func(w *nucular.Window) { + wnd := nucular.NewMasterWindowSize(0, appName, image.Point{600, 400}, func(w *nucular.Window) { updatefn(&ctx, w) }) + ctx.masterWindow = &wnd style := style.FromTheme(style.DarkTheme, 2.0) style.Font = font.DefaultFont(16, 1) wnd.SetStyle(style) + + //this is a disgusting hack that searches for the noisetorch window + //and then fixes up the WM_CLASS attribute so it displays + //properly in the taskbar + go fixWindowClass() wnd.Main() } @@ -190,29 +235,59 @@ func removeLib(file string) { log.Printf("Deleted temp librnnoise: %s\n", file) } -func getSources(client *pulseaudio.Client) []input { +func getSources(client *pulseaudio.Client) []device { sources, err := client.Sources() if err != nil { log.Printf("Couldn't fetch sources from pulseaudio\n") } - inputs := make([]input, 0) + outputs := make([]device, 0) for i := range sources { - if sources[i].Name == "nui_mic_remap" { + if strings.Contains(sources[i].Name, "nui_") { continue } log.Printf("Input %s, %+v\n", sources[i].Name, sources[i]) - var inp input + var inp device inp.ID = sources[i].Name inp.Name = sources[i].PropList["device.description"] inp.isMonitor = (sources[i].MonitorSourceIndex != 0xffffffff) + inp.rate = sources[i].SampleSpec.Rate //PA_SOURCE_DYNAMIC_LATENCY = 0x0040U inp.dynamicLatency = sources[i].Flags&uint32(0x0040) != 0 + outputs = append(outputs, inp) + } + + return outputs +} + +func getSinks(client *pulseaudio.Client) []device { + sources, err := client.Sinks() + if err != nil { + log.Printf("Couldn't fetch sources from pulseaudio\n") + } + + inputs := make([]device, 0) + for i := range sources { + if strings.Contains(sources[i].Name, "nui_") { + continue + } + + log.Printf("Output %s, %+v\n", sources[i].Name, sources[i]) + + var inp device + + inp.ID = sources[i].Name + inp.Name = sources[i].PropList["device.description"] + inp.rate = sources[i].SampleSpec.Rate + + // PA_SINK_DYNAMIC_LATENCY = 0x0080U + inp.dynamicLatency = sources[i].Flags&uint32(0x0080) != 0 + inputs = append(inputs, inp) } @@ -233,9 +308,10 @@ func paConnectionWatchdog(ctx *ntcontext) { } ctx.paClient = paClient - go updateNoiseSupressorLoaded(paClient, &ctx.noiseSupressorState) + go updateNoiseSupressorLoaded(ctx) - ctx.inputList = getSourcesWithPreSelectedInput(ctx) + ctx.inputList = preselectDevice(ctx, getSources(ctx.paClient), ctx.config.LastUsedInput, getDefaultSourceID) + ctx.outputList = preselectDevice(ctx, getSinks(paClient), ctx.config.LastUsedOutput, getDefaultSinkID) resetUI(ctx) @@ -243,32 +319,28 @@ func paConnectionWatchdog(ctx *ntcontext) { } } -func getSourcesWithPreSelectedInput(ctx *ntcontext) []input { - inputs := getSources(ctx.paClient) - preselectedInputID := &ctx.config.LastUsedInput - inputExists := false - if preselectedInputID != nil { - for _, input := range inputs { - inputExists = inputExists || input.ID == *preselectedInputID - } +func preselectDevice(ctx *ntcontext, devices []device, preselectID string, + fallbackFunc func(client *pulseaudio.Client) (string, error)) []device { + + deviceExists := false + for _, input := range devices { + deviceExists = deviceExists || input.ID == preselectID } - if !inputExists { - defaultSource, err := getDefaultSourceID(ctx.paClient) + if !deviceExists { + defaultDevice, err := fallbackFunc(ctx.paClient) if err != nil { - log.Printf("Failed to load default source: %+v\n", err) + log.Printf("Failed to load default device: %+v\n", err) } else { - preselectedInputID = &defaultSource + preselectID = defaultDevice } } - if preselectedInputID != nil { - for i := range inputs { - if inputs[i].ID == *preselectedInputID { - inputs[i].checked = true - } + for i := range devices { + if devices[i].ID == preselectID { + devices[i].checked = true } } - return inputs + return devices } func getDefaultSourceID(client *pulseaudio.Client) (string, error) { @@ -278,3 +350,43 @@ func getDefaultSourceID(client *pulseaudio.Client) (string, error) { } return server.DefaultSource, nil } + +func getDefaultSinkID(client *pulseaudio.Client) (string, error) { + server, err := client.ServerInfo() + if err != nil { + return "", err + } + return server.DefaultSink, nil +} + +//this is disgusting +func fixWindowClass() { + xu, err := xgbutil.NewConn() + defer xu.Conn().Close() + if err != nil { + log.Printf("Couldn't create XU xdg conn: %+v\n", err) + return + } + for i := 0; i < 100; i++ { + wnds, _ := ewmh.ClientListGet(xu) + for _, w := range wnds { + n, _ := ewmh.WmNameGet(xu, w) + if n == appName { + _, err := icccm.WmClassGet(xu, w) + //if we have *NO* WM_CLASS, then the above call errors. We *want* to make sure this errors + if err == nil { + continue + } + + class := icccm.WmClass{} + class.Class = appName + class.Instance = appName + icccm.WmClassSet(xu, w, &class) + return + } + + } + time.Sleep(100 * time.Millisecond) + } + +} diff --git a/module.go b/module.go index d39421c..e621dd4 100644 --- a/module.go +++ b/module.go @@ -4,7 +4,6 @@ import ( "fmt" "log" "strings" - "time" "github.com/lawl/pulseaudio" ) @@ -17,15 +16,15 @@ const ( // the ugly and (partially) repeated strings are unforunately difficult to avoid, as it's what pulse audio expects -func updateNoiseSupressorLoaded(c *pulseaudio.Client, b *int) { - +func updateNoiseSupressorLoaded(ctx *ntcontext) { + c := ctx.paClient upd, err := c.Updates() if err != nil { fmt.Printf("Error listening for updates: %v\n", err) } for { - *b = supressorState(c) + ctx.noiseSupressorState = supressorState(ctx) if !c.Connected() { break @@ -35,39 +34,82 @@ func updateNoiseSupressorLoaded(c *pulseaudio.Client, b *int) { } } -func supressorState(c *pulseaudio.Client) int { +func supressorState(ctx *ntcontext) int { //perform some checks to see if it looks like the noise supressor is loaded - _, nullsink, err := findModule(c, "module-null-sink", "sink_name=nui_mic_denoised_out") - if err != nil { - log.Printf("Couldn't fetch module list to check for module-null-sink: %v\n", err) - } - _, ladspasink, err := findModule(c, "module-ladspa-sink", "sink_name=nui_mic_raw_in sink_master=nui_mic_denoised_out") - if err != nil { - log.Printf("Couldn't fetch module list to check for module-ladspa-sink: %v\n", err) - } - _, loopback, err := findModule(c, "module-loopback", "sink=nui_mic_raw_in") - if err != nil { - log.Printf("Couldn't fetch module list to check for module-loopback: %v\n", err) + c := ctx.paClient + var inpLoaded, outLoaded, inputInc, outputInc bool + if ctx.config.FilterInput { + _, nullsink, err := findModule(c, "module-null-sink", "sink_name=nui_mic_denoised_out") + if err != nil { + log.Printf("Couldn't fetch module list to check for module-null-sink: %v\n", err) + } + _, ladspasink, err := findModule(c, "module-ladspa-sink", "sink_name=nui_mic_raw_in sink_master=nui_mic_denoised_out") + if err != nil { + log.Printf("Couldn't fetch module list to check for module-ladspa-sink: %v\n", err) + } + _, loopback, err := findModule(c, "module-loopback", "sink=nui_mic_raw_in") + if err != nil { + log.Printf("Couldn't fetch module list to check for module-loopback: %v\n", err) + } + _, remap, err := findModule(c, "module-remap-source", "master=nui_mic_denoised_out.monitor source_name=nui_mic_remap") + if err != nil { + log.Printf("Couldn't fetch module list to check for module-remap-source: %v\n", err) + } + + if nullsink && ladspasink && loopback && remap { + inpLoaded = true + } else if nullsink || ladspasink || loopback || remap { + inputInc = true + } + } else { + inpLoaded = true } - _, remap, err := findModule(c, "module-remap-source", "master=nui_mic_denoised_out.monitor source_name=nui_mic_remap") - if err != nil { - log.Printf("Couldn't fetch module list to check for module-remap-source: %v\n", err) + + if ctx.config.FilterOutput { + _, out, err := findModule(c, "module-null-sink", "sink_name=nui_out_out_sink") + if err != nil { + log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err) + } + _, lad, err := findModule(c, "module-ladspa-sink", "sink_name=nui_out_ladspa") + if err != nil { + log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err) + } + _, loop, err := findModule(c, "module-loopback", "source=nui_out_out_sink.monitor") + if err != nil { + log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err) + } + _, outin, err := findModule(c, "module-null-sink", "sink_name=nui_out_in_sink") + if err != nil { + log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err) + } + _, loop2, err := findModule(c, "module-loopback", "source=nui_out_in_sink.monitor") + if err != nil { + log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err) + } + + outLoaded = out && lad && loop && outin && loop2 + outputInc = out || lad || loop || outin || loop2 + } else { + outLoaded = true } - if nullsink && ladspasink && loopback && remap { + if (inpLoaded || !ctx.config.FilterInput) && (outLoaded || !ctx.config.FilterOutput) && !inputInc { return loaded } - if nullsink || ladspasink || loopback || remap { + + if (inpLoaded && ctx.config.FilterInput) || (outLoaded && ctx.config.FilterOutput) || inputInc || outputInc { return inconsistent } + return unloaded } -func loadSupressor(ctx *ntcontext, inp input) error { - c := ctx.paClient +func loadSupressor(ctx *ntcontext, inp *device, out *device) error { log.Printf("Querying pulse rlimit\n") + c := ctx.paClient + pid, err := getPulsePid() if err != nil { return err @@ -93,45 +135,80 @@ func loadSupressor(ctx *ntcontext, inp input) error { } log.Printf("Rlimit: %+v\n", newLim) - log.Printf("Loading supressor\n") - idx, err := c.LoadModule("module-null-sink", "sink_name=nui_mic_denoised_out rate=48000") - if err != nil { - return err - } - log.Printf("Loaded null sink as idx: %d\n", idx) + if inp.checked { + log.Printf("Loading supressor\n") + idx, err := c.LoadModule("module-null-sink", "sink_name=nui_mic_denoised_out rate=48000") + if err != nil { + return err + } + log.Printf("Loaded null sink as idx: %d\n", idx) - time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast + //time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast - idx, err = c.LoadModule("module-ladspa-sink", - fmt.Sprintf("sink_name=nui_mic_raw_in sink_master=nui_mic_denoised_out "+ - "label=noise_suppressor_mono plugin=%s control=%d", ctx.librnnoise, ctx.config.Threshold)) - if err != nil { - return err - } - log.Printf("Loaded ladspa sink as idx: %d\n", idx) + idx, err = c.LoadModule("module-ladspa-sink", + fmt.Sprintf("sink_name=nui_mic_raw_in sink_master=nui_mic_denoised_out "+ + "label=noise_suppressor_mono plugin=%s control=%d", ctx.librnnoise, ctx.config.Threshold)) + if err != nil { + return err + } + log.Printf("Loaded ladspa sink as idx: %d\n", idx) - time.Sleep(time.Millisecond * 1000) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast + //time.Sleep(time.Millisecond * 1000) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast - idx, err = c.LoadModule("module-loopback", - fmt.Sprintf("source=%s sink=nui_mic_raw_in channels=1 latency_msec=1 source_dont_move=true sink_dont_move=true", inp.ID)) - if err != nil { - return err + idx, err = c.LoadModule("module-loopback", + fmt.Sprintf("source=%s sink=nui_mic_raw_in channels=1 latency_msec=1 source_dont_move=true sink_dont_move=true", inp.ID)) + if err != nil { + return err + } + log.Printf("Loaded loopback as idx: %d\n", idx) + + //time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast + + idx, err = c.LoadModule("module-remap-source", `master=nui_mic_denoised_out.monitor `+ + `source_name=nui_mic_remap source_properties="device.description='NoiseTorch Microphone'"`) + if err != nil { + return err + } + log.Printf("Loaded remap source as idx: %d\n", idx) } - log.Printf("Loaded loopback as idx: %d\n", idx) - time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast + if out.checked { + + _, err := c.LoadModule("module-null-sink", `sink_name=nui_out_out_sink`) + if err != nil { + return err + } + + _, err = c.LoadModule("module-null-sink", `sink_name=nui_out_in_sink sink_properties="device.description='NoiseTorch Headphones'"`) + if err != nil { + return err + } + + _, err = c.LoadModule("module-ladspa-sink", fmt.Sprintf(`sink_name=nui_out_ladspa sink_master=nui_out_out_sink `+ + `label=noise_suppressor_mono channels=1 plugin=%s control=%d rate=%d`, + /*out.ID,*/ ctx.librnnoise, ctx.config.Threshold /*out.rate*/, 48000)) + if err != nil { + return err + } + + _, err = c.LoadModule("module-loopback", + fmt.Sprintf("source=nui_out_out_sink.monitor sink=%s channels=2 latency_msec=50 source_dont_move=true sink_dont_move=true", out.ID)) + if err != nil { + return err + } + + _, err = c.LoadModule("module-loopback", + fmt.Sprintf("source=nui_out_in_sink.monitor sink=nui_out_ladspa channels=1 latency_msec=50 source_dont_move=true sink_dont_move=true")) + if err != nil { + return err + } - idx, err = c.LoadModule("module-remap-source", `master=nui_mic_denoised_out.monitor `+ - `source_name=nui_mic_remap source_properties="device.description='NoiseTorch Microphone'"`) - if err != nil { - return err } - log.Printf("Loaded ladspa sink as idx: %d\n", idx) return nil } -func unloadSupressor(c *pulseaudio.Client) error { +func unloadSupressor(ctx *ntcontext) error { log.Printf("Unloading pulseaudio modules\n") // we ignore errors here on purpose, since NT didn't use to do this for unloading anyways @@ -146,6 +223,7 @@ func unloadSupressor(c *pulseaudio.Client) error { } log.Printf("Searching for null-sink\n") + c := ctx.paClient m, found, err := findModule(c, "module-null-sink", "sink_name=nui_mic_denoised_out") if err != nil { return err @@ -154,7 +232,7 @@ func unloadSupressor(c *pulseaudio.Client) error { log.Printf("Found null-sink at id [%d], sending unload command\n", m.Index) c.UnloadModule(m.Index) } - time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast + //time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast log.Printf("Searching for ladspa-sink\n") m, found, err = findModule(c, "module-ladspa-sink", "sink_name=nui_mic_raw_in sink_master=nui_mic_denoised_out") if err != nil { @@ -164,7 +242,7 @@ func unloadSupressor(c *pulseaudio.Client) error { log.Printf("Found ladspa-sink at id [%d], sending unload command\n", m.Index) c.UnloadModule(m.Index) } - time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast + //time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast log.Printf("Searching for loopback\n") m, found, err = findModule(c, "module-loopback", "sink=nui_mic_raw_in") if err != nil { @@ -174,7 +252,7 @@ func unloadSupressor(c *pulseaudio.Client) error { log.Printf("Found loopback at id [%d], sending unload command\n", m.Index) c.UnloadModule(m.Index) } - time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast + //time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast log.Printf("Searching for remap-source\n") m, found, err = findModule(c, "module-remap-source", "master=nui_mic_denoised_out.monitor source_name=nui_mic_remap") if err != nil { @@ -184,6 +262,62 @@ func unloadSupressor(c *pulseaudio.Client) error { log.Printf("Found remap source at id [%d], sending unload command\n", m.Index) c.UnloadModule(m.Index) } + + //time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast + log.Printf("Searching for output module-null-sink\n") + m, found, err = findModule(c, "module-null-sink", "sink_name=nui_out_out_sink") + if err != nil { + return err + } + if found { + log.Printf("Found output null sink at id [%d], sending unload command\n", m.Index) + c.UnloadModule(m.Index) + } + + //time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast + log.Printf("Searching for output module-null-sink\n") + m, found, err = findModule(c, "module-null-sink", "sink_name=nui_out_in_sink") + if err != nil { + return err + } + if found { + log.Printf("Found output null sink at id [%d], sending unload command\n", m.Index) + c.UnloadModule(m.Index) + } + + //time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast + log.Printf("Searching for output module-ladspa-sink\n") + m, found, err = findModule(c, "module-ladspa-sink", "sink_name=nui_out_ladspa") + if err != nil { + return err + } + if found { + log.Printf("Found output ladspa sink at id [%d], sending unload command\n", m.Index) + c.UnloadModule(m.Index) + } + + //time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast + log.Printf("Searching for output module-loopback\n") + m, found, err = findModule(c, "module-loopback", "source=nui_out_out_sink.monitor") + if err != nil { + return err + } + if found { + log.Printf("Found output loopback at id [%d], sending unload command\n", m.Index) + c.UnloadModule(m.Index) + } + + //time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast + log.Printf("Searching for output module-loopback\n") + m, found, err = findModule(c, "module-loopback", "source=nui_out_in_sink.monitor") + if err != nil { + return err + } + if found { + log.Printf("Found output loopback at id [%d], sending unload command\n", m.Index) + c.UnloadModule(m.Index) + } + return nil } diff --git a/ui.go b/ui.go index d4c51c2..0343a6d 100644 --- a/ui.go +++ b/ui.go @@ -17,12 +17,12 @@ import ( ) type ntcontext struct { - inputList []input + inputList []device + outputList []device noiseSupressorState int paClient *pulseaudio.Client librnnoise string sourceListColdWidthIndex int - useBuiltinRNNoise bool config *config loadingScreen bool licenseScreen bool @@ -137,9 +137,22 @@ func updatefn(ctx *ntcontext, w *nucular.Window) { w.LabelColored("Reloading NoiseTorch is required to apply these changes.", "LC", orange) } + w.Row(15).Dynamic(2) + if w.CheckboxText("Filter Microphone", &ctx.config.FilterInput) { + ctx.sourceListColdWidthIndex++ //recompute the with because of new elements + go writeConfig(ctx.config) + go (func() { ctx.noiseSupressorState = supressorState(ctx) })() + } + + if w.CheckboxText("Filter Headphones", &ctx.config.FilterOutput) { + ctx.sourceListColdWidthIndex++ //recompute the with because of new elements + go writeConfig(ctx.config) + go (func() { ctx.noiseSupressorState = supressorState(ctx) })() + } + w.TreePop() } - if w.TreePush(nucular.TreeTab, "Select Device", true) { + if ctx.config.FilterInput && w.TreePush(nucular.TreeTab, "Select Microphone", true) { w.Row(15).Dynamic(1) w.Label("Select an input device below:", "LC") @@ -163,69 +176,125 @@ func updatefn(ctx *ntcontext, w *nucular.Window) { } } - w.Row(30).Dynamic(1) - w.Spacing(1) + w.TreePop() + } + + if ctx.config.FilterOutput && w.TreePush(nucular.TreeTab, "Select Headphones", true) { + if ctx.config.GuiltTripped { + w.Row(15).Dynamic(1) + w.Label("Select an output device below:", "LC") + + for i := range ctx.outputList { + el := &ctx.outputList[i] + + if el.isMonitor && !ctx.config.DisplayMonitorSources { + continue + } + w.Row(15).Static() + w.LayoutFitWidth(0, 0) + if w.CheckboxText("", &el.checked) { + ensureOnlyOneInputSelected(&ctx.outputList, el) + } + + w.LayoutFitWidth(ctx.sourceListColdWidthIndex, 0) + if el.dynamicLatency { + w.Label(el.Name, "LC") + } else { + w.LabelColored("(incompatible?) "+el.Name, "LC", orange) + } - w.Row(25).Dynamic(2) - if ctx.noiseSupressorState != unloaded { - if w.ButtonText("Unload NoiseTorch") { - ctx.loadingScreen = true - ctx.reloadRequired = false - go func() { // don't block the UI thread, just display a working screen so user can't run multiple loads/unloads - if err := unloadSupressor(ctx.paClient); err != nil { - log.Println(err) - } - //wait until PA reports it has actually loaded it, timeout at 10s - for i := 0; i < 20; i++ { - if supressorState(ctx.paClient) != unloaded { - time.Sleep(time.Millisecond * 500) - } - } - ctx.loadingScreen = false - (*ctx.masterWindow).Changed() - }() } } else { - w.Spacing(1) - } - txt := "Load NoiseTorch" - if ctx.noiseSupressorState == loaded { - txt = "Reload NoiseTorch" + w.Row(15).Dynamic(1) + w.Label("This feature is only for patrons.", "LC") + w.Row(15).Dynamic(1) + w.Label("You can still use it eitherway, but you are legally required to feel bad.", "LC") + w.Row(25).Dynamic(2) + if w.ButtonText("Become a patron") { + exec.Command("xdg-open", "https://patreon.com/lawl").Run() + ctx.config.GuiltTripped = true + go writeConfig(ctx.config) + } + + if w.ButtonText("Feel bad") { + ctx.config.GuiltTripped = true + go writeConfig(ctx.config) + } } - if inp, ok := inputSelection(ctx); ok && ctx.noiseSupressorState != inconsistent { - if w.ButtonText(txt) { - ctx.loadingScreen = true - ctx.reloadRequired = false - go func() { // don't block the UI thread, just display a working screen so user can't run multiple loads/unloads - if ctx.noiseSupressorState == loaded { - if err := unloadSupressor(ctx.paClient); err != nil { - log.Println(err) - } + + w.TreePop() + } + + w.Row(15).Dynamic(1) + w.Spacing(1) + + w.Row(25).Dynamic(2) + if ctx.noiseSupressorState != unloaded { + if w.ButtonText("Unload NoiseTorch") { + ctx.loadingScreen = true + ctx.reloadRequired = false + go func() { // don't block the UI thread, just display a working screen so user can't run multiple loads/unloads + if err := unloadSupressor(ctx); err != nil { + log.Println(err) + } + //wait until PA reports it has actually loaded it, timeout at 10s + for i := 0; i < 20; i++ { + if supressorState(ctx) != unloaded { + time.Sleep(time.Millisecond * 500) } - if err := loadSupressor(ctx, inp); err != nil { + } + ctx.loadingScreen = false + (*ctx.masterWindow).Changed() + }() + } + } else { + w.Spacing(1) + } + txt := "Load NoiseTorch" + if ctx.noiseSupressorState == loaded { + txt = "Reload NoiseTorch" + } + + inp, inpOk := inputSelection(ctx) + out, outOk := outputSelection(ctx) + if (!ctx.config.FilterInput || (ctx.config.FilterInput && inpOk)) && + (!ctx.config.FilterOutput || (ctx.config.FilterOutput && outOk)) && + (ctx.config.FilterInput || ctx.config.FilterOutput) && + ((ctx.config.FilterOutput && ctx.config.GuiltTripped) || !ctx.config.FilterOutput) && + ctx.noiseSupressorState != inconsistent { + if w.ButtonText(txt) { + ctx.loadingScreen = true + ctx.reloadRequired = false + go func() { // don't block the UI thread, just display a working screen so user can't run multiple loads/unloads + if ctx.noiseSupressorState == loaded { + if err := unloadSupressor(ctx); err != nil { log.Println(err) } - - //wait until PA reports it has actually loaded it, timeout at 10s - for i := 0; i < 20; i++ { - if supressorState(ctx.paClient) != loaded { - time.Sleep(time.Millisecond * 500) - } + } + if err := loadSupressor(ctx, &inp, &out); err != nil { + log.Println(err) + } + + //wait until PA reports it has actually loaded it, timeout at 10s + for i := 0; i < 20; i++ { + if supressorState(ctx) != loaded { + time.Sleep(time.Millisecond * 500) } - ctx.config.LastUsedInput = inp.ID - go writeConfig(ctx.config) - ctx.loadingScreen = false - (*ctx.masterWindow).Changed() - }() - } - } else { - w.Spacing(1) + } + ctx.config.LastUsedInput = inp.ID + ctx.config.LastUsedOutput = out.ID + go writeConfig(ctx.config) + ctx.loadingScreen = false + (*ctx.masterWindow).Changed() + }() } - w.TreePop() + } else { + w.Spacing(1) } + } -func ensureOnlyOneInputSelected(inps *[]input, current *input) { +func ensureOnlyOneInputSelected(inps *[]device, current *device) { if current.checked != true { return } @@ -236,13 +305,30 @@ func ensureOnlyOneInputSelected(inps *[]input, current *input) { current.checked = true } -func inputSelection(ctx *ntcontext) (input, bool) { +func inputSelection(ctx *ntcontext) (device, bool) { + if !ctx.config.FilterInput { + return device{}, false + } + for _, in := range ctx.inputList { if in.checked { return in, true } } - return input{}, false + return device{}, false +} + +func outputSelection(ctx *ntcontext) (device, bool) { + if !ctx.config.FilterOutput { + return device{}, false + } + + for _, out := range ctx.outputList { + if out.checked { + return out, true + } + } + return device{}, false } func loadingScreen(ctx *ntcontext, w *nucular.Window) { diff --git a/update_noop.go b/update_noop.go index bdd7698..fc1685c 100644 --- a/update_noop.go +++ b/update_noop.go @@ -1,9 +1,5 @@ -// +build !release - package main -import "errors" - type updateui struct { serverVersion string available bool @@ -12,17 +8,9 @@ type updateui struct { } func updateCheck(ctx *ntcontext) { - + // noop for non-release versions } func update(ctx *ntcontext) { - -} - -func fetchFile(file string) ([]byte, error) { - return make([]byte, 0), errors.New("Disabled by build flags") -} - -func publickey() []byte { - return make([]byte, 0) + // noop for non-release versions } diff --git a/vendor/github.com/BurntSushi/xgb/shape/shape.go b/vendor/github.com/BurntSushi/xgb/shape/shape.go new file mode 100644 index 0000000..7069f7e --- /dev/null +++ b/vendor/github.com/BurntSushi/xgb/shape/shape.go @@ -0,0 +1,1025 @@ +// Package shape is the X client API for the SHAPE extension. +package shape + +// This file is automatically generated from shape.xml. Edit at your peril! + +import ( + "github.com/BurntSushi/xgb" + + "github.com/BurntSushi/xgb/xproto" +) + +// Init must be called before using the SHAPE extension. +func Init(c *xgb.Conn) error { + reply, err := xproto.QueryExtension(c, 5, "SHAPE").Reply() + switch { + case err != nil: + return err + case !reply.Present: + return xgb.Errorf("No extension named SHAPE could be found on on the server.") + } + + c.ExtLock.Lock() + c.Extensions["SHAPE"] = reply.MajorOpcode + c.ExtLock.Unlock() + for evNum, fun := range xgb.NewExtEventFuncs["SHAPE"] { + xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun + } + for errNum, fun := range xgb.NewExtErrorFuncs["SHAPE"] { + xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun + } + return nil +} + +func init() { + xgb.NewExtEventFuncs["SHAPE"] = make(map[int]xgb.NewEventFun) + xgb.NewExtErrorFuncs["SHAPE"] = make(map[int]xgb.NewErrorFun) +} + +type Kind byte + +// Notify is the event number for a NotifyEvent. +const Notify = 0 + +type NotifyEvent struct { + Sequence uint16 + ShapeKind Kind + AffectedWindow xproto.Window + ExtentsX int16 + ExtentsY int16 + ExtentsWidth uint16 + ExtentsHeight uint16 + ServerTime xproto.Timestamp + Shaped bool + // padding: 11 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.ShapeKind = Kind(buf[b]) + b += 1 + + v.Sequence = xgb.Get16(buf[b:]) + b += 2 + + v.AffectedWindow = xproto.Window(xgb.Get32(buf[b:])) + b += 4 + + v.ExtentsX = int16(xgb.Get16(buf[b:])) + b += 2 + + v.ExtentsY = int16(xgb.Get16(buf[b:])) + b += 2 + + v.ExtentsWidth = xgb.Get16(buf[b:]) + b += 2 + + v.ExtentsHeight = xgb.Get16(buf[b:]) + b += 2 + + v.ServerTime = xproto.Timestamp(xgb.Get32(buf[b:])) + b += 4 + + if buf[b] == 1 { + v.Shaped = true + } else { + v.Shaped = false + } + b += 1 + + b += 11 // 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] = byte(v.ShapeKind) + b += 1 + + b += 2 // skip sequence number + + xgb.Put32(buf[b:], uint32(v.AffectedWindow)) + b += 4 + + xgb.Put16(buf[b:], uint16(v.ExtentsX)) + b += 2 + + xgb.Put16(buf[b:], uint16(v.ExtentsY)) + b += 2 + + xgb.Put16(buf[b:], v.ExtentsWidth) + b += 2 + + xgb.Put16(buf[b:], v.ExtentsHeight) + b += 2 + + xgb.Put32(buf[b:], uint32(v.ServerTime)) + b += 4 + + if v.Shaped { + buf[b] = 1 + } else { + buf[b] = 0 + } + b += 1 + + b += 11 // 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, 9) + fieldVals = append(fieldVals, xgb.Sprintf("Sequence: %d", v.Sequence)) + fieldVals = append(fieldVals, xgb.Sprintf("ShapeKind: %d", v.ShapeKind)) + fieldVals = append(fieldVals, xgb.Sprintf("AffectedWindow: %d", v.AffectedWindow)) + fieldVals = append(fieldVals, xgb.Sprintf("ExtentsX: %d", v.ExtentsX)) + fieldVals = append(fieldVals, xgb.Sprintf("ExtentsY: %d", v.ExtentsY)) + fieldVals = append(fieldVals, xgb.Sprintf("ExtentsWidth: %d", v.ExtentsWidth)) + fieldVals = append(fieldVals, xgb.Sprintf("ExtentsHeight: %d", v.ExtentsHeight)) + fieldVals = append(fieldVals, xgb.Sprintf("ServerTime: %d", v.ServerTime)) + fieldVals = append(fieldVals, xgb.Sprintf("Shaped: %t", v.Shaped)) + return "Notify {" + xgb.StringsJoin(fieldVals, ", ") + "}" +} + +func init() { + xgb.NewExtEventFuncs["SHAPE"][0] = NotifyEventNew +} + +type Op byte + +const ( + SkBounding = 0 + SkClip = 1 + SkInput = 2 +) + +const ( + SoSet = 0 + SoUnion = 1 + SoIntersect = 2 + SoSubtract = 3 + SoInvert = 4 +) + +// 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' + +// CombineCookie is a cookie used only for Combine requests. +type CombineCookie struct { + *xgb.Cookie +} + +// Combine sends an unchecked request. +// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. +func Combine(c *xgb.Conn, Operation Op, DestinationKind Kind, SourceKind Kind, DestinationWindow xproto.Window, XOffset int16, YOffset int16, SourceWindow xproto.Window) CombineCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["SHAPE"]; !ok { + panic("Cannot issue request 'Combine' using the uninitialized extension 'SHAPE'. shape.Init(connObj) must be called first.") + } + cookie := c.NewCookie(false, false) + c.NewRequest(combineRequest(c, Operation, DestinationKind, SourceKind, DestinationWindow, XOffset, YOffset, SourceWindow), cookie) + return CombineCookie{cookie} +} + +// CombineChecked sends a checked request. +// If an error occurs, it can be retrieved using CombineCookie.Check() +func CombineChecked(c *xgb.Conn, Operation Op, DestinationKind Kind, SourceKind Kind, DestinationWindow xproto.Window, XOffset int16, YOffset int16, SourceWindow xproto.Window) CombineCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["SHAPE"]; !ok { + panic("Cannot issue request 'Combine' using the uninitialized extension 'SHAPE'. shape.Init(connObj) must be called first.") + } + cookie := c.NewCookie(true, false) + c.NewRequest(combineRequest(c, Operation, DestinationKind, SourceKind, DestinationWindow, XOffset, YOffset, SourceWindow), cookie) + return CombineCookie{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 CombineCookie) Check() error { + return cook.Cookie.Check() +} + +// Write request to wire for Combine +// combineRequest writes a Combine request to a byte slice. +func combineRequest(c *xgb.Conn, Operation Op, DestinationKind Kind, SourceKind Kind, DestinationWindow xproto.Window, XOffset int16, YOffset int16, SourceWindow xproto.Window) []byte { + size := 20 + b := 0 + buf := make([]byte, size) + + c.ExtLock.RLock() + buf[b] = c.Extensions["SHAPE"] + 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 + + buf[b] = byte(Operation) + b += 1 + + buf[b] = byte(DestinationKind) + b += 1 + + buf[b] = byte(SourceKind) + b += 1 + + b += 1 // padding + + xgb.Put32(buf[b:], uint32(DestinationWindow)) + b += 4 + + xgb.Put16(buf[b:], uint16(XOffset)) + b += 2 + + xgb.Put16(buf[b:], uint16(YOffset)) + b += 2 + + xgb.Put32(buf[b:], uint32(SourceWindow)) + b += 4 + + return buf +} + +// GetRectanglesCookie is a cookie used only for GetRectangles requests. +type GetRectanglesCookie struct { + *xgb.Cookie +} + +// GetRectangles sends a checked request. +// If an error occurs, it will be returned with the reply by calling GetRectanglesCookie.Reply() +func GetRectangles(c *xgb.Conn, Window xproto.Window, SourceKind Kind) GetRectanglesCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["SHAPE"]; !ok { + panic("Cannot issue request 'GetRectangles' using the uninitialized extension 'SHAPE'. shape.Init(connObj) must be called first.") + } + cookie := c.NewCookie(true, true) + c.NewRequest(getRectanglesRequest(c, Window, SourceKind), cookie) + return GetRectanglesCookie{cookie} +} + +// GetRectanglesUnchecked sends an unchecked request. +// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. +func GetRectanglesUnchecked(c *xgb.Conn, Window xproto.Window, SourceKind Kind) GetRectanglesCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["SHAPE"]; !ok { + panic("Cannot issue request 'GetRectangles' using the uninitialized extension 'SHAPE'. shape.Init(connObj) must be called first.") + } + cookie := c.NewCookie(false, true) + c.NewRequest(getRectanglesRequest(c, Window, SourceKind), cookie) + return GetRectanglesCookie{cookie} +} + +// GetRectanglesReply represents the data returned from a GetRectangles request. +type GetRectanglesReply struct { + Sequence uint16 // sequence number of the request for this reply + Length uint32 // number of bytes in this reply + Ordering byte + RectanglesLen uint32 + // padding: 20 bytes + Rectangles []xproto.Rectangle // size: xgb.Pad((int(RectanglesLen) * 8)) +} + +// Reply blocks and returns the reply data for a GetRectangles request. +func (cook GetRectanglesCookie) Reply() (*GetRectanglesReply, error) { + buf, err := cook.Cookie.Reply() + if err != nil { + return nil, err + } + if buf == nil { + return nil, nil + } + return getRectanglesReply(buf), nil +} + +// getRectanglesReply reads a byte slice into a GetRectanglesReply value. +func getRectanglesReply(buf []byte) *GetRectanglesReply { + v := new(GetRectanglesReply) + b := 1 // skip reply determinant + + v.Ordering = buf[b] + b += 1 + + v.Sequence = xgb.Get16(buf[b:]) + b += 2 + + v.Length = xgb.Get32(buf[b:]) // 4-byte units + b += 4 + + v.RectanglesLen = xgb.Get32(buf[b:]) + b += 4 + + b += 20 // padding + + v.Rectangles = make([]xproto.Rectangle, v.RectanglesLen) + b += xproto.RectangleReadList(buf[b:], v.Rectangles) + + return v +} + +// Write request to wire for GetRectangles +// getRectanglesRequest writes a GetRectangles request to a byte slice. +func getRectanglesRequest(c *xgb.Conn, Window xproto.Window, SourceKind Kind) []byte { + size := 12 + b := 0 + buf := make([]byte, size) + + c.ExtLock.RLock() + buf[b] = c.Extensions["SHAPE"] + 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 + + buf[b] = byte(SourceKind) + b += 1 + + b += 3 // padding + + return buf +} + +// InputSelectedCookie is a cookie used only for InputSelected requests. +type InputSelectedCookie struct { + *xgb.Cookie +} + +// InputSelected sends a checked request. +// If an error occurs, it will be returned with the reply by calling InputSelectedCookie.Reply() +func InputSelected(c *xgb.Conn, DestinationWindow xproto.Window) InputSelectedCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["SHAPE"]; !ok { + panic("Cannot issue request 'InputSelected' using the uninitialized extension 'SHAPE'. shape.Init(connObj) must be called first.") + } + cookie := c.NewCookie(true, true) + c.NewRequest(inputSelectedRequest(c, DestinationWindow), cookie) + return InputSelectedCookie{cookie} +} + +// InputSelectedUnchecked sends an unchecked request. +// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. +func InputSelectedUnchecked(c *xgb.Conn, DestinationWindow xproto.Window) InputSelectedCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["SHAPE"]; !ok { + panic("Cannot issue request 'InputSelected' using the uninitialized extension 'SHAPE'. shape.Init(connObj) must be called first.") + } + cookie := c.NewCookie(false, true) + c.NewRequest(inputSelectedRequest(c, DestinationWindow), cookie) + return InputSelectedCookie{cookie} +} + +// InputSelectedReply represents the data returned from a InputSelected request. +type InputSelectedReply struct { + Sequence uint16 // sequence number of the request for this reply + Length uint32 // number of bytes in this reply + Enabled bool +} + +// Reply blocks and returns the reply data for a InputSelected request. +func (cook InputSelectedCookie) Reply() (*InputSelectedReply, error) { + buf, err := cook.Cookie.Reply() + if err != nil { + return nil, err + } + if buf == nil { + return nil, nil + } + return inputSelectedReply(buf), nil +} + +// inputSelectedReply reads a byte slice into a InputSelectedReply value. +func inputSelectedReply(buf []byte) *InputSelectedReply { + v := new(InputSelectedReply) + b := 1 // skip reply determinant + + if buf[b] == 1 { + v.Enabled = true + } else { + v.Enabled = false + } + b += 1 + + v.Sequence = xgb.Get16(buf[b:]) + b += 2 + + v.Length = xgb.Get32(buf[b:]) // 4-byte units + b += 4 + + return v +} + +// Write request to wire for InputSelected +// inputSelectedRequest writes a InputSelected request to a byte slice. +func inputSelectedRequest(c *xgb.Conn, DestinationWindow xproto.Window) []byte { + size := 8 + b := 0 + buf := make([]byte, size) + + c.ExtLock.RLock() + buf[b] = c.Extensions["SHAPE"] + 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(DestinationWindow)) + b += 4 + + return buf +} + +// MaskCookie is a cookie used only for Mask requests. +type MaskCookie struct { + *xgb.Cookie +} + +// Mask sends an unchecked request. +// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. +func Mask(c *xgb.Conn, Operation Op, DestinationKind Kind, DestinationWindow xproto.Window, XOffset int16, YOffset int16, SourceBitmap xproto.Pixmap) MaskCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["SHAPE"]; !ok { + panic("Cannot issue request 'Mask' using the uninitialized extension 'SHAPE'. shape.Init(connObj) must be called first.") + } + cookie := c.NewCookie(false, false) + c.NewRequest(maskRequest(c, Operation, DestinationKind, DestinationWindow, XOffset, YOffset, SourceBitmap), cookie) + return MaskCookie{cookie} +} + +// MaskChecked sends a checked request. +// If an error occurs, it can be retrieved using MaskCookie.Check() +func MaskChecked(c *xgb.Conn, Operation Op, DestinationKind Kind, DestinationWindow xproto.Window, XOffset int16, YOffset int16, SourceBitmap xproto.Pixmap) MaskCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["SHAPE"]; !ok { + panic("Cannot issue request 'Mask' using the uninitialized extension 'SHAPE'. shape.Init(connObj) must be called first.") + } + cookie := c.NewCookie(true, false) + c.NewRequest(maskRequest(c, Operation, DestinationKind, DestinationWindow, XOffset, YOffset, SourceBitmap), cookie) + return MaskCookie{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 MaskCookie) Check() error { + return cook.Cookie.Check() +} + +// Write request to wire for Mask +// maskRequest writes a Mask request to a byte slice. +func maskRequest(c *xgb.Conn, Operation Op, DestinationKind Kind, DestinationWindow xproto.Window, XOffset int16, YOffset int16, SourceBitmap xproto.Pixmap) []byte { + size := 20 + b := 0 + buf := make([]byte, size) + + c.ExtLock.RLock() + buf[b] = c.Extensions["SHAPE"] + 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 + + buf[b] = byte(Operation) + b += 1 + + buf[b] = byte(DestinationKind) + b += 1 + + b += 2 // padding + + xgb.Put32(buf[b:], uint32(DestinationWindow)) + b += 4 + + xgb.Put16(buf[b:], uint16(XOffset)) + b += 2 + + xgb.Put16(buf[b:], uint16(YOffset)) + b += 2 + + xgb.Put32(buf[b:], uint32(SourceBitmap)) + b += 4 + + return buf +} + +// OffsetCookie is a cookie used only for Offset requests. +type OffsetCookie struct { + *xgb.Cookie +} + +// Offset sends an unchecked request. +// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. +func Offset(c *xgb.Conn, DestinationKind Kind, DestinationWindow xproto.Window, XOffset int16, YOffset int16) OffsetCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["SHAPE"]; !ok { + panic("Cannot issue request 'Offset' using the uninitialized extension 'SHAPE'. shape.Init(connObj) must be called first.") + } + cookie := c.NewCookie(false, false) + c.NewRequest(offsetRequest(c, DestinationKind, DestinationWindow, XOffset, YOffset), cookie) + return OffsetCookie{cookie} +} + +// OffsetChecked sends a checked request. +// If an error occurs, it can be retrieved using OffsetCookie.Check() +func OffsetChecked(c *xgb.Conn, DestinationKind Kind, DestinationWindow xproto.Window, XOffset int16, YOffset int16) OffsetCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["SHAPE"]; !ok { + panic("Cannot issue request 'Offset' using the uninitialized extension 'SHAPE'. shape.Init(connObj) must be called first.") + } + cookie := c.NewCookie(true, false) + c.NewRequest(offsetRequest(c, DestinationKind, DestinationWindow, XOffset, YOffset), cookie) + return OffsetCookie{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 OffsetCookie) Check() error { + return cook.Cookie.Check() +} + +// Write request to wire for Offset +// offsetRequest writes a Offset request to a byte slice. +func offsetRequest(c *xgb.Conn, DestinationKind Kind, DestinationWindow xproto.Window, XOffset int16, YOffset int16) []byte { + size := 16 + b := 0 + buf := make([]byte, size) + + c.ExtLock.RLock() + buf[b] = c.Extensions["SHAPE"] + 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 + + buf[b] = byte(DestinationKind) + b += 1 + + b += 3 // padding + + xgb.Put32(buf[b:], uint32(DestinationWindow)) + b += 4 + + xgb.Put16(buf[b:], uint16(XOffset)) + b += 2 + + xgb.Put16(buf[b:], uint16(YOffset)) + b += 2 + + return buf +} + +// QueryExtentsCookie is a cookie used only for QueryExtents requests. +type QueryExtentsCookie struct { + *xgb.Cookie +} + +// QueryExtents sends a checked request. +// If an error occurs, it will be returned with the reply by calling QueryExtentsCookie.Reply() +func QueryExtents(c *xgb.Conn, DestinationWindow xproto.Window) QueryExtentsCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["SHAPE"]; !ok { + panic("Cannot issue request 'QueryExtents' using the uninitialized extension 'SHAPE'. shape.Init(connObj) must be called first.") + } + cookie := c.NewCookie(true, true) + c.NewRequest(queryExtentsRequest(c, DestinationWindow), cookie) + return QueryExtentsCookie{cookie} +} + +// QueryExtentsUnchecked sends an unchecked request. +// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. +func QueryExtentsUnchecked(c *xgb.Conn, DestinationWindow xproto.Window) QueryExtentsCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["SHAPE"]; !ok { + panic("Cannot issue request 'QueryExtents' using the uninitialized extension 'SHAPE'. shape.Init(connObj) must be called first.") + } + cookie := c.NewCookie(false, true) + c.NewRequest(queryExtentsRequest(c, DestinationWindow), cookie) + return QueryExtentsCookie{cookie} +} + +// QueryExtentsReply represents the data returned from a QueryExtents request. +type QueryExtentsReply struct { + Sequence uint16 // sequence number of the request for this reply + Length uint32 // number of bytes in this reply + // padding: 1 bytes + BoundingShaped bool + ClipShaped bool + // padding: 2 bytes + BoundingShapeExtentsX int16 + BoundingShapeExtentsY int16 + BoundingShapeExtentsWidth uint16 + BoundingShapeExtentsHeight uint16 + ClipShapeExtentsX int16 + ClipShapeExtentsY int16 + ClipShapeExtentsWidth uint16 + ClipShapeExtentsHeight uint16 +} + +// Reply blocks and returns the reply data for a QueryExtents request. +func (cook QueryExtentsCookie) Reply() (*QueryExtentsReply, error) { + buf, err := cook.Cookie.Reply() + if err != nil { + return nil, err + } + if buf == nil { + return nil, nil + } + return queryExtentsReply(buf), nil +} + +// queryExtentsReply reads a byte slice into a QueryExtentsReply value. +func queryExtentsReply(buf []byte) *QueryExtentsReply { + v := new(QueryExtentsReply) + 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.BoundingShaped = true + } else { + v.BoundingShaped = false + } + b += 1 + + if buf[b] == 1 { + v.ClipShaped = true + } else { + v.ClipShaped = false + } + b += 1 + + b += 2 // padding + + v.BoundingShapeExtentsX = int16(xgb.Get16(buf[b:])) + b += 2 + + v.BoundingShapeExtentsY = int16(xgb.Get16(buf[b:])) + b += 2 + + v.BoundingShapeExtentsWidth = xgb.Get16(buf[b:]) + b += 2 + + v.BoundingShapeExtentsHeight = xgb.Get16(buf[b:]) + b += 2 + + v.ClipShapeExtentsX = int16(xgb.Get16(buf[b:])) + b += 2 + + v.ClipShapeExtentsY = int16(xgb.Get16(buf[b:])) + b += 2 + + v.ClipShapeExtentsWidth = xgb.Get16(buf[b:]) + b += 2 + + v.ClipShapeExtentsHeight = xgb.Get16(buf[b:]) + b += 2 + + return v +} + +// Write request to wire for QueryExtents +// queryExtentsRequest writes a QueryExtents request to a byte slice. +func queryExtentsRequest(c *xgb.Conn, DestinationWindow xproto.Window) []byte { + size := 8 + b := 0 + buf := make([]byte, size) + + c.ExtLock.RLock() + buf[b] = c.Extensions["SHAPE"] + 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(DestinationWindow)) + 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["SHAPE"]; !ok { + panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'SHAPE'. shape.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["SHAPE"]; !ok { + panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'SHAPE'. shape.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 + // padding: 1 bytes + MajorVersion uint16 + MinorVersion uint16 +} + +// 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 + + 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["SHAPE"] + 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 +} + +// RectanglesCookie is a cookie used only for Rectangles requests. +type RectanglesCookie struct { + *xgb.Cookie +} + +// Rectangles sends an unchecked request. +// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. +func Rectangles(c *xgb.Conn, Operation Op, DestinationKind Kind, Ordering byte, DestinationWindow xproto.Window, XOffset int16, YOffset int16, Rectangles []xproto.Rectangle) RectanglesCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["SHAPE"]; !ok { + panic("Cannot issue request 'Rectangles' using the uninitialized extension 'SHAPE'. shape.Init(connObj) must be called first.") + } + cookie := c.NewCookie(false, false) + c.NewRequest(rectanglesRequest(c, Operation, DestinationKind, Ordering, DestinationWindow, XOffset, YOffset, Rectangles), cookie) + return RectanglesCookie{cookie} +} + +// RectanglesChecked sends a checked request. +// If an error occurs, it can be retrieved using RectanglesCookie.Check() +func RectanglesChecked(c *xgb.Conn, Operation Op, DestinationKind Kind, Ordering byte, DestinationWindow xproto.Window, XOffset int16, YOffset int16, Rectangles []xproto.Rectangle) RectanglesCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["SHAPE"]; !ok { + panic("Cannot issue request 'Rectangles' using the uninitialized extension 'SHAPE'. shape.Init(connObj) must be called first.") + } + cookie := c.NewCookie(true, false) + c.NewRequest(rectanglesRequest(c, Operation, DestinationKind, Ordering, DestinationWindow, XOffset, YOffset, Rectangles), cookie) + return RectanglesCookie{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 RectanglesCookie) Check() error { + return cook.Cookie.Check() +} + +// Write request to wire for Rectangles +// rectanglesRequest writes a Rectangles request to a byte slice. +func rectanglesRequest(c *xgb.Conn, Operation Op, DestinationKind Kind, Ordering byte, DestinationWindow xproto.Window, XOffset int16, YOffset int16, Rectangles []xproto.Rectangle) []byte { + size := xgb.Pad((16 + xgb.Pad((len(Rectangles) * 8)))) + b := 0 + buf := make([]byte, size) + + c.ExtLock.RLock() + buf[b] = c.Extensions["SHAPE"] + 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 + + buf[b] = byte(Operation) + b += 1 + + buf[b] = byte(DestinationKind) + b += 1 + + buf[b] = Ordering + b += 1 + + b += 1 // padding + + xgb.Put32(buf[b:], uint32(DestinationWindow)) + b += 4 + + xgb.Put16(buf[b:], uint16(XOffset)) + b += 2 + + xgb.Put16(buf[b:], uint16(YOffset)) + b += 2 + + b += xproto.RectangleListBytes(buf[b:], Rectangles) + + 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, DestinationWindow xproto.Window, Enable bool) SelectInputCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["SHAPE"]; !ok { + panic("Cannot issue request 'SelectInput' using the uninitialized extension 'SHAPE'. shape.Init(connObj) must be called first.") + } + cookie := c.NewCookie(false, false) + c.NewRequest(selectInputRequest(c, DestinationWindow, Enable), 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, DestinationWindow xproto.Window, Enable bool) SelectInputCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["SHAPE"]; !ok { + panic("Cannot issue request 'SelectInput' using the uninitialized extension 'SHAPE'. shape.Init(connObj) must be called first.") + } + cookie := c.NewCookie(true, false) + c.NewRequest(selectInputRequest(c, DestinationWindow, Enable), 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, DestinationWindow xproto.Window, Enable bool) []byte { + size := 12 + b := 0 + buf := make([]byte, size) + + c.ExtLock.RLock() + buf[b] = c.Extensions["SHAPE"] + 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(DestinationWindow)) + b += 4 + + if Enable { + buf[b] = 1 + } else { + buf[b] = 0 + } + b += 1 + + b += 3 // padding + + return buf +} diff --git a/vendor/github.com/BurntSushi/xgb/xinerama/xinerama.go b/vendor/github.com/BurntSushi/xgb/xinerama/xinerama.go new file mode 100644 index 0000000..ec97406 --- /dev/null +++ b/vendor/github.com/BurntSushi/xgb/xinerama/xinerama.go @@ -0,0 +1,718 @@ +// Package xinerama is the X client API for the XINERAMA extension. +package xinerama + +// This file is automatically generated from xinerama.xml. Edit at your peril! + +import ( + "github.com/BurntSushi/xgb" + + "github.com/BurntSushi/xgb/xproto" +) + +// Init must be called before using the XINERAMA extension. +func Init(c *xgb.Conn) error { + reply, err := xproto.QueryExtension(c, 8, "XINERAMA").Reply() + switch { + case err != nil: + return err + case !reply.Present: + return xgb.Errorf("No extension named XINERAMA could be found on on the server.") + } + + c.ExtLock.Lock() + c.Extensions["XINERAMA"] = reply.MajorOpcode + c.ExtLock.Unlock() + for evNum, fun := range xgb.NewExtEventFuncs["XINERAMA"] { + xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun + } + for errNum, fun := range xgb.NewExtErrorFuncs["XINERAMA"] { + xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun + } + return nil +} + +func init() { + xgb.NewExtEventFuncs["XINERAMA"] = make(map[int]xgb.NewEventFun) + xgb.NewExtErrorFuncs["XINERAMA"] = make(map[int]xgb.NewErrorFun) +} + +type ScreenInfo struct { + XOrg int16 + YOrg int16 + Width uint16 + Height uint16 +} + +// ScreenInfoRead reads a byte slice into a ScreenInfo value. +func ScreenInfoRead(buf []byte, v *ScreenInfo) int { + b := 0 + + v.XOrg = int16(xgb.Get16(buf[b:])) + b += 2 + + v.YOrg = int16(xgb.Get16(buf[b:])) + b += 2 + + v.Width = xgb.Get16(buf[b:]) + b += 2 + + v.Height = xgb.Get16(buf[b:]) + b += 2 + + return b +} + +// ScreenInfoReadList reads a byte slice into a list of ScreenInfo values. +func ScreenInfoReadList(buf []byte, dest []ScreenInfo) int { + b := 0 + for i := 0; i < len(dest); i++ { + dest[i] = ScreenInfo{} + b += ScreenInfoRead(buf[b:], &dest[i]) + } + return xgb.Pad(b) +} + +// Bytes writes a ScreenInfo value to a byte slice. +func (v ScreenInfo) Bytes() []byte { + buf := make([]byte, 8) + b := 0 + + xgb.Put16(buf[b:], uint16(v.XOrg)) + b += 2 + + xgb.Put16(buf[b:], uint16(v.YOrg)) + b += 2 + + xgb.Put16(buf[b:], v.Width) + b += 2 + + xgb.Put16(buf[b:], v.Height) + b += 2 + + return buf[:b] +} + +// ScreenInfoListBytes writes a list of ScreenInfo values to a byte slice. +func ScreenInfoListBytes(buf []byte, list []ScreenInfo) 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' + +// GetScreenCountCookie is a cookie used only for GetScreenCount requests. +type GetScreenCountCookie struct { + *xgb.Cookie +} + +// GetScreenCount sends a checked request. +// If an error occurs, it will be returned with the reply by calling GetScreenCountCookie.Reply() +func GetScreenCount(c *xgb.Conn, Window xproto.Window) GetScreenCountCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["XINERAMA"]; !ok { + panic("Cannot issue request 'GetScreenCount' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.") + } + cookie := c.NewCookie(true, true) + c.NewRequest(getScreenCountRequest(c, Window), cookie) + return GetScreenCountCookie{cookie} +} + +// GetScreenCountUnchecked sends an unchecked request. +// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. +func GetScreenCountUnchecked(c *xgb.Conn, Window xproto.Window) GetScreenCountCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["XINERAMA"]; !ok { + panic("Cannot issue request 'GetScreenCount' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.") + } + cookie := c.NewCookie(false, true) + c.NewRequest(getScreenCountRequest(c, Window), cookie) + return GetScreenCountCookie{cookie} +} + +// GetScreenCountReply represents the data returned from a GetScreenCount request. +type GetScreenCountReply struct { + Sequence uint16 // sequence number of the request for this reply + Length uint32 // number of bytes in this reply + ScreenCount byte + Window xproto.Window +} + +// Reply blocks and returns the reply data for a GetScreenCount request. +func (cook GetScreenCountCookie) Reply() (*GetScreenCountReply, error) { + buf, err := cook.Cookie.Reply() + if err != nil { + return nil, err + } + if buf == nil { + return nil, nil + } + return getScreenCountReply(buf), nil +} + +// getScreenCountReply reads a byte slice into a GetScreenCountReply value. +func getScreenCountReply(buf []byte) *GetScreenCountReply { + v := new(GetScreenCountReply) + b := 1 // skip reply determinant + + v.ScreenCount = buf[b] + b += 1 + + v.Sequence = xgb.Get16(buf[b:]) + b += 2 + + v.Length = xgb.Get32(buf[b:]) // 4-byte units + b += 4 + + v.Window = xproto.Window(xgb.Get32(buf[b:])) + b += 4 + + return v +} + +// Write request to wire for GetScreenCount +// getScreenCountRequest writes a GetScreenCount request to a byte slice. +func getScreenCountRequest(c *xgb.Conn, Window xproto.Window) []byte { + size := 8 + b := 0 + buf := make([]byte, size) + + c.ExtLock.RLock() + buf[b] = c.Extensions["XINERAMA"] + 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 + + return buf +} + +// GetScreenSizeCookie is a cookie used only for GetScreenSize requests. +type GetScreenSizeCookie struct { + *xgb.Cookie +} + +// GetScreenSize sends a checked request. +// If an error occurs, it will be returned with the reply by calling GetScreenSizeCookie.Reply() +func GetScreenSize(c *xgb.Conn, Window xproto.Window, Screen uint32) GetScreenSizeCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["XINERAMA"]; !ok { + panic("Cannot issue request 'GetScreenSize' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.") + } + cookie := c.NewCookie(true, true) + c.NewRequest(getScreenSizeRequest(c, Window, Screen), cookie) + return GetScreenSizeCookie{cookie} +} + +// GetScreenSizeUnchecked sends an unchecked request. +// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. +func GetScreenSizeUnchecked(c *xgb.Conn, Window xproto.Window, Screen uint32) GetScreenSizeCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["XINERAMA"]; !ok { + panic("Cannot issue request 'GetScreenSize' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.") + } + cookie := c.NewCookie(false, true) + c.NewRequest(getScreenSizeRequest(c, Window, Screen), cookie) + return GetScreenSizeCookie{cookie} +} + +// GetScreenSizeReply represents the data returned from a GetScreenSize request. +type GetScreenSizeReply struct { + Sequence uint16 // sequence number of the request for this reply + Length uint32 // number of bytes in this reply + // padding: 1 bytes + Width uint32 + Height uint32 + Window xproto.Window + Screen uint32 +} + +// Reply blocks and returns the reply data for a GetScreenSize request. +func (cook GetScreenSizeCookie) Reply() (*GetScreenSizeReply, error) { + buf, err := cook.Cookie.Reply() + if err != nil { + return nil, err + } + if buf == nil { + return nil, nil + } + return getScreenSizeReply(buf), nil +} + +// getScreenSizeReply reads a byte slice into a GetScreenSizeReply value. +func getScreenSizeReply(buf []byte) *GetScreenSizeReply { + v := new(GetScreenSizeReply) + 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.Width = xgb.Get32(buf[b:]) + b += 4 + + v.Height = xgb.Get32(buf[b:]) + b += 4 + + v.Window = xproto.Window(xgb.Get32(buf[b:])) + b += 4 + + v.Screen = xgb.Get32(buf[b:]) + b += 4 + + return v +} + +// Write request to wire for GetScreenSize +// getScreenSizeRequest writes a GetScreenSize request to a byte slice. +func getScreenSizeRequest(c *xgb.Conn, Window xproto.Window, Screen uint32) []byte { + size := 12 + b := 0 + buf := make([]byte, size) + + c.ExtLock.RLock() + buf[b] = c.Extensions["XINERAMA"] + 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 + + xgb.Put32(buf[b:], Screen) + b += 4 + + return buf +} + +// GetStateCookie is a cookie used only for GetState requests. +type GetStateCookie struct { + *xgb.Cookie +} + +// GetState sends a checked request. +// If an error occurs, it will be returned with the reply by calling GetStateCookie.Reply() +func GetState(c *xgb.Conn, Window xproto.Window) GetStateCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["XINERAMA"]; !ok { + panic("Cannot issue request 'GetState' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.") + } + cookie := c.NewCookie(true, true) + c.NewRequest(getStateRequest(c, Window), cookie) + return GetStateCookie{cookie} +} + +// GetStateUnchecked sends an unchecked request. +// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. +func GetStateUnchecked(c *xgb.Conn, Window xproto.Window) GetStateCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["XINERAMA"]; !ok { + panic("Cannot issue request 'GetState' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.") + } + cookie := c.NewCookie(false, true) + c.NewRequest(getStateRequest(c, Window), cookie) + return GetStateCookie{cookie} +} + +// GetStateReply represents the data returned from a GetState request. +type GetStateReply struct { + Sequence uint16 // sequence number of the request for this reply + Length uint32 // number of bytes in this reply + State byte + Window xproto.Window +} + +// Reply blocks and returns the reply data for a GetState request. +func (cook GetStateCookie) Reply() (*GetStateReply, error) { + buf, err := cook.Cookie.Reply() + if err != nil { + return nil, err + } + if buf == nil { + return nil, nil + } + return getStateReply(buf), nil +} + +// getStateReply reads a byte slice into a GetStateReply value. +func getStateReply(buf []byte) *GetStateReply { + v := new(GetStateReply) + 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.Window = xproto.Window(xgb.Get32(buf[b:])) + b += 4 + + return v +} + +// Write request to wire for GetState +// getStateRequest writes a GetState request to a byte slice. +func getStateRequest(c *xgb.Conn, Window xproto.Window) []byte { + size := 8 + b := 0 + buf := make([]byte, size) + + c.ExtLock.RLock() + buf[b] = c.Extensions["XINERAMA"] + 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 + + return buf +} + +// IsActiveCookie is a cookie used only for IsActive requests. +type IsActiveCookie struct { + *xgb.Cookie +} + +// IsActive sends a checked request. +// If an error occurs, it will be returned with the reply by calling IsActiveCookie.Reply() +func IsActive(c *xgb.Conn) IsActiveCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["XINERAMA"]; !ok { + panic("Cannot issue request 'IsActive' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.") + } + cookie := c.NewCookie(true, true) + c.NewRequest(isActiveRequest(c), cookie) + return IsActiveCookie{cookie} +} + +// IsActiveUnchecked sends an unchecked request. +// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. +func IsActiveUnchecked(c *xgb.Conn) IsActiveCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["XINERAMA"]; !ok { + panic("Cannot issue request 'IsActive' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.") + } + cookie := c.NewCookie(false, true) + c.NewRequest(isActiveRequest(c), cookie) + return IsActiveCookie{cookie} +} + +// IsActiveReply represents the data returned from a IsActive request. +type IsActiveReply struct { + Sequence uint16 // sequence number of the request for this reply + Length uint32 // number of bytes in this reply + // padding: 1 bytes + State uint32 +} + +// Reply blocks and returns the reply data for a IsActive request. +func (cook IsActiveCookie) Reply() (*IsActiveReply, error) { + buf, err := cook.Cookie.Reply() + if err != nil { + return nil, err + } + if buf == nil { + return nil, nil + } + return isActiveReply(buf), nil +} + +// isActiveReply reads a byte slice into a IsActiveReply value. +func isActiveReply(buf []byte) *IsActiveReply { + v := new(IsActiveReply) + 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.State = xgb.Get32(buf[b:]) + b += 4 + + return v +} + +// Write request to wire for IsActive +// isActiveRequest writes a IsActive request to a byte slice. +func isActiveRequest(c *xgb.Conn) []byte { + size := 4 + b := 0 + buf := make([]byte, size) + + c.ExtLock.RLock() + buf[b] = c.Extensions["XINERAMA"] + 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 +} + +// QueryScreensCookie is a cookie used only for QueryScreens requests. +type QueryScreensCookie struct { + *xgb.Cookie +} + +// QueryScreens sends a checked request. +// If an error occurs, it will be returned with the reply by calling QueryScreensCookie.Reply() +func QueryScreens(c *xgb.Conn) QueryScreensCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["XINERAMA"]; !ok { + panic("Cannot issue request 'QueryScreens' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.") + } + cookie := c.NewCookie(true, true) + c.NewRequest(queryScreensRequest(c), cookie) + return QueryScreensCookie{cookie} +} + +// QueryScreensUnchecked sends an unchecked request. +// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. +func QueryScreensUnchecked(c *xgb.Conn) QueryScreensCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["XINERAMA"]; !ok { + panic("Cannot issue request 'QueryScreens' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.") + } + cookie := c.NewCookie(false, true) + c.NewRequest(queryScreensRequest(c), cookie) + return QueryScreensCookie{cookie} +} + +// QueryScreensReply represents the data returned from a QueryScreens request. +type QueryScreensReply struct { + Sequence uint16 // sequence number of the request for this reply + Length uint32 // number of bytes in this reply + // padding: 1 bytes + Number uint32 + // padding: 20 bytes + ScreenInfo []ScreenInfo // size: xgb.Pad((int(Number) * 8)) +} + +// Reply blocks and returns the reply data for a QueryScreens request. +func (cook QueryScreensCookie) Reply() (*QueryScreensReply, error) { + buf, err := cook.Cookie.Reply() + if err != nil { + return nil, err + } + if buf == nil { + return nil, nil + } + return queryScreensReply(buf), nil +} + +// queryScreensReply reads a byte slice into a QueryScreensReply value. +func queryScreensReply(buf []byte) *QueryScreensReply { + v := new(QueryScreensReply) + 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.Number = xgb.Get32(buf[b:]) + b += 4 + + b += 20 // padding + + v.ScreenInfo = make([]ScreenInfo, v.Number) + b += ScreenInfoReadList(buf[b:], v.ScreenInfo) + + return v +} + +// Write request to wire for QueryScreens +// queryScreensRequest writes a QueryScreens request to a byte slice. +func queryScreensRequest(c *xgb.Conn) []byte { + size := 4 + b := 0 + buf := make([]byte, size) + + c.ExtLock.RLock() + buf[b] = c.Extensions["XINERAMA"] + 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 +} + +// 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, Major byte, Minor byte) QueryVersionCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["XINERAMA"]; !ok { + panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.") + } + cookie := c.NewCookie(true, true) + c.NewRequest(queryVersionRequest(c, Major, Minor), 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, Major byte, Minor byte) QueryVersionCookie { + c.ExtLock.RLock() + defer c.ExtLock.RUnlock() + if _, ok := c.Extensions["XINERAMA"]; !ok { + panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.") + } + cookie := c.NewCookie(false, true) + c.NewRequest(queryVersionRequest(c, Major, Minor), 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 + Major uint16 + Minor uint16 +} + +// 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.Major = xgb.Get16(buf[b:]) + b += 2 + + v.Minor = xgb.Get16(buf[b:]) + b += 2 + + return v +} + +// Write request to wire for QueryVersion +// queryVersionRequest writes a QueryVersion request to a byte slice. +func queryVersionRequest(c *xgb.Conn, Major byte, Minor byte) []byte { + size := 8 + b := 0 + buf := make([]byte, size) + + c.ExtLock.RLock() + buf[b] = c.Extensions["XINERAMA"] + 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] = Major + b += 1 + + buf[b] = Minor + b += 1 + + return buf +} diff --git a/vendor/github.com/BurntSushi/xgbutil/.gitignore b/vendor/github.com/BurntSushi/xgbutil/.gitignore new file mode 100644 index 0000000..22df4f4 --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/.gitignore @@ -0,0 +1,6 @@ +*.swp +*.png +tst_first +tst_graphics +TAGS + diff --git a/vendor/github.com/BurntSushi/xgbutil/COPYING b/vendor/github.com/BurntSushi/xgbutil/COPYING new file mode 100644 index 0000000..5c93f45 --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/COPYING @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + 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. diff --git a/vendor/github.com/BurntSushi/xgbutil/Makefile b/vendor/github.com/BurntSushi/xgbutil/Makefile new file mode 100644 index 0000000..a510c83 --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/Makefile @@ -0,0 +1,36 @@ +all: callback.go types_auto.go gofmt + +install: + go install -p 6 . ./ewmh ./gopher ./icccm ./keybind ./motif ./mousebind \ + ./xcursor ./xevent ./xgraphics ./xinerama ./xprop ./xrect ./xwindow + +push: + git push origin master + git push github master + +build-ex: + find ./_examples/ -type d -wholename './_examples/[a-z]*' -print0 \ + | xargs -0 go build -p 6 + +gofmt: + gofmt -w *.go */*.go _examples/*/*.go + colcheck *.go */*.go _examples/*/*.go + +callback.go: + scripts/write-events callbacks > xevent/callback.go + +types_auto.go: + scripts/write-events evtypes > xevent/types_auto.go + +tags: + find ./ \( -name '*.go' -and -not -wholename './tests/*' -and -not -wholename './_examples/*' \) -print0 | xargs -0 gotags > TAGS + +loc: + find ./ -name '*.go' -and -not -wholename './tests*' -and -not -name '*keysymdef.go' -and -not -name '*gopher.go' -print | sort | xargs wc -l + +ex-%: + go run _examples/$*/main.go + +gopherimg: + go-bindata -f GopherPng -p gopher -i gopher/gophercolor-small.png -o gopher/gopher.go + diff --git a/vendor/github.com/BurntSushi/xgbutil/README b/vendor/github.com/BurntSushi/xgbutil/README new file mode 100644 index 0000000..a31507a --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/README @@ -0,0 +1,55 @@ +xgbutil is a utility library designed to work with the X Go Binding. This +project's main goal is to make various X related tasks easier. For example, +binding keys, using the EWMH or ICCCM specs with the window manager, +moving/resizing windows, assigning function callbacks to particular events, +drawing images to a window, etc. + +xgbutil attempts to be thread safe, but it has not been completely tested in +this regard. In general, the X event loop implemented in the xevent package is +sequential. The idea is to be sequential by default, and let the user spawn +concurrent code at their discretion. (i.e., the complexity of making the main +event loop generally concurrent is vast.) + +You may sleep safely at night by assuming that XGB is thread safe, though. + +To start using xgbutil, you should have at least a passing familiarity with X. +Your first stop should be the examples directory. + +Installation +============ +go get github.com/BurntSushi/xgbutil + +Dependencies +============ +XGB is the main dependency. Use of the xgraphics packages requires graphics-go +and freetype-go. + +XGB project URL: https://github.com/BurntSushi/xgb + +Quick Example +============= +go get github.com/BurntSushi/xgbutil/_examples/window-name-sizes +"$GOPATH"/bin/window-name-sizes + +The output will be a list of names of all top-level windows and their geometry +including window manager decorations. (Assuming your window manager supports +some basic EWMH properties.) + +Documentation +============= +https://godoc.org/github.com/BurntSushi/xgbutil + +Examples +======== +There are several examples in the examples directory covering common use cases. +They are heavily documented and should run out of the box. + +Python +====== +An older project of mine, xpybutil, served as inspiration for xgbutil. If you +want to use Python, xpybutil should help quite a bit. Please note though, that +at this point, xgbutil provides a lot more functionality and is much better +documented. + +xpybutil project URL: https://github.com/BurntSushi/xpybutil + diff --git a/vendor/github.com/BurntSushi/xgbutil/STYLE b/vendor/github.com/BurntSushi/xgbutil/STYLE new file mode 100644 index 0000000..b827c3c --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/STYLE @@ -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. + diff --git a/vendor/github.com/BurntSushi/xgbutil/doc.go b/vendor/github.com/BurntSushi/xgbutil/doc.go new file mode 100644 index 0000000..d4c9586 --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/doc.go @@ -0,0 +1,67 @@ +/* +Package xgbutil is a utility library designed to make common tasks with the X +server easier. The central design choice that has driven development is to hide +the complexity of X wherever possible but expose it when necessary. + +For example, the xevent package provides an implementation of an X event loop +that acts as a dispatcher to event handlers set up with the xevent, keybind and +mousebind packages. At the same time, the event queue is exposed and can be +modified using xevent.Peek and xevent.DequeueAt. + +Sub-packages + +The xgbutil package is considerably small, and only contains some type +definitions and the initial setup for an X connection. Much of the +functionality of xgbutil comes from its sub-packages. Each sub-package is +appropriately documented. + +Installation + +xgbutil is go-gettable: + + go get github.com/BurntSushi/xgbutil + +Dependencies + +XGB is the main dependency, and is required for all packages inside xgbutil. + +graphics-go and freetype-go are also required if using the xgraphics package. + +Quick Example + +A quick example to demonstrate that xgbutil is working correctly: + + go get github.com/BurntSushi/xgbutil/examples/window-name-sizes + GO/PATH/bin/window-name-sizes + +The output will be a list of names of all top-level windows and their geometry +including window manager decorations. (Assuming your window manager supports +some basic EWMH properties.) + +Examples + +The examples directory contains a sizable number of examples demonstrating +common tasks with X. They are intended to demonstrate a single thing each, +although a few that require setup are necessarily long. Each example is +heavily documented. + +The examples directory should be your first stop when learning how to use +xgbutil. + +xgbutil is also used heavily throughout my window manager, Wingo. It may be +useful reference material. + +Wingo project page: https://github.com/BurntSushi/wingo + +Thread Safety + +While I am fairly confident that XGB is thread safe, I am only somewhat +confident that xgbutil is thread safe. It simply has not been tested enough for +my confidence to be higher. + +Note that the xevent package's X event loop is not concurrent. Namely, +designing a generally concurrent X event loop is extremely complex. Instead, +the onus is on you, the user, to design concurrent callback functions if +concurrency is desired. +*/ +package xgbutil diff --git a/vendor/github.com/BurntSushi/xgbutil/ewmh/doc.go b/vendor/github.com/BurntSushi/xgbutil/ewmh/doc.go new file mode 100644 index 0000000..6e7506a --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/ewmh/doc.go @@ -0,0 +1,56 @@ +/* +Package ewmh provides a comprehensive API to get and set properties specified +by the EWMH spec, as well as perform actions specified by the EWMH spec. + +Since there are so many functions and they adhere to an existing spec, +this package file does not contain much documentation. Indeed, each +method has only a single comment associated with it: the EWMH property name. + +The idea is to +provide a consistent interface to use all facilities described in the EWMH +spec: http://standards.freedesktop.org/wm-spec/wm-spec-latest.html. + +Naming scheme + +Using "_NET_ACTIVE_WINDOW" as an example, +functions "ActiveWindowGet" and "ActiveWindowSet" get and set the +property, respectively. Both of these functions exist for most EWMH +properties. Additionally, some EWMH properties support sending a client +message event to request the window manager to perform some action. In the +case of "_NET_ACTIVE_WINDOW", this request is used to set the active +window. + +These sorts of functions end in "Req". So for "_NET_ACTIVE_WINDOW", +the method name is "ActiveWindowReq". Moreover, most requests include +various parameters that don't need to be changed often (like the source +indication). Thus, by default, functions ending in "Req" force these to +sensible defaults. If you need access to all of the parameters, use the +corresponding "ReqExtra" method. So for "_NET_ACTIVE_WINDOW", that would +be "ActiveWindowReqExtra". (If no "ReqExtra" method exists, then the +"Req" method covers all available parameters.) + +This naming scheme has one exception: if a property's only use is through +sending an event (like "_NET_CLOSE_WINDOW"), then the name will be +"CloseWindow" for the short-hand version and "CloseWindowExtra" +for access to all of the parameters. (Since there is no "_NET_CLOSE_WINDOW" +property, there is no need for "CloseWindowGet" and "CloseWindowSet" +functions.) + +For properties that store more than just a simple integer, name or list +of integers, structs have been created and exposed to organize the +information returned in a sensible manner. For example, the +"_NET_DESKTOP_GEOMETRY" property would typically return a slice of integers +of length 2, where the first integer is the width and the second is the +height. Xgbutil will wrap this in a struct with the obvious members. These +structs are documented. + +Finally, functions ending in "*Set" are typically only used when setting +properties on clients *you've* created or when the window manager sets +properties. Thus, it's unlikely that you should use them unless you're +creating a top-level client or building a window manager. + +Functions ending in "Get" or "Req[Extra]" are commonly used. + +N.B. Not all properties have "*Req" functions. +*/ +package ewmh diff --git a/vendor/github.com/BurntSushi/xgbutil/ewmh/ewmh.go b/vendor/github.com/BurntSushi/xgbutil/ewmh/ewmh.go new file mode 100644 index 0000000..69cab5a --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/ewmh/ewmh.go @@ -0,0 +1,1159 @@ +package ewmh + +import ( + "github.com/BurntSushi/xgb/xproto" + + "github.com/BurntSushi/xgbutil" + "github.com/BurntSushi/xgbutil/xevent" + "github.com/BurntSushi/xgbutil/xprop" +) + +// ClientEvent is a convenience function that sends ClientMessage events +// to the root window as specified by the EWMH spec. +func ClientEvent(xu *xgbutil.XUtil, window xproto.Window, messageType string, + data ...interface{}) error { + + mstype, err := xprop.Atm(xu, messageType) + if err != nil { + return err + } + + evMask := (xproto.EventMaskSubstructureNotify | + xproto.EventMaskSubstructureRedirect) + cm, err := xevent.NewClientMessage(32, window, mstype, data...) + if err != nil { + return err + } + + return xevent.SendRootEvent(xu, cm, uint32(evMask)) +} + +// _NET_ACTIVE_WINDOW get +func ActiveWindowGet(xu *xgbutil.XUtil) (xproto.Window, error) { + return xprop.PropValWindow(xprop.GetProperty(xu, xu.RootWin(), + "_NET_ACTIVE_WINDOW")) +} + +// _NET_ACTIVE_WINDOW set +func ActiveWindowSet(xu *xgbutil.XUtil, win xproto.Window) error { + return xprop.ChangeProp32(xu, xu.RootWin(), "_NET_ACTIVE_WINDOW", "WINDOW", + uint(win)) +} + +// _NET_ACTIVE_WINDOW req +func ActiveWindowReq(xu *xgbutil.XUtil, win xproto.Window) error { + return ActiveWindowReqExtra(xu, win, 2, 0, 0) +} + +// _NET_ACTIVE_WINDOW req extra +func ActiveWindowReqExtra(xu *xgbutil.XUtil, win xproto.Window, source int, + time xproto.Timestamp, currentActive xproto.Window) error { + + return ClientEvent(xu, win, "_NET_ACTIVE_WINDOW", source, int(time), + int(currentActive)) +} + +// _NET_CLIENT_LIST get +func ClientListGet(xu *xgbutil.XUtil) ([]xproto.Window, error) { + return xprop.PropValWindows(xprop.GetProperty(xu, xu.RootWin(), + "_NET_CLIENT_LIST")) +} + +// _NET_CLIENT_LIST set +func ClientListSet(xu *xgbutil.XUtil, wins []xproto.Window) error { + return xprop.ChangeProp32(xu, xu.RootWin(), "_NET_CLIENT_LIST", "WINDOW", + xprop.WindowToInt(wins)...) +} + +// _NET_CLIENT_LIST_STACKING get +func ClientListStackingGet(xu *xgbutil.XUtil) ([]xproto.Window, error) { + return xprop.PropValWindows(xprop.GetProperty(xu, xu.RootWin(), + "_NET_CLIENT_LIST_STACKING")) +} + +// _NET_CLIENT_LIST_STACKING set +func ClientListStackingSet(xu *xgbutil.XUtil, wins []xproto.Window) error { + return xprop.ChangeProp32(xu, xu.RootWin(), "_NET_CLIENT_LIST_STACKING", + "WINDOW", xprop.WindowToInt(wins)...) +} + +// _NET_CLOSE_WINDOW req +func CloseWindow(xu *xgbutil.XUtil, win xproto.Window) error { + return CloseWindowExtra(xu, win, 0, 2) +} + +// _NET_CLOSE_WINDOW req extra +func CloseWindowExtra(xu *xgbutil.XUtil, win xproto.Window, + time xproto.Timestamp, source int) error { + + return ClientEvent(xu, win, "_NET_CLOSE_WINDOW", int(time), source) +} + +// _NET_CURRENT_DESKTOP get +func CurrentDesktopGet(xu *xgbutil.XUtil) (uint, error) { + return xprop.PropValNum(xprop.GetProperty(xu, xu.RootWin(), + "_NET_CURRENT_DESKTOP")) +} + +// _NET_CURRENT_DESKTOP set +func CurrentDesktopSet(xu *xgbutil.XUtil, desk uint) error { + return xprop.ChangeProp32(xu, xu.RootWin(), "_NET_CURRENT_DESKTOP", + "CARDINAL", desk) +} + +// _NET_CURRENT_DESKTOP req +func CurrentDesktopReq(xu *xgbutil.XUtil, desk int) error { + return CurrentDesktopReqExtra(xu, desk, 0) +} + +// _NET_CURRENT_DESKTOP req extra +func CurrentDesktopReqExtra(xu *xgbutil.XUtil, desk int, + time xproto.Timestamp) error { + + return ClientEvent(xu, xu.RootWin(), "_NET_CURRENT_DESKTOP", desk, + int(time)) +} + +// _NET_DESKTOP_NAMES get +func DesktopNamesGet(xu *xgbutil.XUtil) ([]string, error) { + return xprop.PropValStrs(xprop.GetProperty(xu, xu.RootWin(), + "_NET_DESKTOP_NAMES")) +} + +// _NET_DESKTOP_NAMES set +func DesktopNamesSet(xu *xgbutil.XUtil, names []string) error { + nullterm := make([]byte, 0) + for _, name := range names { + nullterm = append(nullterm, name...) + nullterm = append(nullterm, 0) + } + return xprop.ChangeProp(xu, xu.RootWin(), 8, "_NET_DESKTOP_NAMES", + "UTF8_STRING", nullterm) +} + +// DesktopGeometry is a struct that houses the width and height of a +// _NET_DESKTOP_GEOMETRY property reply. +type DesktopGeometry struct { + Width int + Height int +} + +// _NET_DESKTOP_GEOMETRY get +func DesktopGeometryGet(xu *xgbutil.XUtil) (*DesktopGeometry, error) { + geom, err := xprop.PropValNums(xprop.GetProperty(xu, xu.RootWin(), + "_NET_DESKTOP_GEOMETRY")) + if err != nil { + return nil, err + } + + return &DesktopGeometry{Width: int(geom[0]), Height: int(geom[1])}, nil +} + +// _NET_DESKTOP_GEOMETRY set +func DesktopGeometrySet(xu *xgbutil.XUtil, dg *DesktopGeometry) error { + return xprop.ChangeProp32(xu, xu.RootWin(), "_NET_DESKTOP_GEOMETRY", + "CARDINAL", uint(dg.Width), uint(dg.Height)) +} + +// _NET_DESKTOP_GEOMETRY req +func DesktopGeometryReq(xu *xgbutil.XUtil, dg *DesktopGeometry) error { + return ClientEvent(xu, xu.RootWin(), "_NET_DESKTOP_GEOMETRY", dg.Width, + dg.Height) +} + +// DesktopLayout is a struct that organizes information pertaining to +// the _NET_DESKTOP_LAYOUT property. Namely, the orientation, the number +// of columns, the number of rows, and the starting corner. +type DesktopLayout struct { + Orientation int + Columns int + Rows int + StartingCorner int +} + +// _NET_DESKTOP_LAYOUT constants for orientation +const ( + OrientHorz = iota + OrientVert +) + +// _NET_DESKTOP_LAYOUT constants for starting corner +const ( + TopLeft = iota + TopRight + BottomRight + BottomLeft +) + +// _NET_DESKTOP_LAYOUT get +func DesktopLayoutGet(xu *xgbutil.XUtil) (dl *DesktopLayout, err error) { + dlraw, err := xprop.PropValNums(xprop.GetProperty(xu, xu.RootWin(), + "_NET_DESKTOP_LAYOUT")) + if err != nil { + return nil, err + } + + dl = &DesktopLayout{} + dl.Orientation = int(dlraw[0]) + dl.Columns = int(dlraw[1]) + dl.Rows = int(dlraw[2]) + + if len(dlraw) > 3 { + dl.StartingCorner = int(dlraw[3]) + } else { + dl.StartingCorner = TopLeft + } + + return dl, nil +} + +// _NET_DESKTOP_LAYOUT set +func DesktopLayoutSet(xu *xgbutil.XUtil, orientation, columns, rows, + startingCorner uint) error { + + return xprop.ChangeProp32(xu, xu.RootWin(), "_NET_DESKTOP_LAYOUT", + "CARDINAL", orientation, columns, rows, + startingCorner) +} + +// DesktopViewport is a struct that contains a pairing of x,y coordinates +// representing the top-left corner of each desktop. (There will typically +// be one struct here for each desktop in existence.) +type DesktopViewport struct { + X int + Y int +} + +// _NET_DESKTOP_VIEWPORT get +func DesktopViewportGet(xu *xgbutil.XUtil) ([]DesktopViewport, error) { + coords, err := xprop.PropValNums(xprop.GetProperty(xu, xu.RootWin(), + "_NET_DESKTOP_VIEWPORT")) + if err != nil { + return nil, err + } + + viewports := make([]DesktopViewport, len(coords)/2) + for i, _ := range viewports { + viewports[i] = DesktopViewport{ + X: int(coords[i*2]), + Y: int(coords[i*2+1]), + } + } + return viewports, nil +} + +// _NET_DESKTOP_VIEWPORT set +func DesktopViewportSet(xu *xgbutil.XUtil, viewports []DesktopViewport) error { + coords := make([]uint, len(viewports)*2) + for i, viewport := range viewports { + coords[i*2] = uint(viewport.X) + coords[i*2+1] = uint(viewport.Y) + } + + return xprop.ChangeProp32(xu, xu.RootWin(), "_NET_DESKTOP_VIEWPORT", + "CARDINAL", coords...) +} + +// _NET_DESKTOP_VIEWPORT req +func DesktopViewportReq(xu *xgbutil.XUtil, x, y int) error { + return ClientEvent(xu, xu.RootWin(), "_NET_DESKTOP_VIEWPORT", x, y) +} + +// FrameExtents is a struct that organizes information associated with +// the _NET_FRAME_EXTENTS property. Namely, the left, right, top and bottom +// decoration sizes. +type FrameExtents struct { + Left int + Right int + Top int + Bottom int +} + +// _NET_FRAME_EXTENTS get +func FrameExtentsGet(xu *xgbutil.XUtil, + win xproto.Window) (*FrameExtents, error) { + + raw, err := xprop.PropValNums(xprop.GetProperty(xu, win, + "_NET_FRAME_EXTENTS")) + if err != nil { + return nil, err + } + + return &FrameExtents{ + Left: int(raw[0]), + Right: int(raw[1]), + Top: int(raw[2]), + Bottom: int(raw[3]), + }, nil +} + +// _NET_FRAME_EXTENTS set +func FrameExtentsSet(xu *xgbutil.XUtil, win xproto.Window, + extents *FrameExtents) error { + raw := make([]uint, 4) + raw[0] = uint(extents.Left) + raw[1] = uint(extents.Right) + raw[2] = uint(extents.Top) + raw[3] = uint(extents.Bottom) + + return xprop.ChangeProp32(xu, win, "_NET_FRAME_EXTENTS", "CARDINAL", raw...) +} + +// _NET_MOVERESIZE_WINDOW req +// If 'w' or 'h' are 0, then they are not sent. +// If you need to resize a window without moving it, use the ReqExtra variant, +// or Resize. +func MoveresizeWindow(xu *xgbutil.XUtil, win xproto.Window, + x, y, w, h int) error { + + return MoveresizeWindowExtra(xu, win, x, y, w, h, xproto.GravityBitForget, + 2, true, true) +} + +// _NET_MOVERESIZE_WINDOW req resize only +func ResizeWindow(xu *xgbutil.XUtil, win xproto.Window, w, h int) error { + return MoveresizeWindowExtra(xu, win, 0, 0, w, h, xproto.GravityBitForget, + 2, false, false) +} + +// _NET_MOVERESIZE_WINDOW req move only +func MoveWindow(xu *xgbutil.XUtil, win xproto.Window, x, y int) error { + return MoveresizeWindowExtra(xu, win, x, y, 0, 0, xproto.GravityBitForget, + 2, true, true) +} + +// _NET_MOVERESIZE_WINDOW req extra +// If 'w' or 'h' are 0, then they are not sent. +// To not set 'x' or 'y', 'usex' or 'usey' need to be set to false. +func MoveresizeWindowExtra(xu *xgbutil.XUtil, win xproto.Window, x, y, w, h, + gravity, source int, usex, usey bool) error { + + flags := gravity + flags |= source << 12 + if usex { + flags |= 1 << 8 + } + if usey { + flags |= 1 << 9 + } + if w > 0 { + flags |= 1 << 10 + } + if h > 0 { + flags |= 1 << 11 + } + + return ClientEvent(xu, win, "_NET_MOVERESIZE_WINDOW", flags, x, y, w, h) +} + +// _NET_NUMBER_OF_DESKTOPS get +func NumberOfDesktopsGet(xu *xgbutil.XUtil) (uint, error) { + return xprop.PropValNum(xprop.GetProperty(xu, xu.RootWin(), + "_NET_NUMBER_OF_DESKTOPS")) +} + +// _NET_NUMBER_OF_DESKTOPS set +func NumberOfDesktopsSet(xu *xgbutil.XUtil, numDesks uint) error { + return xprop.ChangeProp32(xu, xu.RootWin(), "_NET_NUMBER_OF_DESKTOPS", + "CARDINAL", numDesks) +} + +// _NET_NUMBER_OF_DESKTOPS req +func NumberOfDesktopsReq(xu *xgbutil.XUtil, numDesks int) error { + return ClientEvent(xu, xu.RootWin(), "_NET_NUMBER_OF_DESKTOPS", numDesks) +} + +// _NET_REQUEST_FRAME_EXTENTS req +func RequestFrameExtents(xu *xgbutil.XUtil, win xproto.Window) error { + return ClientEvent(xu, win, "_NET_REQUEST_FRAME_EXTENTS") +} + +// _NET_RESTACK_WINDOW req +// The shortcut here is to just raise the window to the top of the window stack. +func RestackWindow(xu *xgbutil.XUtil, win xproto.Window) error { + return RestackWindowExtra(xu, win, xproto.StackModeAbove, 0, 2) +} + +// _NET_RESTACK_WINDOW req extra +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) +} + +// _NET_SHOWING_DESKTOP get +func ShowingDesktopGet(xu *xgbutil.XUtil) (bool, error) { + reply, err := xprop.GetProperty(xu, xu.RootWin(), "_NET_SHOWING_DESKTOP") + if err != nil { + return false, err + } + + val, err := xprop.PropValNum(reply, nil) + if err != nil { + return false, err + } + + return val == 1, nil +} + +// _NET_SHOWING_DESKTOP set +func ShowingDesktopSet(xu *xgbutil.XUtil, show bool) error { + var showInt uint + if show { + showInt = 1 + } else { + showInt = 0 + } + return xprop.ChangeProp32(xu, xu.RootWin(), "_NET_SHOWING_DESKTOP", + "CARDINAL", showInt) +} + +// _NET_SHOWING_DESKTOP req +func ShowingDesktopReq(xu *xgbutil.XUtil, show bool) error { + var showInt uint + if show { + showInt = 1 + } else { + showInt = 0 + } + return ClientEvent(xu, xu.RootWin(), "_NET_SHOWING_DESKTOP", showInt) +} + +// _NET_SUPPORTED get +func SupportedGet(xu *xgbutil.XUtil) ([]string, error) { + reply, err := xprop.GetProperty(xu, xu.RootWin(), "_NET_SUPPORTED") + return xprop.PropValAtoms(xu, reply, err) +} + +// _NET_SUPPORTED set +// This will create any atoms in the argument if they don't already exist. +func SupportedSet(xu *xgbutil.XUtil, atomNames []string) error { + atoms, err := xprop.StrToAtoms(xu, atomNames) + if err != nil { + return err + } + + return xprop.ChangeProp32(xu, xu.RootWin(), "_NET_SUPPORTED", "ATOM", + atoms...) +} + +// _NET_SUPPORTING_WM_CHECK get +func SupportingWmCheckGet(xu *xgbutil.XUtil, + win xproto.Window) (xproto.Window, error) { + + return xprop.PropValWindow(xprop.GetProperty(xu, win, + "_NET_SUPPORTING_WM_CHECK")) +} + +// _NET_SUPPORTING_WM_CHECK set +func SupportingWmCheckSet(xu *xgbutil.XUtil, win xproto.Window, + wmWin xproto.Window) error { + + return xprop.ChangeProp32(xu, win, "_NET_SUPPORTING_WM_CHECK", "WINDOW", + uint(wmWin)) +} + +// _NET_VIRTUAL_ROOTS get +func VirtualRootsGet(xu *xgbutil.XUtil) ([]xproto.Window, error) { + return xprop.PropValWindows(xprop.GetProperty(xu, xu.RootWin(), + "_NET_VIRTUAL_ROOTS")) +} + +// _NET_VIRTUAL_ROOTS set +func VirtualRootsSet(xu *xgbutil.XUtil, wins []xproto.Window) error { + return xprop.ChangeProp32(xu, xu.RootWin(), "_NET_VIRTUAL_ROOTS", "WINDOW", + xprop.WindowToInt(wins)...) +} + +// _NET_VISIBLE_DESKTOPS get +// This is not part of the EWMH spec, but is a property of my own creation. +// It allows the window manager to report that it has multiple desktops +// viewable at the same time. (This conflicts with other EWMH properties, +// so I don't think this will ever be added to the official spec.) +func VisibleDesktopsGet(xu *xgbutil.XUtil) ([]uint, error) { + return xprop.PropValNums(xprop.GetProperty(xu, xu.RootWin(), + "_NET_VISIBLE_DESKTOPS")) +} + +// _NET_VISIBLE_DESKTOPS set +func VisibleDesktopsSet(xu *xgbutil.XUtil, desktops []uint) error { + return xprop.ChangeProp32(xu, xu.RootWin(), "_NET_VISIBLE_DESKTOPS", + "CARDINAL", desktops...) +} + +// _NET_WM_ALLOWED_ACTIONS get +func WmAllowedActionsGet(xu *xgbutil.XUtil, + win xproto.Window) ([]string, error) { + + raw, err := xprop.GetProperty(xu, win, "_NET_WM_ALLOWED_ACTIONS") + return xprop.PropValAtoms(xu, raw, err) +} + +// _NET_WM_ALLOWED_ACTIONS set +func WmAllowedActionsSet(xu *xgbutil.XUtil, win xproto.Window, + atomNames []string) error { + + atoms, err := xprop.StrToAtoms(xu, atomNames) + if err != nil { + return err + } + + return xprop.ChangeProp32(xu, win, "_NET_WM_ALLOWED_ACTIONS", "ATOM", + atoms...) +} + +// _NET_WM_DESKTOP get +func WmDesktopGet(xu *xgbutil.XUtil, win xproto.Window) (uint, error) { + return xprop.PropValNum(xprop.GetProperty(xu, win, "_NET_WM_DESKTOP")) +} + +// _NET_WM_DESKTOP set +func WmDesktopSet(xu *xgbutil.XUtil, win xproto.Window, desk uint) error { + return xprop.ChangeProp32(xu, win, "_NET_WM_DESKTOP", "CARDINAL", + uint(desk)) +} + +// _NET_WM_DESKTOP req +func WmDesktopReq(xu *xgbutil.XUtil, win xproto.Window, desk uint) error { + return WmDesktopReqExtra(xu, win, desk, 2) +} + +// _NET_WM_DESKTOP req extra +func WmDesktopReqExtra(xu *xgbutil.XUtil, win xproto.Window, desk uint, + source int) error { + + return ClientEvent(xu, win, "_NET_WM_DESKTOP", desk, source) +} + +// WmFullscreenMonitors is a struct that organizes information related to the +// _NET_WM_FULLSCREEN_MONITORS property. Namely, the top, bottom, left and +// right monitor edges for a particular window. +type WmFullscreenMonitors struct { + Top uint + Bottom uint + Left uint + Right uint +} + +// _NET_WM_FULLSCREEN_MONITORS get +func WmFullscreenMonitorsGet(xu *xgbutil.XUtil, + win xproto.Window) (*WmFullscreenMonitors, error) { + + raw, err := xprop.PropValNums( + xprop.GetProperty(xu, win, "_NET_WM_FULLSCREEN_MONITORS")) + if err != nil { + return nil, err + } + + return &WmFullscreenMonitors{ + Top: raw[0], + Bottom: raw[1], + Left: raw[2], + Right: raw[3], + }, nil +} + +// _NET_WM_FULLSCREEN_MONITORS set +func WmFullscreenMonitorsSet(xu *xgbutil.XUtil, win xproto.Window, + edges *WmFullscreenMonitors) error { + + raw := make([]uint, 4) + raw[0] = edges.Top + raw[1] = edges.Bottom + raw[2] = edges.Left + raw[3] = edges.Right + + return xprop.ChangeProp32(xu, win, "_NET_WM_FULLSCREEN_MONITORS", + "CARDINAL", raw...) +} + +// _NET_WM_FULLSCREEN_MONITORS req +func WmFullscreenMonitorsReq(xu *xgbutil.XUtil, win xproto.Window, + edges *WmFullscreenMonitors) error { + + return WmFullscreenMonitorsReqExtra(xu, win, edges, 2) +} + +// _NET_WM_FULLSCREEN_MONITORS req extra +func WmFullscreenMonitorsReqExtra(xu *xgbutil.XUtil, win xproto.Window, + edges *WmFullscreenMonitors, source int) error { + + return ClientEvent(xu, win, "_NET_WM_FULLSCREEN_MONITORS", + edges.Top, edges.Bottom, edges.Left, edges.Right, source) +} + +// _NET_WM_HANDLED_ICONS get +func WmHandledIconsGet(xu *xgbutil.XUtil, win xproto.Window) (bool, error) { + reply, err := xprop.GetProperty(xu, win, "_NET_WM_HANDLED_ICONS") + if err != nil { + return false, err + } + + val, err := xprop.PropValNum(reply, nil) + if err != nil { + return false, err + } + + return val == 1, nil +} + +// _NET_WM_HANDLED_ICONS set +func WmHandledIconsSet(xu *xgbutil.XUtil, handle bool) error { + var handled uint + if handle { + handled = 1 + } else { + handled = 0 + } + return xprop.ChangeProp32(xu, xu.RootWin(), "_NET_WM_HANDLED_ICONS", + "CARDINAL", handled) +} + +// WmIcon is a struct that contains data for a single icon. +// The WmIcon method will return a list of these, since a single +// client can specify multiple icons of varying sizes. +type WmIcon struct { + Width uint + Height uint + Data []uint +} + +// _NET_WM_ICON get +func WmIconGet(xu *xgbutil.XUtil, win xproto.Window) ([]WmIcon, error) { + icon, err := xprop.PropValNums(xprop.GetProperty(xu, win, "_NET_WM_ICON")) + if err != nil { + return nil, err + } + + wmicons := make([]WmIcon, 0) + start := uint(0) + for int(start) < len(icon) { + w, h := icon[start], icon[start+1] + upto := w * h + + wmicon := WmIcon{ + Width: w, + Height: h, + Data: icon[(start + 2):(start + upto + 2)], + } + wmicons = append(wmicons, wmicon) + + start += upto + 2 + } + + return wmicons, nil +} + +// _NET_WM_ICON set +func WmIconSet(xu *xgbutil.XUtil, win xproto.Window, icons []WmIcon) error { + raw := make([]uint, 0, 10000) // start big + for _, icon := range icons { + raw = append(raw, icon.Width, icon.Height) + raw = append(raw, icon.Data...) + } + + return xprop.ChangeProp32(xu, win, "_NET_WM_ICON", "CARDINAL", raw...) +} + +// WmIconGeometry struct organizes the information pertaining to the +// _NET_WM_ICON_GEOMETRY property. Namely, x, y, width and height. +type WmIconGeometry struct { + X int + Y int + Width uint + Height uint +} + +// _NET_WM_ICON_GEOMETRY get +func WmIconGeometryGet(xu *xgbutil.XUtil, + win xproto.Window) (*WmIconGeometry, error) { + + geom, err := xprop.PropValNums(xprop.GetProperty(xu, win, + "_NET_WM_ICON_GEOMETRY")) + if err != nil { + return nil, err + } + + return &WmIconGeometry{ + X: int(geom[0]), + Y: int(geom[1]), + Width: geom[2], + Height: geom[3], + }, nil +} + +// _NET_WM_ICON_GEOMETRY set +func WmIconGeometrySet(xu *xgbutil.XUtil, win xproto.Window, + geom *WmIconGeometry) error { + + rawGeom := make([]uint, 4) + rawGeom[0] = uint(geom.X) + rawGeom[1] = uint(geom.Y) + rawGeom[2] = geom.Width + rawGeom[3] = geom.Height + + return xprop.ChangeProp32(xu, win, "_NET_WM_ICON_GEOMETRY", "CARDINAL", + rawGeom...) +} + +// _NET_WM_ICON_NAME get +func WmIconNameGet(xu *xgbutil.XUtil, win xproto.Window) (string, error) { + return xprop.PropValStr(xprop.GetProperty(xu, win, "_NET_WM_ICON_NAME")) +} + +// _NET_WM_ICON_NAME set +func WmIconNameSet(xu *xgbutil.XUtil, win xproto.Window, name string) error { + return xprop.ChangeProp(xu, win, 8, "_NET_WM_ICON_NAME", "UTF8_STRING", + []byte(name)) +} + +// _NET_WM_MOVERESIZE constants +const ( + SizeTopLeft = iota + SizeTop + SizeTopRight + SizeRight + SizeBottomRight + SizeBottom + SizeBottomLeft + SizeLeft + Move + SizeKeyboard + MoveKeyboard + Cancel + Infer // special for Wingo. DO NOT USE. +) + +// _NET_WM_MOVERESIZE req +func WmMoveresize(xu *xgbutil.XUtil, win xproto.Window, direction int) error { + return WmMoveresizeExtra(xu, win, direction, 0, 0, 0, 2) +} + +// _NET_WM_MOVERESIZE req extra +func WmMoveresizeExtra(xu *xgbutil.XUtil, win xproto.Window, direction, + xRoot, yRoot, button, source int) error { + + return ClientEvent(xu, win, "_NET_WM_MOVERESIZE", + xRoot, yRoot, direction, button, source) +} + +// _NET_WM_NAME get +func WmNameGet(xu *xgbutil.XUtil, win xproto.Window) (string, error) { + return xprop.PropValStr(xprop.GetProperty(xu, win, "_NET_WM_NAME")) +} + +// _NET_WM_NAME set +func WmNameSet(xu *xgbutil.XUtil, win xproto.Window, name string) error { + return xprop.ChangeProp(xu, win, 8, "_NET_WM_NAME", "UTF8_STRING", + []byte(name)) +} + +// WmOpaqueRegion organizes information related to the _NET_WM_OPAQUE_REGION +// property. Namely, the x, y, width and height of an opaque rectangle +// relative to the client window. +type WmOpaqueRegion struct { + X int + Y int + Width uint + Height uint +} + +// _NET_WM_OPAQUE_REGION get +func WmOpaqueRegionGet(xu *xgbutil.XUtil, + win xproto.Window) ([]WmOpaqueRegion, error) { + + raw, err := xprop.PropValNums(xprop.GetProperty(xu, win, + "_NET_WM_OPAQUE_REGION")) + if err != nil { + return nil, err + } + + regions := make([]WmOpaqueRegion, len(raw)/4) + for i, _ := range regions { + regions[i] = WmOpaqueRegion{ + X: int(raw[i*4+0]), + Y: int(raw[i*4+1]), + Width: raw[i*4+2], + Height: raw[i*4+3], + } + } + return regions, nil +} + +// _NET_WM_OPAQUE_REGION set +func WmOpaqueRegionSet(xu *xgbutil.XUtil, win xproto.Window, + regions []WmOpaqueRegion) error { + + raw := make([]uint, len(regions)*4) + + for i, region := range regions { + raw[i*4+0] = uint(region.X) + raw[i*4+1] = uint(region.Y) + raw[i*4+2] = region.Width + raw[i*4+3] = region.Height + } + + return xprop.ChangeProp32(xu, win, "_NET_WM_OPAQUE_REGION", "CARDINAL", + raw...) +} + +// _NET_WM_PID get +func WmPidGet(xu *xgbutil.XUtil, win xproto.Window) (uint, error) { + return xprop.PropValNum(xprop.GetProperty(xu, win, "_NET_WM_PID")) +} + +// _NET_WM_PID set +func WmPidSet(xu *xgbutil.XUtil, win xproto.Window, pid uint) error { + return xprop.ChangeProp32(xu, win, "_NET_WM_PID", "CARDINAL", pid) +} + +// _NET_WM_PING req +func WmPing(xu *xgbutil.XUtil, win xproto.Window, response bool) error { + return WmPingExtra(xu, win, response, 0) +} + +// _NET_WM_PING req extra +func WmPingExtra(xu *xgbutil.XUtil, win xproto.Window, response bool, + time xproto.Timestamp) error { + + pingAtom, err := xprop.Atm(xu, "_NET_WM_PING") + if err != nil { + return err + } + + var evWindow xproto.Window + if response { + evWindow = xu.RootWin() + } else { + evWindow = win + } + + return ClientEvent(xu, evWindow, "WM_PROTOCOLS", int(pingAtom), int(time), + int(win)) +} + +// _NET_WM_STATE constants for state toggling +// These correspond to the "action" parameter. +const ( + StateRemove = iota + StateAdd + StateToggle +) + +// _NET_WM_STATE get +func WmStateGet(xu *xgbutil.XUtil, win xproto.Window) ([]string, error) { + raw, err := xprop.GetProperty(xu, win, "_NET_WM_STATE") + return xprop.PropValAtoms(xu, raw, err) +} + +// _NET_WM_STATE set +func WmStateSet(xu *xgbutil.XUtil, win xproto.Window, + atomNames []string) error { + + atoms, err := xprop.StrToAtoms(xu, atomNames) + if err != nil { + return err + } + + return xprop.ChangeProp32(xu, win, "_NET_WM_STATE", "ATOM", atoms...) +} + +// _NET_WM_STATE req +func WmStateReq(xu *xgbutil.XUtil, win xproto.Window, action int, + atomName string) error { + + return WmStateReqExtra(xu, win, action, atomName, "", 2) +} + +// _NET_WM_STATE req extra +func WmStateReqExtra(xu *xgbutil.XUtil, win xproto.Window, action int, + first string, second string, source int) (err error) { + + var atom1, atom2 xproto.Atom + + atom1, err = xprop.Atom(xu, first, false) + if err != nil { + return err + } + + if len(second) > 0 { + atom2, err = xprop.Atom(xu, second, false) + if err != nil { + return err + } + } else { + atom2 = 0 + } + + return ClientEvent(xu, win, "_NET_WM_STATE", action, int(atom1), int(atom2), + source) +} + +// WmStrut struct organizes information for the _NET_WM_STRUT property. +// Namely, it encapsulates its four values: left, right, top and bottom. +type WmStrut struct { + Left uint + Right uint + Top uint + Bottom uint +} + +// _NET_WM_STRUT get +func WmStrutGet(xu *xgbutil.XUtil, win xproto.Window) (*WmStrut, error) { + struts, err := xprop.PropValNums(xprop.GetProperty(xu, win, + "_NET_WM_STRUT")) + if err != nil { + return nil, err + } + + return &WmStrut{ + Left: struts[0], + Right: struts[1], + Top: struts[2], + Bottom: struts[3], + }, nil +} + +// _NET_WM_STRUT set +func WmStrutSet(xu *xgbutil.XUtil, win xproto.Window, struts *WmStrut) error { + rawStruts := make([]uint, 4) + rawStruts[0] = struts.Left + rawStruts[1] = struts.Right + rawStruts[2] = struts.Top + rawStruts[3] = struts.Bottom + + return xprop.ChangeProp32(xu, win, "_NET_WM_STRUT", "CARDINAL", + rawStruts...) +} + +// WmStrutPartial struct organizes information for the _NET_WM_STRUT_PARTIAL +// property. Namely, it encapsulates its twelve values: left, right, top, +// bottom, left_start_y, left_end_y, right_start_y, right_end_y, +// top_start_x, top_end_x, bottom_start_x, and bottom_end_x. +type WmStrutPartial struct { + Left, Right, Top, Bottom uint + LeftStartY, LeftEndY, RightStartY, RightEndY uint + TopStartX, TopEndX, BottomStartX, BottomEndX uint +} + +// _NET_WM_STRUT_PARTIAL get +func WmStrutPartialGet(xu *xgbutil.XUtil, + win xproto.Window) (*WmStrutPartial, error) { + + struts, err := xprop.PropValNums(xprop.GetProperty(xu, win, + "_NET_WM_STRUT_PARTIAL")) + if err != nil { + return nil, err + } + + return &WmStrutPartial{ + Left: struts[0], Right: struts[1], Top: struts[2], Bottom: struts[3], + LeftStartY: struts[4], LeftEndY: struts[5], + RightStartY: struts[6], RightEndY: struts[7], + TopStartX: struts[8], TopEndX: struts[9], + BottomStartX: struts[10], BottomEndX: struts[11], + }, nil +} + +// _NET_WM_STRUT_PARTIAL set +func WmStrutPartialSet(xu *xgbutil.XUtil, win xproto.Window, + struts *WmStrutPartial) error { + + rawStruts := make([]uint, 12) + rawStruts[0] = struts.Left + rawStruts[1] = struts.Right + rawStruts[2] = struts.Top + rawStruts[3] = struts.Bottom + rawStruts[4] = struts.LeftStartY + rawStruts[5] = struts.LeftEndY + rawStruts[6] = struts.RightStartY + rawStruts[7] = struts.RightEndY + rawStruts[8] = struts.TopStartX + rawStruts[9] = struts.TopEndX + rawStruts[10] = struts.BottomStartX + rawStruts[11] = struts.BottomEndX + + return xprop.ChangeProp32(xu, win, "_NET_WM_STRUT_PARTIAL", "CARDINAL", + rawStruts...) +} + +// _NET_WM_SYNC_REQUEST req +func WmSyncRequest(xu *xgbutil.XUtil, win xproto.Window, req_num uint64) error { + return WmSyncRequestExtra(xu, win, req_num, 0) +} + +// _NET_WM_SYNC_REQUEST req extra +func WmSyncRequestExtra(xu *xgbutil.XUtil, win xproto.Window, reqNum uint64, + time xproto.Timestamp) error { + + syncReq, err := xprop.Atm(xu, "_NET_WM_SYNC_REQUEST") + if err != nil { + return err + } + + high := int(reqNum >> 32) + low := int(reqNum<<32 ^ reqNum) + + return ClientEvent(xu, win, "WM_PROTOCOLS", int(syncReq), int(time), + low, high) +} + +// _NET_WM_SYNC_REQUEST_COUNTER get +// I'm pretty sure this needs 64 bit integers, but I'm not quite sure +// how to go about that yet. Any ideas? +func WmSyncRequestCounter(xu *xgbutil.XUtil, win xproto.Window) (uint, error) { + return xprop.PropValNum(xprop.GetProperty(xu, win, + "_NET_WM_SYNC_REQUEST_COUNTER")) +} + +// _NET_WM_SYNC_REQUEST_COUNTER set +// I'm pretty sure this needs 64 bit integers, but I'm not quite sure +// how to go about that yet. Any ideas? +func WmSyncRequestCounterSet(xu *xgbutil.XUtil, win xproto.Window, + counter uint) error { + + return xprop.ChangeProp32(xu, win, "_NET_WM_SYNC_REQUEST_COUNTER", + "CARDINAL", counter) +} + +// _NET_WM_USER_TIME get +func WmUserTimeGet(xu *xgbutil.XUtil, win xproto.Window) (uint, error) { + return xprop.PropValNum(xprop.GetProperty(xu, win, "_NET_WM_USER_TIME")) +} + +// _NET_WM_USER_TIME set +func WmUserTimeSet(xu *xgbutil.XUtil, win xproto.Window, userTime uint) error { + return xprop.ChangeProp32(xu, win, "_NET_WM_USER_TIME", "CARDINAL", + userTime) +} + +// _NET_WM_USER_TIME_WINDOW get +func WmUserTimeWindowGet(xu *xgbutil.XUtil, + win xproto.Window) (xproto.Window, error) { + + return xprop.PropValWindow(xprop.GetProperty(xu, win, + "_NET_WM_USER_TIME_WINDOW")) +} + +// _NET_WM_USER_TIME set +func WmUserTimeWindowSet(xu *xgbutil.XUtil, win xproto.Window, + timeWin xproto.Window) error { + + return xprop.ChangeProp32(xu, win, "_NET_WM_USER_TIME_WINDOW", "CARDINAL", + uint(timeWin)) +} + +// _NET_WM_VISIBLE_ICON_NAME get +func WmVisibleIconNameGet(xu *xgbutil.XUtil, + win xproto.Window) (string, error) { + + return xprop.PropValStr(xprop.GetProperty(xu, win, + "_NET_WM_VISIBLE_ICON_NAME")) +} + +// _NET_WM_VISIBLE_ICON_NAME set +func WmVisibleIconNameSet(xu *xgbutil.XUtil, win xproto.Window, + name string) error { + + return xprop.ChangeProp(xu, win, 8, "_NET_WM_VISIBLE_ICON_NAME", + "UTF8_STRING", []byte(name)) +} + +// _NET_WM_VISIBLE_NAME get +func WmVisibleNameGet(xu *xgbutil.XUtil, win xproto.Window) (string, error) { + return xprop.PropValStr(xprop.GetProperty(xu, win, "_NET_WM_VISIBLE_NAME")) +} + +// _NET_WM_VISIBLE_NAME set +func WmVisibleNameSet(xu *xgbutil.XUtil, win xproto.Window, name string) error { + return xprop.ChangeProp(xu, win, 8, "_NET_WM_VISIBLE_NAME", "UTF8_STRING", + []byte(name)) +} + +// _NET_WM_WINDOW_OPACITY get +// This isn't part of the EWMH spec, but is widely used by drop in +// compositing managers (i.e., xcompmgr, cairo-compmgr, etc.). +// This property is typically set not on a client window, but the *parent* +// of a client window in reparenting window managers. +// The float returned will be in the range [0.0, 1.0] where 0.0 is completely +// transparent and 1.0 is completely opaque. +func WmWindowOpacityGet(xu *xgbutil.XUtil, win xproto.Window) (float64, error) { + intOpacity, err := xprop.PropValNum( + xprop.GetProperty(xu, win, "_NET_WM_WINDOW_OPACITY")) + if err != nil { + return 0, err + } + + return float64(uint(intOpacity)) / float64(0xffffffff), nil +} + +// _NET_WM_WINDOW_OPACITY set +func WmWindowOpacitySet(xu *xgbutil.XUtil, win xproto.Window, + opacity float64) error { + + return xprop.ChangeProp32(xu, win, "_NET_WM_WINDOW_OPACITY", "CARDINAL", + uint(opacity*0xffffffff)) +} + +// _NET_WM_WINDOW_TYPE get +func WmWindowTypeGet(xu *xgbutil.XUtil, win xproto.Window) ([]string, error) { + raw, err := xprop.GetProperty(xu, win, "_NET_WM_WINDOW_TYPE") + return xprop.PropValAtoms(xu, raw, err) +} + +// _NET_WM_WINDOW_TYPE set +// This will create any atoms used in 'atomNames' if they don't already exist. +func WmWindowTypeSet(xu *xgbutil.XUtil, win xproto.Window, + atomNames []string) error { + + atoms, err := xprop.StrToAtoms(xu, atomNames) + if err != nil { + return err + } + return xprop.ChangeProp32(xu, win, "_NET_WM_WINDOW_TYPE", "ATOM", atoms...) +} + +// Workarea is a struct that represents a rectangle as a bounding box of +// a single desktop. So there should be as many Workarea structs as there +// are desktops. +type Workarea struct { + X int + Y int + Width uint + Height uint +} + +// _NET_WORKAREA get +func WorkareaGet(xu *xgbutil.XUtil) ([]Workarea, error) { + rects, err := xprop.PropValNums(xprop.GetProperty(xu, xu.RootWin(), + "_NET_WORKAREA")) + if err != nil { + return nil, err + } + + workareas := make([]Workarea, len(rects)/4) + for i, _ := range workareas { + workareas[i] = Workarea{ + X: int(rects[i*4]), + Y: int(rects[i*4+1]), + Width: rects[i*4+2], + Height: rects[i*4+3], + } + } + return workareas, nil +} + +// _NET_WORKAREA set +func WorkareaSet(xu *xgbutil.XUtil, workareas []Workarea) error { + rects := make([]uint, len(workareas)*4) + for i, workarea := range workareas { + rects[i*4+0] = uint(workarea.X) + rects[i*4+1] = uint(workarea.Y) + rects[i*4+2] = workarea.Width + rects[i*4+3] = workarea.Height + } + + return xprop.ChangeProp32(xu, xu.RootWin(), "_NET_WORKAREA", "CARDINAL", + rects...) +} diff --git a/vendor/github.com/BurntSushi/xgbutil/ewmh/winman.go b/vendor/github.com/BurntSushi/xgbutil/ewmh/winman.go new file mode 100644 index 0000000..b5ec80e --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/ewmh/winman.go @@ -0,0 +1,31 @@ +package ewmh + +import ( + "fmt" + + "github.com/BurntSushi/xgbutil" +) + +// GetEwmhWM uses the EWMH spec to find if a conforming window manager +// is currently running or not. If it is, then its name will be returned. +// Otherwise, an error will be returned explaining why one couldn't be found. +func GetEwmhWM(xu *xgbutil.XUtil) (string, error) { + childCheck, err := SupportingWmCheckGet(xu, xu.RootWin()) + if err != nil { + return "", fmt.Errorf("GetEwmhWM: Failed because: %s", err) + } + + childCheck2, err := SupportingWmCheckGet(xu, childCheck) + if err != nil { + return "", fmt.Errorf("GetEwmhWM: Failed because: %s", err) + } + + if childCheck != childCheck2 { + return "", fmt.Errorf( + "GetEwmhWM: _NET_SUPPORTING_WM_CHECK value on the root window "+ + "(%x) does not match _NET_SUPPORTING_WM_CHECK value "+ + "on the child window (%x).", childCheck, childCheck2) + } + + return WmNameGet(xu, childCheck) +} diff --git a/vendor/github.com/BurntSushi/xgbutil/icccm/doc.go b/vendor/github.com/BurntSushi/xgbutil/icccm/doc.go new file mode 100644 index 0000000..0aa16ea --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/icccm/doc.go @@ -0,0 +1,61 @@ +/* +Package icccm provides an API for a portion of the ICCCM, namely, getters +and setters for many of the properties specified in the ICCCM. There is also a +smattering of support for other protocols specified by ICCCM. For example, to +satisfy the WM_DELETE_WINDOW protocol, package icccm provides 'IsDeleteProtocol' +which returns whether a ClientMessage event satisfies the WM_DELETE_WINDOW +protocol. + +If a property has values that aren't simple strings or integers, struct types +are provided to organize the data. In particular, WM_NORMAL_HINTS and WM_HINTS. + +Also note that properties like WM_NORMAL_HINTS and WM_HINTS contain a 'Flags' +field (a bit mask) that specifies which values are "active". This is of +importance when setting and reading WM_NORMAL_HINTS and WM_HINTS; one must make +sure the appropriate bit is set in Flags. + +For example, you might want to check if a window has specified a resize +increment in the WM_NORMAL_HINTS property. The values in the corresponding +NormalHints struct are WidthInc and HeightInc. So to check if such values exist +*and* should be used: + + normalHints, err := icccm.WmNormalHintsGet(XUtilValue, window-id) + if err != nil { + // handle error + } + if normalHints.Flags&icccm.SizeHintPResizeInc > 0 { + // Use normalHints.WidthInc and normalHints.HeightInc + } + +When you should use icccm + +Although the ICCCM is extremely old, a lot of it is still used. In fact, the +EWMH spec itself specifically states that the ICCCM should still be used unless +otherwise noted by the EWMH. For example, WM_HINTS and WM_NORMAL_HINTS are +still used, but _NET_WM_NAME replaces WM_NAME. + +With that said, many applications (like xterm or LibreOffice) have not been +updated to be fully EWMH compliant. Therefore, code that finds a window's name +often looks like this: + + winName, err := ewmh.WmNameGet(XUtilValue, window-id) + if err != nil || winName == "" { + winName, err = icccm.WmNameGet(XUtilValue, window-id) + if err != nill || winName == "" { + winName = "N/A" + } + } + +Something similar can be said for the _NET_WM_ICON and the IconPixmap field +in WM_HINTS. + +Naming scheme + +The naming scheme is precisely the same as the one found in the ewmh package. +The documentation for the ewmh package describes the naming scheme in more +detail. The only difference (currently) is that the icccm package only contains +functions ending in "Get" and "Set". It is planned to add "Req" functions. (An +example of a Req function would be to send a ClientMessage implementing the +WM_DELETE_WINDOW protocol to a client window.) +*/ +package icccm diff --git a/vendor/github.com/BurntSushi/xgbutil/icccm/icccm.go b/vendor/github.com/BurntSushi/xgbutil/icccm/icccm.go new file mode 100644 index 0000000..95db745 --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/icccm/icccm.go @@ -0,0 +1,358 @@ +package icccm + +import ( + "fmt" + + "github.com/BurntSushi/xgb/xproto" + + "github.com/BurntSushi/xgbutil" + "github.com/BurntSushi/xgbutil/xprop" +) + +const ( + HintInput = (1 << iota) + HintState + HintIconPixmap + HintIconWindow + HintIconPosition + HintIconMask + HintWindowGroup + HintMessage + HintUrgency +) + +const ( + SizeHintUSPosition = (1 << iota) + SizeHintUSSize + SizeHintPPosition + SizeHintPSize + SizeHintPMinSize + SizeHintPMaxSize + SizeHintPResizeInc + SizeHintPAspect + SizeHintPBaseSize + SizeHintPWinGravity +) + +const ( + StateWithdrawn = iota + StateNormal + StateZoomed + StateIconic + StateInactive +) + +// WM_NAME get +func WmNameGet(xu *xgbutil.XUtil, win xproto.Window) (string, error) { + return xprop.PropValStr(xprop.GetProperty(xu, win, "WM_NAME")) +} + +// WM_NAME set +func WmNameSet(xu *xgbutil.XUtil, win xproto.Window, name string) error { + return xprop.ChangeProp(xu, win, 8, "WM_NAME", "STRING", ([]byte)(name)) +} + +// WM_ICON_NAME get +func WmIconNameGet(xu *xgbutil.XUtil, win xproto.Window) (string, error) { + return xprop.PropValStr(xprop.GetProperty(xu, win, "WM_ICON_NAME")) +} + +// WM_ICON_NAME set +func WmIconNameSet(xu *xgbutil.XUtil, win xproto.Window, name string) error { + return xprop.ChangeProp(xu, win, 8, "WM_ICON_NAME", "STRING", + ([]byte)(name)) +} + +// NormalHints is a struct that organizes the information related to the +// WM_NORMAL_HINTS property. Please see the ICCCM spec for more details. +type NormalHints struct { + Flags uint + X, Y int + Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight uint + WidthInc, HeightInc uint + MinAspectNum, MinAspectDen, MaxAspectNum, MaxAspectDen uint + BaseWidth, BaseHeight, WinGravity uint +} + +// WM_NORMAL_HINTS get +func WmNormalHintsGet(xu *xgbutil.XUtil, + win xproto.Window) (nh *NormalHints, err error) { + + lenExpect := 18 + hints, err := xprop.PropValNums(xprop.GetProperty(xu, win, + "WM_NORMAL_HINTS")) + if err != nil { + return nil, err + } + if len(hints) != lenExpect { + return nil, + fmt.Errorf("WmNormalHint: There are %d fields in WM_NORMAL_HINTS, "+ + "but xgbutil expects %d.", len(hints), lenExpect) + } + + nh = &NormalHints{} + nh.Flags = hints[0] + nh.X = int(hints[1]) + nh.Y = int(hints[2]) + nh.Width = hints[3] + nh.Height = hints[4] + nh.MinWidth = hints[5] + nh.MinHeight = hints[6] + nh.MaxWidth = hints[7] + nh.MaxHeight = hints[8] + nh.WidthInc = hints[9] + nh.HeightInc = hints[10] + nh.MinAspectNum = hints[11] + nh.MinAspectDen = hints[12] + nh.MaxAspectNum = hints[13] + nh.MaxAspectDen = hints[14] + nh.BaseWidth = hints[15] + nh.BaseHeight = hints[16] + nh.WinGravity = hints[17] + + if nh.WinGravity <= 0 { + nh.WinGravity = xproto.GravityNorthWest + } + + return nh, nil +} + +// WM_NORMAL_HINTS set +// Make sure to set the flags in the NormalHints struct correctly! +func WmNormalHintsSet(xu *xgbutil.XUtil, win xproto.Window, + nh *NormalHints) error { + + raw := []uint{ + nh.Flags, + uint(nh.X), uint(nh.Y), nh.Width, nh.Height, + nh.MinWidth, nh.MinHeight, + nh.MaxWidth, nh.MaxHeight, + nh.WidthInc, nh.HeightInc, + nh.MinAspectNum, nh.MinAspectDen, + nh.MaxAspectNum, nh.MaxAspectDen, + nh.BaseWidth, nh.BaseHeight, + nh.WinGravity, + } + return xprop.ChangeProp32(xu, win, "WM_NORMAL_HINTS", "WM_SIZE_HINTS", + raw...) +} + +// Hints is a struct that organizes information related to the WM_HINTS +// property. Once again, I refer you to the ICCCM spec for documentation. +type Hints struct { + Flags uint + Input, InitialState uint + IconX, IconY int + IconPixmap, IconMask xproto.Pixmap + WindowGroup, IconWindow xproto.Window +} + +// WM_HINTS get +func WmHintsGet(xu *xgbutil.XUtil, + win xproto.Window) (hints *Hints, err error) { + + lenExpect := 9 + raw, err := xprop.PropValNums(xprop.GetProperty(xu, win, "WM_HINTS")) + if err != nil { + return nil, err + } + if len(raw) != lenExpect { + return nil, + fmt.Errorf("WmHints: There are %d fields in "+ + "WM_HINTS, but xgbutil expects %d.", len(raw), lenExpect) + } + + hints = &Hints{} + hints.Flags = raw[0] + hints.Input = raw[1] + hints.InitialState = raw[2] + hints.IconPixmap = xproto.Pixmap(raw[3]) + hints.IconWindow = xproto.Window(raw[4]) + hints.IconX = int(raw[5]) + hints.IconY = int(raw[6]) + hints.IconMask = xproto.Pixmap(raw[7]) + hints.WindowGroup = xproto.Window(raw[8]) + + return hints, nil +} + +// WM_HINTS set +// Make sure to set the flags in the Hints struct correctly! +func WmHintsSet(xu *xgbutil.XUtil, win xproto.Window, hints *Hints) error { + raw := []uint{ + hints.Flags, hints.Input, hints.InitialState, + uint(hints.IconPixmap), uint(hints.IconWindow), + uint(hints.IconX), uint(hints.IconY), + uint(hints.IconMask), + uint(hints.WindowGroup), + } + return xprop.ChangeProp32(xu, win, "WM_HINTS", "WM_HINTS", raw...) +} + +// WmClass struct contains two data points: +// the instance and a class of a window. +type WmClass struct { + Instance, Class string +} + +// WM_CLASS get +func WmClassGet(xu *xgbutil.XUtil, win xproto.Window) (*WmClass, error) { + raw, err := xprop.PropValStrs(xprop.GetProperty(xu, win, "WM_CLASS")) + if err != nil { + return nil, err + } + if len(raw) != 2 { + return nil, + fmt.Errorf("WmClass: Two string make up WM_CLASS, but "+ + "xgbutil found %d in '%v'.", len(raw), raw) + } + + return &WmClass{ + Instance: raw[0], + Class: raw[1], + }, nil +} + +// WM_CLASS set +func WmClassSet(xu *xgbutil.XUtil, win xproto.Window, class *WmClass) error { + raw := make([]byte, len(class.Instance)+len(class.Class)+2) + copy(raw, class.Instance) + copy(raw[(len(class.Instance)+1):], class.Class) + + return xprop.ChangeProp(xu, win, 8, "WM_CLASS", "STRING", raw) +} + +// WM_TRANSIENT_FOR get +func WmTransientForGet(xu *xgbutil.XUtil, + win xproto.Window) (xproto.Window, error) { + + return xprop.PropValWindow(xprop.GetProperty(xu, win, "WM_TRANSIENT_FOR")) +} + +// WM_TRANSIENT_FOR set +func WmTransientForSet(xu *xgbutil.XUtil, win xproto.Window, + transient xproto.Window) error { + + return xprop.ChangeProp32(xu, win, "WM_TRANSIENT_FOR", "WINDOW", + uint(transient)) +} + +// WM_PROTOCOLS get +func WmProtocolsGet(xu *xgbutil.XUtil, win xproto.Window) ([]string, error) { + raw, err := xprop.GetProperty(xu, win, "WM_PROTOCOLS") + return xprop.PropValAtoms(xu, raw, err) +} + +// WM_PROTOCOLS set +func WmProtocolsSet(xu *xgbutil.XUtil, win xproto.Window, + atomNames []string) error { + + atoms, err := xprop.StrToAtoms(xu, atomNames) + if err != nil { + return err + } + return xprop.ChangeProp32(xu, win, "WM_PROTOCOLS", "ATOM", atoms...) +} + +// WM_COLORMAP_WINDOWS get +func WmColormapWindowsGet(xu *xgbutil.XUtil, + win xproto.Window) ([]xproto.Window, error) { + + return xprop.PropValWindows(xprop.GetProperty(xu, win, + "WM_COLORMAP_WINDOWS")) +} + +// WM_COLORMAP_WINDOWS set +func WmColormapWindowsSet(xu *xgbutil.XUtil, win xproto.Window, + windows []xproto.Window) error { + + return xprop.ChangeProp32(xu, win, "WM_COLORMAP_WINDOWS", "WINDOW", + xprop.WindowToInt(windows)...) +} + +// WM_CLIENT_MACHINE get +func WmClientMachineGet(xu *xgbutil.XUtil, win xproto.Window) (string, error) { + return xprop.PropValStr(xprop.GetProperty(xu, win, "WM_CLIENT_MACHINE")) +} + +// WM_CLIENT_MACHINE set +func WmClientMachineSet(xu *xgbutil.XUtil, win xproto.Window, + client string) error { + + return xprop.ChangeProp(xu, win, 8, "WM_CLIENT_MACHINE", "STRING", + ([]byte)(client)) +} + +// WmState is a struct that organizes information related to the WM_STATE +// property. Namely, the state (corresponding to a State* constant in this file) +// and the icon window (probably not used). +type WmState struct { + State uint + Icon xproto.Window +} + +// WM_STATE get +func WmStateGet(xu *xgbutil.XUtil, win xproto.Window) (*WmState, error) { + raw, err := xprop.PropValNums(xprop.GetProperty(xu, win, "WM_STATE")) + if err != nil { + return nil, err + } + if len(raw) != 2 { + return nil, + fmt.Errorf("WmState: Expected two integers in WM_STATE property "+ + "but xgbutil found %d in '%v'.", len(raw), raw) + } + + return &WmState{ + State: raw[0], + Icon: xproto.Window(raw[1]), + }, nil +} + +// WM_STATE set +func WmStateSet(xu *xgbutil.XUtil, win xproto.Window, state *WmState) error { + raw := []uint{ + state.State, + uint(state.Icon), + } + + return xprop.ChangeProp32(xu, win, "WM_STATE", "WM_STATE", raw...) +} + +// IconSize is a struct the organizes information related to the WM_ICON_SIZE +// property. Mostly info about its dimensions. +type IconSize struct { + MinWidth, MinHeight, MaxWidth, MaxHeight, WidthInc, HeightInc uint +} + +// WM_ICON_SIZE get +func WmIconSizeGet(xu *xgbutil.XUtil, win xproto.Window) (*IconSize, error) { + raw, err := xprop.PropValNums(xprop.GetProperty(xu, win, "WM_ICON_SIZE")) + if err != nil { + return nil, err + } + if len(raw) != 6 { + return nil, + fmt.Errorf("WmIconSize: Expected six integers in WM_ICON_SIZE "+ + "property, but xgbutil found "+"%d in '%v'.", len(raw), raw) + } + + return &IconSize{ + MinWidth: raw[0], MinHeight: raw[1], + MaxWidth: raw[2], MaxHeight: raw[3], + WidthInc: raw[4], HeightInc: raw[5], + }, nil +} + +// WM_ICON_SIZE set +func WmIconSizeSet(xu *xgbutil.XUtil, win xproto.Window, + icondim *IconSize) error { + + raw := []uint{ + icondim.MinWidth, icondim.MinHeight, + icondim.MaxWidth, icondim.MaxHeight, + icondim.WidthInc, icondim.HeightInc, + } + + return xprop.ChangeProp32(xu, win, "WM_ICON_SIZE", "WM_ICON_SIZE", raw...) +} diff --git a/vendor/github.com/BurntSushi/xgbutil/icccm/protocols.go b/vendor/github.com/BurntSushi/xgbutil/icccm/protocols.go new file mode 100644 index 0000000..acc8620 --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/icccm/protocols.go @@ -0,0 +1,64 @@ +package icccm + +import ( + "github.com/BurntSushi/xgb/xproto" + + "github.com/BurntSushi/xgbutil" + "github.com/BurntSushi/xgbutil/xevent" + "github.com/BurntSushi/xgbutil/xprop" +) + +// IsDeleteProtocol checks whether a ClientMessage event satisfies the +// WM_DELETE_WINDOW protocol. Namely, the format must be 32, the type must +// be the WM_PROTOCOLS atom, and the first data item must be the atom +// WM_DELETE_WINDOW. +// +// Note that if you're using the xwindow package, you should use the +// WMGracefulClose method instead of directly using IsDeleteProtocol. +func IsDeleteProtocol(X *xgbutil.XUtil, ev xevent.ClientMessageEvent) bool { + // Make sure the Format is 32. (Meaning that each data item is + // 32 bits.) + if ev.Format != 32 { + return false + } + + // Check to make sure the Type atom is WM_PROTOCOLS. + typeName, err := xprop.AtomName(X, ev.Type) + if err != nil || typeName != "WM_PROTOCOLS" { // not what we want + return false + } + + // Check to make sure the first data item is WM_DELETE_WINDOW. + protocolType, err := xprop.AtomName(X, + xproto.Atom(ev.Data.Data32[0])) + if err != nil || protocolType != "WM_DELETE_WINDOW" { + return false + } + + return true +} + +// IsFocusProtocol checks whether a ClientMessage event satisfies the +// WM_TAKE_FOCUS protocol. +func IsFocusProtocol(X *xgbutil.XUtil, ev xevent.ClientMessageEvent) bool { + // Make sure the Format is 32. (Meaning that each data item is + // 32 bits.) + if ev.Format != 32 { + return false + } + + // Check to make sure the Type atom is WM_PROTOCOLS. + typeName, err := xprop.AtomName(X, ev.Type) + if err != nil || typeName != "WM_PROTOCOLS" { // not what we want + return false + } + + // Check to make sure the first data item is WM_TAKE_FOCUS. + protocolType, err := xprop.AtomName(X, + xproto.Atom(ev.Data.Data32[0])) + if err != nil || protocolType != "WM_TAKE_FOCUS" { + return false + } + + return true +} diff --git a/vendor/github.com/BurntSushi/xgbutil/session.vim b/vendor/github.com/BurntSushi/xgbutil/session.vim new file mode 100644 index 0000000..562164b --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/session.vim @@ -0,0 +1 @@ +au BufWritePost *.go silent!make tags > /dev/null 2>&1 diff --git a/vendor/github.com/BurntSushi/xgbutil/types.go b/vendor/github.com/BurntSushi/xgbutil/types.go new file mode 100644 index 0000000..6ea44d8 --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/types.go @@ -0,0 +1,167 @@ +package xgbutil + +/* +types.go contains several types used in the XUtil structure. In an ideal world, +they would be defined in their appropriate packages, but must be defined here +(and exported) for use in some sub-packages. (Namely, xevent, keybind and +mousebind.) +*/ + +import ( + "github.com/BurntSushi/xgb" + "github.com/BurntSushi/xgb/xproto" +) + +// Callback is an interface that should be implemented by event callback +// functions. Namely, to assign a function to a particular event/window +// combination, simply define a function with type 'SomeEventFun' (pre-defined +// in xevent/callback.go), and call the 'Connect' method. +// The 'Run' method is used inside the Main event loop, and shouldn't be used +// by the user. +// Also, it is perfectly legitimate to connect to events that don't specify +// a window (like MappingNotify and KeymapNotify). In this case, simply +// use 'xgbutil.NoWindow' as the window id. +// +// Example to respond to ConfigureNotify events on window 0x1 +// +// xevent.ConfigureNotifyFun( +// func(X *xgbutil.XUtil, e xevent.ConfigureNotifyEvent) { +// fmt.Printf("(%d, %d) %dx%d\n", e.X, e.Y, e.Width, e.Height) +// }).Connect(X, 0x1) +type Callback interface { + // Connect modifies XUtil's state to attach an event handler to a + // particular event. + Connect(xu *XUtil, win xproto.Window) + + // Run is exported for use in the xevent package but should not be + // used by the user. (It is used to run the callback function in the + // main event loop.) + Run(xu *XUtil, ev interface{}) +} + +// CallbackHook works similarly to the more general Callback, but it is +// for hooks into the main xevent loop. As such it does not get attached +// to a window. +type CallbackHook interface { + // Connect connects this hook to the main loop of the passed XUtil + // instance. + Connect(xu *XUtil) + + // Run is exported for use in the xevent package, but should not be + // used by the user. It should return true if it's ok to process + // the event as usual, or false if it should be suppressed. + Run(xu *XUtil, ev interface{}) bool +} + +// CallbackKey works similarly to the more general Callback, but it adds +// parameters specific to key bindings. +type CallbackKey interface { + // Connect modifies XUtil's state to attach an event handler to a + // particular key press. If grab is true, connect will request a passive + // grab. + Connect(xu *XUtil, win xproto.Window, keyStr string, grab bool) error + + // Run is exported for use in the keybind package but should not be + // used by the user. (It is used to run the callback function in the + // main event loop. + Run(xu *XUtil, ev interface{}) +} + +// CallbackMouse works similarly to the more general Callback, but it adds +// parameters specific to mouse bindings. +type CallbackMouse interface { + // Connect modifies XUtil's state to attach an event handler to a + // particular button press. + // If sync is true, the grab will be synchronous. (This will require a + // call to xproto.AllowEvents in response, otherwise no further events + // will be processed and your program will lock.) + // If grab is true, connect will request a passive grab. + Connect(xu *XUtil, win xproto.Window, buttonStr string, + sync bool, grab bool) error + + // Run is exported for use in the mousebind package but should not be + // used by the user. (It is used to run the callback function in the + // main event loop.) + Run(xu *XUtil, ev interface{}) +} + +// KeyKey is the type of the key in the map of keybindings. +// It essentially represents the tuple +// (event type, window id, modifier, keycode). +// It is exported for use in the keybind package. It should not be used. +type KeyKey struct { + Evtype int + Win xproto.Window + Mod uint16 + Code xproto.Keycode +} + +// KeyString is the type of a key binding string used to connect to particular +// key combinations. A list of all such key strings is maintained in order to +// rebind keys when the keyboard mapping has been changed. +type KeyString struct { + Str string + Callback CallbackKey + Evtype int + Win xproto.Window + Grab bool +} + +// MouseKey is the type of the key in the map of mouse bindings. +// It essentially represents the tuple +// (event type, window id, modifier, button). +// It is exported for use in the mousebind package. It should not be used. +type MouseKey struct { + Evtype int + Win xproto.Window + Mod uint16 + Button xproto.Button +} + +// KeyboardMapping embeds a keyboard mapping reply from XGB. +// It should be retrieved using keybind.KeyMapGet, if necessary. +// xgbutil tries quite hard to absolve you from ever having to use this. +// A keyboard mapping is a table that maps keycodes to one or more keysyms. +type KeyboardMapping struct { + *xproto.GetKeyboardMappingReply +} + +// ModifierMapping embeds a modifier mapping reply from XGB. +// It should be retrieved using keybind.ModMapGet, if necessary. +// xgbutil tries quite hard to absolve you from ever having to use this. +// A modifier mapping is a table that maps modifiers to one or more keycodes. +type ModifierMapping struct { + *xproto.GetModifierMappingReply +} + +// ErrorHandlerFun is the type of function required to handle errors that +// come in through the main event loop. +// For example, to set a new error handler, use: +// +// xevent.ErrorHandlerSet(xgbutil.ErrorHandlerFun( +// func(err xgb.Error) { +// // do something with err +// })) +type ErrorHandlerFun func(err xgb.Error) + +// EventOrError is a struct that contains either an event value or an error +// value. It is an error to contain both. Containing neither indicates an +// error too. +// This is exported for use in the xevent package. You shouldn't have any +// direct contact with values of this type, unless you need to inspect the +// queue directly with xevent.Peek. +type EventOrError struct { + Event xgb.Event + Err xgb.Error +} + +// MouseDragFun is the kind of function used on each dragging step +// and at the end of a drag. +type MouseDragFun func(xu *XUtil, rootX, rootY, eventX, eventY int) + +// MouseDragBeginFun is the kind of function used to initialize a drag. +// The difference between this and MouseDragFun is that the begin function +// returns a bool (of whether or not to cancel the drag) and an X resource +// identifier corresponding to a cursor. +type MouseDragBeginFun func(xu *XUtil, rootX, rootY, + eventX, eventY int) (bool, xproto.Cursor) diff --git a/vendor/github.com/BurntSushi/xgbutil/xevent/callback.go b/vendor/github.com/BurntSushi/xgbutil/xevent/callback.go new file mode 100644 index 0000000..1aee451 --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/xevent/callback.go @@ -0,0 +1,389 @@ +package xevent + +/* + Does all the plumbing to allow a simple callback interface for users. + + This file is automatically generated using `scripts/write-events callbacks`. + + Edit it at your peril. +*/ + +import ( + "github.com/BurntSushi/xgb/xproto" + + "github.com/BurntSushi/xgbutil" +) + +type KeyPressFun func(xu *xgbutil.XUtil, event KeyPressEvent) + +func (callback KeyPressFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, KeyPress, win, callback) +} + +func (callback KeyPressFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(KeyPressEvent)) +} + +type KeyReleaseFun func(xu *xgbutil.XUtil, event KeyReleaseEvent) + +func (callback KeyReleaseFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, KeyRelease, win, callback) +} + +func (callback KeyReleaseFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(KeyReleaseEvent)) +} + +type ButtonPressFun func(xu *xgbutil.XUtil, event ButtonPressEvent) + +func (callback ButtonPressFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, ButtonPress, win, callback) +} + +func (callback ButtonPressFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(ButtonPressEvent)) +} + +type ButtonReleaseFun func(xu *xgbutil.XUtil, event ButtonReleaseEvent) + +func (callback ButtonReleaseFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, ButtonRelease, win, callback) +} + +func (callback ButtonReleaseFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(ButtonReleaseEvent)) +} + +type MotionNotifyFun func(xu *xgbutil.XUtil, event MotionNotifyEvent) + +func (callback MotionNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, MotionNotify, win, callback) +} + +func (callback MotionNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(MotionNotifyEvent)) +} + +type EnterNotifyFun func(xu *xgbutil.XUtil, event EnterNotifyEvent) + +func (callback EnterNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, EnterNotify, win, callback) +} + +func (callback EnterNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(EnterNotifyEvent)) +} + +type LeaveNotifyFun func(xu *xgbutil.XUtil, event LeaveNotifyEvent) + +func (callback LeaveNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, LeaveNotify, win, callback) +} + +func (callback LeaveNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(LeaveNotifyEvent)) +} + +type FocusInFun func(xu *xgbutil.XUtil, event FocusInEvent) + +func (callback FocusInFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, FocusIn, win, callback) +} + +func (callback FocusInFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(FocusInEvent)) +} + +type FocusOutFun func(xu *xgbutil.XUtil, event FocusOutEvent) + +func (callback FocusOutFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, FocusOut, win, callback) +} + +func (callback FocusOutFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(FocusOutEvent)) +} + +type KeymapNotifyFun func(xu *xgbutil.XUtil, event KeymapNotifyEvent) + +func (callback KeymapNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, KeymapNotify, win, callback) +} + +func (callback KeymapNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(KeymapNotifyEvent)) +} + +type ExposeFun func(xu *xgbutil.XUtil, event ExposeEvent) + +func (callback ExposeFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, Expose, win, callback) +} + +func (callback ExposeFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(ExposeEvent)) +} + +type GraphicsExposureFun func(xu *xgbutil.XUtil, event GraphicsExposureEvent) + +func (callback GraphicsExposureFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, GraphicsExposure, win, callback) +} + +func (callback GraphicsExposureFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(GraphicsExposureEvent)) +} + +type NoExposureFun func(xu *xgbutil.XUtil, event NoExposureEvent) + +func (callback NoExposureFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, NoExposure, win, callback) +} + +func (callback NoExposureFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(NoExposureEvent)) +} + +type VisibilityNotifyFun func(xu *xgbutil.XUtil, event VisibilityNotifyEvent) + +func (callback VisibilityNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, VisibilityNotify, win, callback) +} + +func (callback VisibilityNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(VisibilityNotifyEvent)) +} + +type CreateNotifyFun func(xu *xgbutil.XUtil, event CreateNotifyEvent) + +func (callback CreateNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, CreateNotify, win, callback) +} + +func (callback CreateNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(CreateNotifyEvent)) +} + +type DestroyNotifyFun func(xu *xgbutil.XUtil, event DestroyNotifyEvent) + +func (callback DestroyNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, DestroyNotify, win, callback) +} + +func (callback DestroyNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(DestroyNotifyEvent)) +} + +type UnmapNotifyFun func(xu *xgbutil.XUtil, event UnmapNotifyEvent) + +func (callback UnmapNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, UnmapNotify, win, callback) +} + +func (callback UnmapNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(UnmapNotifyEvent)) +} + +type MapNotifyFun func(xu *xgbutil.XUtil, event MapNotifyEvent) + +func (callback MapNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, MapNotify, win, callback) +} + +func (callback MapNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(MapNotifyEvent)) +} + +type MapRequestFun func(xu *xgbutil.XUtil, event MapRequestEvent) + +func (callback MapRequestFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, MapRequest, win, callback) +} + +func (callback MapRequestFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(MapRequestEvent)) +} + +type ReparentNotifyFun func(xu *xgbutil.XUtil, event ReparentNotifyEvent) + +func (callback ReparentNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, ReparentNotify, win, callback) +} + +func (callback ReparentNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(ReparentNotifyEvent)) +} + +type ConfigureNotifyFun func(xu *xgbutil.XUtil, event ConfigureNotifyEvent) + +func (callback ConfigureNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, ConfigureNotify, win, callback) +} + +func (callback ConfigureNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(ConfigureNotifyEvent)) +} + +type ConfigureRequestFun func(xu *xgbutil.XUtil, event ConfigureRequestEvent) + +func (callback ConfigureRequestFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, ConfigureRequest, win, callback) +} + +func (callback ConfigureRequestFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(ConfigureRequestEvent)) +} + +type GravityNotifyFun func(xu *xgbutil.XUtil, event GravityNotifyEvent) + +func (callback GravityNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, GravityNotify, win, callback) +} + +func (callback GravityNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(GravityNotifyEvent)) +} + +type ResizeRequestFun func(xu *xgbutil.XUtil, event ResizeRequestEvent) + +func (callback ResizeRequestFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, ResizeRequest, win, callback) +} + +func (callback ResizeRequestFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(ResizeRequestEvent)) +} + +type CirculateNotifyFun func(xu *xgbutil.XUtil, event CirculateNotifyEvent) + +func (callback CirculateNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, CirculateNotify, win, callback) +} + +func (callback CirculateNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(CirculateNotifyEvent)) +} + +type CirculateRequestFun func(xu *xgbutil.XUtil, event CirculateRequestEvent) + +func (callback CirculateRequestFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, CirculateRequest, win, callback) +} + +func (callback CirculateRequestFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(CirculateRequestEvent)) +} + +type PropertyNotifyFun func(xu *xgbutil.XUtil, event PropertyNotifyEvent) + +func (callback PropertyNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, PropertyNotify, win, callback) +} + +func (callback PropertyNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(PropertyNotifyEvent)) +} + +type SelectionClearFun func(xu *xgbutil.XUtil, event SelectionClearEvent) + +func (callback SelectionClearFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, SelectionClear, win, callback) +} + +func (callback SelectionClearFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(SelectionClearEvent)) +} + +type SelectionRequestFun func(xu *xgbutil.XUtil, event SelectionRequestEvent) + +func (callback SelectionRequestFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, SelectionRequest, win, callback) +} + +func (callback SelectionRequestFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(SelectionRequestEvent)) +} + +type SelectionNotifyFun func(xu *xgbutil.XUtil, event SelectionNotifyEvent) + +func (callback SelectionNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, SelectionNotify, win, callback) +} + +func (callback SelectionNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(SelectionNotifyEvent)) +} + +type ColormapNotifyFun func(xu *xgbutil.XUtil, event ColormapNotifyEvent) + +func (callback ColormapNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, ColormapNotify, win, callback) +} + +func (callback ColormapNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(ColormapNotifyEvent)) +} + +type ClientMessageFun func(xu *xgbutil.XUtil, event ClientMessageEvent) + +func (callback ClientMessageFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, ClientMessage, win, callback) +} + +func (callback ClientMessageFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(ClientMessageEvent)) +} + +type MappingNotifyFun func(xu *xgbutil.XUtil, event MappingNotifyEvent) + +func (callback MappingNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, MappingNotify, win, callback) +} + +func (callback MappingNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(MappingNotifyEvent)) +} + +type ShapeNotifyFun func(xu *xgbutil.XUtil, event ShapeNotifyEvent) + +func (callback ShapeNotifyFun) Connect(xu *xgbutil.XUtil, + win xproto.Window) { + attachCallback(xu, ShapeNotify, win, callback) +} + +func (callback ShapeNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) { + callback(xu, event.(ShapeNotifyEvent)) +} diff --git a/vendor/github.com/BurntSushi/xgbutil/xevent/doc.go b/vendor/github.com/BurntSushi/xgbutil/xevent/doc.go new file mode 100644 index 0000000..eee7c13 --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/xevent/doc.go @@ -0,0 +1,76 @@ +/* +Package xevent provides an event handler interface for attaching callback +functions to X events, and an implementation of an X event loop. + +The X event loop + +One of the biggest conveniences offered by xgbutil is its event handler system. +That is, the ability to attach an arbitrary callback function to any X event. +In order for such things to work, xgbutil needs to control the main X event +loop and act as a dispatcher for all event handlers created by you. + +To run the X event loop, use xevent.Main or xevent.MainPing. The former +runs a normal event loop in the current goroutine and processes events. The +latter runs the event loop in a new goroutine and returns a pingBefore and +a pingAfter channel. The pingBefore channel is sent a benign value right before +an event is dequeued, and the pingAfter channel is sent a benign value right +after after all callbacks for that event have finished execution. These +synchronization points in the main event loop can be combined with a 'select' +statement to process data from other input sources. An example of this is given +in the documentation for the MainPing function. A complete example called +multiple-source-event-loop can also be found in the examples directory of the +xgbutil package. + +To quit the main event loop, you may use xevent.Quit, but there is nothing +inherently wrong with stopping dead using os.Exit. xevent.Quit is provided for +your convenience should you need to run any clean-up code after the main event +loop returns. + +The X event queue + +xgbutil's event queue contains values that are either events or errors. (Never +both and never neither.) Namely, errors are received in the event loop from +unchecked requests. (Errors generated by checked requests are guaranteed to be +returned to the caller and are never received in the event loop.) Also, a +default error handler function can be set with xevent.ErrorHandlerSet. + +To this end, xgbutil's event queue can be inspected. This is advantageous when +information about what events will be processed in the future could be helpful +(i.e., if there is an UnmapNotify event waiting to be processed.) The event +queue can also be manipulated to facilitate event compression. (Two events that +are common candidates for compression are ConfigureNotify and MotionNotify.) + +Detach events + +Whenever a window can no longer receive events (i.e., when it is destroyed), +all event handlers related to that window should be detached. (If this is +omitted, then Go's garbage collector will not be able to reuse memory occupied +by the now-unused event handlers for that window.) Moreover, its possible that +a window id can be reused after it has been discarded, which could result in +odd behavior in your application. + +To detach a window from all event handlers in the xevent package, use +xevent.Detach. If you're also using the keybind and mousebind packages, you'll +need to call keybind.Detach and mousebind.Detach too. So to detach your window +from all possible event handlers in xgbutil, use something like: + + xevent.Detach(XUtilValue, your-window-id) + keybind.Detach(XUtilValue, your-window-id) + mousebind.Detach(XUtilValue, your-window-id) + +Quick example + +A small example that shows how to respond to ConfigureNotify events sent to +your-window-id. + + xevent.ConfigureNotifyFun( + func(X *xgbutil.XUtil, e xevent.ConfigureNotifyEvent) { + fmt.Printf("(%d, %d) %dx%d\n", e.X, e.Y, e.Width, e.Height) + }).Connect(XUtilValue, your-window-id) + +More examples + +The xevent package is used in several of the examples in the examples directory +in the xgbutil package. +*/ +package xevent diff --git a/vendor/github.com/BurntSushi/xgbutil/xevent/eventloop.go b/vendor/github.com/BurntSushi/xgbutil/xevent/eventloop.go new file mode 100644 index 0000000..458f391 --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/xevent/eventloop.go @@ -0,0 +1,291 @@ +package xevent + +/* +xevent/eventloop.go contains code that implements a main X event loop. + +Namely, it provides facilities to read new events into xevent's event queue, +run a normal main event loop and run a main event loop that pings a channel +each time an event is about to be dequeued. The latter facility allows one to +easily include other input sources for processing in a program's main event +loop. +*/ + +import ( + "github.com/BurntSushi/xgb/shape" + "github.com/BurntSushi/xgb/xproto" + + "github.com/BurntSushi/xgbutil" +) + +// Read reads one or more events and queues them in XUtil. +// If 'block' is True, then call 'WaitForEvent' before sucking up +// all events that have been queued by XGB. +func Read(xu *xgbutil.XUtil, block bool) { + if block { + ev, err := xu.Conn().WaitForEvent() + if ev == nil && err == nil { + xgbutil.Logger.Fatal("BUG: Could not read an event or an error.") + } + Enqueue(xu, ev, err) + } + + // Clean up anything that's in the queue + for { + ev, err := xu.Conn().PollForEvent() + + // No events left... + if ev == nil && err == nil { + break + } + + // We're good, queue it up + Enqueue(xu, ev, err) + } +} + +// Main starts the main X event loop. It will read events and call appropriate +// callback functions. +// N.B. If you have multiple X connections in the same program, you should be +// able to run this in different goroutines concurrently. However, only +// *one* of these should run for *each* connection. +func Main(xu *xgbutil.XUtil) { + mainEventLoop(xu, nil, nil, nil) +} + +// MainPing starts the main X event loop, and returns three "ping" channels: +// the first is pinged before an event is dequeued, the second is pinged +// after all callbacks for a particular event have been called and the last +// is pinged when the event loop stops (e.g., after a call to xevent.Quit). +// pingAfter channel. +// +// This is useful if your event loop needs to draw from other sources. e.g., +// +// pingBefore, pingAfter, pingQuit := xevent.MainPing() +// for { +// select { +// case <-pingBefore: +// // Wait for event processing to finish. +// <-pingAfter +// case val <- someOtherChannel: +// // do some work with val +// case <-pingQuit: +// fmt.Printf("xevent loop has quit") +// return +// } +// } +// +// Note that an unbuffered channel is returned, which implies that any work +// done with 'val' will delay further X event processing. +// +// A complete example using MainPing can be found in the examples directory in +// the xgbutil package under the name multiple-source-event-loop. +func MainPing(xu *xgbutil.XUtil) (chan struct{}, chan struct{}, chan struct{}) { + pingBefore := make(chan struct{}, 0) + pingAfter := make(chan struct{}, 0) + pingQuit := make(chan struct{}, 0) + go func() { + mainEventLoop(xu, pingBefore, pingAfter, pingQuit) + }() + return pingBefore, pingAfter, pingQuit +} + +// mainEventLoop runs the main event loop with an optional ping channel. +func mainEventLoop(xu *xgbutil.XUtil, + pingBefore, pingAfter, pingQuit chan struct{}) { + for { + if Quitting(xu) { + if pingQuit != nil { + pingQuit <- struct{}{} + } + break + } + + // Gobble up as many events as possible (into the queue). + // If there are no events, we block. + Read(xu, true) + + // Now process every event/error in the queue. + processEventQueue(xu, pingBefore, pingAfter) + } +} + +// processEventQueue processes every item in the event/error queue. +func processEventQueue(xu *xgbutil.XUtil, pingBefore, pingAfter chan struct{}) { + for !Empty(xu) { + if Quitting(xu) { + return + } + + // We send the ping *before* the next event is dequeued. + // This is so the queue doesn't present a misrepresentation of which + // events haven't been processed yet. + if pingBefore != nil && pingAfter != nil { + pingBefore <- struct{}{} + } + ev, err := Dequeue(xu) + + // If we gobbled up an error, send it to the error event handler + // and move on the next event/error. + if err != nil { + ErrorHandlerGet(xu)(err) + if pingBefore != nil && pingAfter != nil { + pingAfter <- struct{}{} + } + continue + } + + // We know there isn't an error. If there isn't an event either, + // then there's a bug somewhere. + if ev == nil { + xgbutil.Logger.Fatal("BUG: Expected an event but got nil.") + } + + hooks := getHooks(xu) + for _, hook := range hooks { + if !hook.Run(xu, ev) { + goto END + } + } + + switch event := ev.(type) { + case xproto.KeyPressEvent: + e := KeyPressEvent{&event} + + // If we're redirecting key events, this is the place to do it! + if wid := RedirectKeyGet(xu); wid > 0 { + e.Event = wid + } + + xu.TimeSet(e.Time) + runCallbacks(xu, e, KeyPress, e.Event) + case xproto.KeyReleaseEvent: + e := KeyReleaseEvent{&event} + + // If we're redirecting key events, this is the place to do it! + if wid := RedirectKeyGet(xu); wid > 0 { + e.Event = wid + } + + xu.TimeSet(e.Time) + runCallbacks(xu, e, KeyRelease, e.Event) + case xproto.ButtonPressEvent: + e := ButtonPressEvent{&event} + xu.TimeSet(e.Time) + runCallbacks(xu, e, ButtonPress, e.Event) + case xproto.ButtonReleaseEvent: + e := ButtonReleaseEvent{&event} + xu.TimeSet(e.Time) + runCallbacks(xu, e, ButtonRelease, e.Event) + case xproto.MotionNotifyEvent: + e := MotionNotifyEvent{&event} + xu.TimeSet(e.Time) + runCallbacks(xu, e, MotionNotify, e.Event) + case xproto.EnterNotifyEvent: + e := EnterNotifyEvent{&event} + xu.TimeSet(e.Time) + runCallbacks(xu, e, EnterNotify, e.Event) + case xproto.LeaveNotifyEvent: + e := LeaveNotifyEvent{&event} + xu.TimeSet(e.Time) + runCallbacks(xu, e, LeaveNotify, e.Event) + case xproto.FocusInEvent: + e := FocusInEvent{&event} + runCallbacks(xu, e, FocusIn, e.Event) + case xproto.FocusOutEvent: + e := FocusOutEvent{&event} + runCallbacks(xu, e, FocusOut, e.Event) + case xproto.KeymapNotifyEvent: + e := KeymapNotifyEvent{&event} + runCallbacks(xu, e, KeymapNotify, NoWindow) + case xproto.ExposeEvent: + e := ExposeEvent{&event} + runCallbacks(xu, e, Expose, e.Window) + case xproto.GraphicsExposureEvent: + e := GraphicsExposureEvent{&event} + runCallbacks(xu, e, GraphicsExposure, xproto.Window(e.Drawable)) + case xproto.NoExposureEvent: + e := NoExposureEvent{&event} + runCallbacks(xu, e, NoExposure, xproto.Window(e.Drawable)) + case xproto.VisibilityNotifyEvent: + e := VisibilityNotifyEvent{&event} + runCallbacks(xu, e, VisibilityNotify, e.Window) + case xproto.CreateNotifyEvent: + e := CreateNotifyEvent{&event} + runCallbacks(xu, e, CreateNotify, e.Parent) + case xproto.DestroyNotifyEvent: + e := DestroyNotifyEvent{&event} + runCallbacks(xu, e, DestroyNotify, e.Window) + case xproto.UnmapNotifyEvent: + e := UnmapNotifyEvent{&event} + runCallbacks(xu, e, UnmapNotify, e.Window) + case xproto.MapNotifyEvent: + e := MapNotifyEvent{&event} + runCallbacks(xu, e, MapNotify, e.Event) + case xproto.MapRequestEvent: + e := MapRequestEvent{&event} + runCallbacks(xu, e, MapRequest, e.Window) + runCallbacks(xu, e, MapRequest, e.Parent) + case xproto.ReparentNotifyEvent: + e := ReparentNotifyEvent{&event} + runCallbacks(xu, e, ReparentNotify, e.Window) + case xproto.ConfigureNotifyEvent: + e := ConfigureNotifyEvent{&event} + runCallbacks(xu, e, ConfigureNotify, e.Window) + case xproto.ConfigureRequestEvent: + e := ConfigureRequestEvent{&event} + runCallbacks(xu, e, ConfigureRequest, e.Window) + runCallbacks(xu, e, ConfigureRequest, e.Parent) + case xproto.GravityNotifyEvent: + e := GravityNotifyEvent{&event} + runCallbacks(xu, e, GravityNotify, e.Window) + case xproto.ResizeRequestEvent: + e := ResizeRequestEvent{&event} + runCallbacks(xu, e, ResizeRequest, e.Window) + case xproto.CirculateNotifyEvent: + e := CirculateNotifyEvent{&event} + runCallbacks(xu, e, CirculateNotify, e.Window) + case xproto.CirculateRequestEvent: + e := CirculateRequestEvent{&event} + runCallbacks(xu, e, CirculateRequest, e.Window) + case xproto.PropertyNotifyEvent: + e := PropertyNotifyEvent{&event} + xu.TimeSet(e.Time) + runCallbacks(xu, e, PropertyNotify, e.Window) + case xproto.SelectionClearEvent: + e := SelectionClearEvent{&event} + xu.TimeSet(e.Time) + runCallbacks(xu, e, SelectionClear, e.Owner) + case xproto.SelectionRequestEvent: + e := SelectionRequestEvent{&event} + xu.TimeSet(e.Time) + runCallbacks(xu, e, SelectionRequest, e.Requestor) + case xproto.SelectionNotifyEvent: + e := SelectionNotifyEvent{&event} + xu.TimeSet(e.Time) + runCallbacks(xu, e, SelectionNotify, e.Requestor) + case xproto.ColormapNotifyEvent: + e := ColormapNotifyEvent{&event} + runCallbacks(xu, e, ColormapNotify, e.Window) + case xproto.ClientMessageEvent: + e := ClientMessageEvent{&event} + runCallbacks(xu, e, ClientMessage, e.Window) + case xproto.MappingNotifyEvent: + e := MappingNotifyEvent{&event} + runCallbacks(xu, e, MappingNotify, NoWindow) + case shape.NotifyEvent: + e := ShapeNotifyEvent{&event} + runCallbacks(xu, e, ShapeNotify, e.AffectedWindow) + default: + if event != nil { + xgbutil.Logger.Printf("ERROR: UNSUPPORTED EVENT TYPE: %T", + event) + } + } + + END: + + if pingBefore != nil && pingAfter != nil { + pingAfter <- struct{}{} + } + } +} diff --git a/vendor/github.com/BurntSushi/xgbutil/xevent/types_auto.go b/vendor/github.com/BurntSushi/xgbutil/xevent/types_auto.go new file mode 100644 index 0000000..9bdb702 --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/xevent/types_auto.go @@ -0,0 +1,336 @@ +package xevent + +/* + Defines event types and their associated methods automatically. + + This file is automatically generated using `scripts/write-events evtypes`. + + Edit it at your peril. +*/ + +import ( + "fmt" + + "github.com/BurntSushi/xgb/shape" + "github.com/BurntSushi/xgb/xproto" +) + +type KeyPressEvent struct { + *xproto.KeyPressEvent +} + +const KeyPress = xproto.KeyPress + +func (ev KeyPressEvent) String() string { + return fmt.Sprintf("%v", ev.KeyPressEvent) +} + +type KeyReleaseEvent struct { + *xproto.KeyReleaseEvent +} + +const KeyRelease = xproto.KeyRelease + +func (ev KeyReleaseEvent) String() string { + return fmt.Sprintf("%v", ev.KeyReleaseEvent) +} + +type ButtonPressEvent struct { + *xproto.ButtonPressEvent +} + +const ButtonPress = xproto.ButtonPress + +func (ev ButtonPressEvent) String() string { + return fmt.Sprintf("%v", ev.ButtonPressEvent) +} + +type ButtonReleaseEvent struct { + *xproto.ButtonReleaseEvent +} + +const ButtonRelease = xproto.ButtonRelease + +func (ev ButtonReleaseEvent) String() string { + return fmt.Sprintf("%v", ev.ButtonReleaseEvent) +} + +type MotionNotifyEvent struct { + *xproto.MotionNotifyEvent +} + +const MotionNotify = xproto.MotionNotify + +func (ev MotionNotifyEvent) String() string { + return fmt.Sprintf("%v", ev.MotionNotifyEvent) +} + +type EnterNotifyEvent struct { + *xproto.EnterNotifyEvent +} + +const EnterNotify = xproto.EnterNotify + +func (ev EnterNotifyEvent) String() string { + return fmt.Sprintf("%v", ev.EnterNotifyEvent) +} + +type LeaveNotifyEvent struct { + *xproto.LeaveNotifyEvent +} + +const LeaveNotify = xproto.LeaveNotify + +func (ev LeaveNotifyEvent) String() string { + return fmt.Sprintf("%v", ev.LeaveNotifyEvent) +} + +type FocusInEvent struct { + *xproto.FocusInEvent +} + +const FocusIn = xproto.FocusIn + +func (ev FocusInEvent) String() string { + return fmt.Sprintf("%v", ev.FocusInEvent) +} + +type FocusOutEvent struct { + *xproto.FocusOutEvent +} + +const FocusOut = xproto.FocusOut + +func (ev FocusOutEvent) String() string { + return fmt.Sprintf("%v", ev.FocusOutEvent) +} + +type KeymapNotifyEvent struct { + *xproto.KeymapNotifyEvent +} + +const KeymapNotify = xproto.KeymapNotify + +func (ev KeymapNotifyEvent) String() string { + return fmt.Sprintf("%v", ev.KeymapNotifyEvent) +} + +type ExposeEvent struct { + *xproto.ExposeEvent +} + +const Expose = xproto.Expose + +func (ev ExposeEvent) String() string { + return fmt.Sprintf("%v", ev.ExposeEvent) +} + +type GraphicsExposureEvent struct { + *xproto.GraphicsExposureEvent +} + +const GraphicsExposure = xproto.GraphicsExposure + +func (ev GraphicsExposureEvent) String() string { + return fmt.Sprintf("%v", ev.GraphicsExposureEvent) +} + +type NoExposureEvent struct { + *xproto.NoExposureEvent +} + +const NoExposure = xproto.NoExposure + +func (ev NoExposureEvent) String() string { + return fmt.Sprintf("%v", ev.NoExposureEvent) +} + +type VisibilityNotifyEvent struct { + *xproto.VisibilityNotifyEvent +} + +const VisibilityNotify = xproto.VisibilityNotify + +func (ev VisibilityNotifyEvent) String() string { + return fmt.Sprintf("%v", ev.VisibilityNotifyEvent) +} + +type CreateNotifyEvent struct { + *xproto.CreateNotifyEvent +} + +const CreateNotify = xproto.CreateNotify + +func (ev CreateNotifyEvent) String() string { + return fmt.Sprintf("%v", ev.CreateNotifyEvent) +} + +type DestroyNotifyEvent struct { + *xproto.DestroyNotifyEvent +} + +const DestroyNotify = xproto.DestroyNotify + +func (ev DestroyNotifyEvent) String() string { + return fmt.Sprintf("%v", ev.DestroyNotifyEvent) +} + +type UnmapNotifyEvent struct { + *xproto.UnmapNotifyEvent +} + +const UnmapNotify = xproto.UnmapNotify + +func (ev UnmapNotifyEvent) String() string { + return fmt.Sprintf("%v", ev.UnmapNotifyEvent) +} + +type MapNotifyEvent struct { + *xproto.MapNotifyEvent +} + +const MapNotify = xproto.MapNotify + +func (ev MapNotifyEvent) String() string { + return fmt.Sprintf("%v", ev.MapNotifyEvent) +} + +type MapRequestEvent struct { + *xproto.MapRequestEvent +} + +const MapRequest = xproto.MapRequest + +func (ev MapRequestEvent) String() string { + return fmt.Sprintf("%v", ev.MapRequestEvent) +} + +type ReparentNotifyEvent struct { + *xproto.ReparentNotifyEvent +} + +const ReparentNotify = xproto.ReparentNotify + +func (ev ReparentNotifyEvent) String() string { + return fmt.Sprintf("%v", ev.ReparentNotifyEvent) +} + +type ConfigureRequestEvent struct { + *xproto.ConfigureRequestEvent +} + +const ConfigureRequest = xproto.ConfigureRequest + +func (ev ConfigureRequestEvent) String() string { + return fmt.Sprintf("%v", ev.ConfigureRequestEvent) +} + +type GravityNotifyEvent struct { + *xproto.GravityNotifyEvent +} + +const GravityNotify = xproto.GravityNotify + +func (ev GravityNotifyEvent) String() string { + return fmt.Sprintf("%v", ev.GravityNotifyEvent) +} + +type ResizeRequestEvent struct { + *xproto.ResizeRequestEvent +} + +const ResizeRequest = xproto.ResizeRequest + +func (ev ResizeRequestEvent) String() string { + return fmt.Sprintf("%v", ev.ResizeRequestEvent) +} + +type CirculateNotifyEvent struct { + *xproto.CirculateNotifyEvent +} + +const CirculateNotify = xproto.CirculateNotify + +func (ev CirculateNotifyEvent) String() string { + return fmt.Sprintf("%v", ev.CirculateNotifyEvent) +} + +type CirculateRequestEvent struct { + *xproto.CirculateRequestEvent +} + +const CirculateRequest = xproto.CirculateRequest + +func (ev CirculateRequestEvent) String() string { + return fmt.Sprintf("%v", ev.CirculateRequestEvent) +} + +type PropertyNotifyEvent struct { + *xproto.PropertyNotifyEvent +} + +const PropertyNotify = xproto.PropertyNotify + +func (ev PropertyNotifyEvent) String() string { + return fmt.Sprintf("%v", ev.PropertyNotifyEvent) +} + +type SelectionClearEvent struct { + *xproto.SelectionClearEvent +} + +const SelectionClear = xproto.SelectionClear + +func (ev SelectionClearEvent) String() string { + return fmt.Sprintf("%v", ev.SelectionClearEvent) +} + +type SelectionRequestEvent struct { + *xproto.SelectionRequestEvent +} + +const SelectionRequest = xproto.SelectionRequest + +func (ev SelectionRequestEvent) String() string { + return fmt.Sprintf("%v", ev.SelectionRequestEvent) +} + +type SelectionNotifyEvent struct { + *xproto.SelectionNotifyEvent +} + +const SelectionNotify = xproto.SelectionNotify + +func (ev SelectionNotifyEvent) String() string { + return fmt.Sprintf("%v", ev.SelectionNotifyEvent) +} + +type ColormapNotifyEvent struct { + *xproto.ColormapNotifyEvent +} + +const ColormapNotify = xproto.ColormapNotify + +func (ev ColormapNotifyEvent) String() string { + return fmt.Sprintf("%v", ev.ColormapNotifyEvent) +} + +type MappingNotifyEvent struct { + *xproto.MappingNotifyEvent +} + +const MappingNotify = xproto.MappingNotify + +func (ev MappingNotifyEvent) String() string { + return fmt.Sprintf("%v", ev.MappingNotifyEvent) +} + +type ShapeNotifyEvent struct { + *shape.NotifyEvent +} + +const ShapeNotify = shape.Notify + +func (ev ShapeNotifyEvent) String() string { + return fmt.Sprintf("%v", ev.NotifyEvent) +} diff --git a/vendor/github.com/BurntSushi/xgbutil/xevent/types_manual.go b/vendor/github.com/BurntSushi/xgbutil/xevent/types_manual.go new file mode 100644 index 0000000..b32c59a --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/xevent/types_manual.go @@ -0,0 +1,90 @@ +package xevent + +import ( + "fmt" + + "github.com/BurntSushi/xgb/xproto" +) + +// ClientMessageEvent embeds the struct by the same name from the xgb library. +type ClientMessageEvent struct { + *xproto.ClientMessageEvent +} + +const ClientMessage = xproto.ClientMessage + +// NewClientMessage takes all arguments required to build a ClientMessageEvent +// struct and hides the messy details. +// The variadic parameters coincide with the "data" part of a client message. +// The type of the variadic parameters depends upon the value of Format. +// If Format is 8, 'data' should have type byte. +// If Format is 16, 'data' should have type int16. +// If Format is 32, 'data' should have type int. +// Any other value of Format returns an error. +func NewClientMessage(Format byte, Window xproto.Window, Type xproto.Atom, + data ...interface{}) (*ClientMessageEvent, error) { + + // Create the client data list first + var clientData xproto.ClientMessageDataUnion + + // Don't support formats 8 or 16 yet. They aren't used in EWMH anyway. + switch Format { + case 8: + buf := make([]byte, 20) + for i := 0; i < 20; i++ { + if i >= len(data) { + break + } + buf[i] = data[i].(byte) + } + clientData = xproto.ClientMessageDataUnionData8New(buf) + case 16: + buf := make([]uint16, 10) + for i := 0; i < 10; i++ { + if i >= len(data) { + break + } + buf[i] = uint16(data[i].(int16)) + } + clientData = xproto.ClientMessageDataUnionData16New(buf) + case 32: + buf := make([]uint32, 5) + for i := 0; i < 5; i++ { + if i >= len(data) { + break + } + buf[i] = uint32(data[i].(int)) + } + clientData = xproto.ClientMessageDataUnionData32New(buf) + default: + return nil, fmt.Errorf("NewClientMessage: Unsupported format '%d'.", + Format) + } + + return &ClientMessageEvent{&xproto.ClientMessageEvent{ + Format: Format, + Window: Window, + Type: Type, + Data: clientData, + }}, nil +} + +// ConfigureNotifyEvent embeds the struct by the same name in XGB. +type ConfigureNotifyEvent struct { + *xproto.ConfigureNotifyEvent +} + +const ConfigureNotify = xproto.ConfigureNotify + +// NewConfigureNotify takes all arguments required to build a +// ConfigureNotifyEvent struct and returns a ConfigureNotifyEvent value. +func NewConfigureNotify(Event, Window, AboveSibling xproto.Window, + X, Y, Width, Height int, BorderWidth uint16, + OverrideRedirect bool) *ConfigureNotifyEvent { + + return &ConfigureNotifyEvent{&xproto.ConfigureNotifyEvent{ + Event: Event, Window: Window, AboveSibling: AboveSibling, + X: int16(X), Y: int16(Y), Width: uint16(Width), Height: uint16(Height), + BorderWidth: BorderWidth, OverrideRedirect: OverrideRedirect, + }} +} diff --git a/vendor/github.com/BurntSushi/xgbutil/xevent/xevent.go b/vendor/github.com/BurntSushi/xgbutil/xevent/xevent.go new file mode 100644 index 0000000..da52647 --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/xevent/xevent.go @@ -0,0 +1,237 @@ +package xevent + +import ( + "github.com/BurntSushi/xgb" + "github.com/BurntSushi/xgb/xproto" + + "github.com/BurntSushi/xgbutil" +) + +// Sometimes we need to specify NO WINDOW when a window is typically +// expected. (Like connecting to MappingNotify or KeymapNotify events.) +// Use this value to do that. +var NoWindow xproto.Window = 0 + +// IgnoreMods is a list of X modifiers that we don't want interfering +// with our mouse or key bindings. In particular, for each mouse or key binding +// issued, there is a seperate mouse or key binding made for each of the +// modifiers specified. +// +// You may modify this slice to add (or remove) modifiers, but it should be +// done before *any* key or mouse bindings are attached with the keybind and +// mousebind packages. It should not be modified afterwards. +// +// TODO: We're assuming numlock is in the 'mod2' modifier, which is a pretty +// common setup, but by no means guaranteed. This should be modified to actually +// inspect the modifiers table and look for the special Num_Lock keysym. +var IgnoreMods []uint16 = []uint16{ + 0, + xproto.ModMaskLock, // Caps lock + xproto.ModMask2, // Num lock + xproto.ModMaskLock | xproto.ModMask2, // Caps and Num lock +} + +// Enqueue queues up an event read from X. +// Note that an event read may return an error, in which case, this queue +// entry will be an error and not an event. +// +// ev, err := XUtilValue.Conn().WaitForEvent() +// xevent.Enqueue(XUtilValue, ev, err) +// +// You probably shouldn't have to enqueue events yourself. This is done +// automatically if you're using xevent.Main{Ping} and/or xevent.Read. +func Enqueue(xu *xgbutil.XUtil, ev xgb.Event, err xgb.Error) { + xu.EvqueueLck.Lock() + defer xu.EvqueueLck.Unlock() + + xu.Evqueue = append(xu.Evqueue, xgbutil.EventOrError{ + Event: ev, + Err: err, + }) +} + +// Dequeue pops an event/error from the queue and returns it. +// The queue item is unwrapped and returned as multiple return values. +// Only one of the return values can be nil. +func Dequeue(xu *xgbutil.XUtil) (xgb.Event, xgb.Error) { + xu.EvqueueLck.Lock() + defer xu.EvqueueLck.Unlock() + + everr := xu.Evqueue[0] + xu.Evqueue = xu.Evqueue[1:] + return everr.Event, everr.Err +} + +// DequeueAt removes a particular item from the queue. +// This is primarily useful when attempting to compress events. +func DequeueAt(xu *xgbutil.XUtil, i int) { + xu.EvqueueLck.Lock() + defer xu.EvqueueLck.Unlock() + + xu.Evqueue = append(xu.Evqueue[:i], xu.Evqueue[i+1:]...) +} + +// Empty returns whether the event queue is empty or not. +func Empty(xu *xgbutil.XUtil) bool { + xu.EvqueueLck.RLock() + defer xu.EvqueueLck.RUnlock() + + return len(xu.Evqueue) == 0 +} + +// Peek returns a *copy* of the current queue so we can examine it. +// This can be useful when trying to determine if a particular kind of +// event will be processed in the future. +func Peek(xu *xgbutil.XUtil) []xgbutil.EventOrError { + xu.EvqueueLck.RLock() + defer xu.EvqueueLck.RUnlock() + + cpy := make([]xgbutil.EventOrError, len(xu.Evqueue)) + copy(cpy, xu.Evqueue) + return cpy +} + +// ErrorHandlerSet sets the default error handler for errors that come +// into the main event loop. (This may be removed in the future in favor +// of a particular callback interface like events, but these sorts of errors +// aren't handled often in practice, so maybe not.) +// This is only called for errors returned from unchecked (asynchronous error +// handling) requests. +// The default error handler just emits them to stderr. +func ErrorHandlerSet(xu *xgbutil.XUtil, fun xgbutil.ErrorHandlerFun) { + xu.ErrorHandler = fun +} + +// ErrorHandlerGet retrieves the default error handler. +func ErrorHandlerGet(xu *xgbutil.XUtil) xgbutil.ErrorHandlerFun { + return xu.ErrorHandler +} + +type HookFun func(xu *xgbutil.XUtil, event interface{}) bool + +func (callback HookFun) Connect(xu *xgbutil.XUtil) { + xu.HooksLck.Lock() + defer xu.HooksLck.Unlock() + + // COW + newHooks := make([]xgbutil.CallbackHook, len(xu.Hooks)) + copy(newHooks, xu.Hooks) + newHooks = append(newHooks, callback) + + xu.Hooks = newHooks +} + +func (callback HookFun) Run(xu *xgbutil.XUtil, event interface{}) bool { + return callback(xu, event) +} + +func getHooks(xu *xgbutil.XUtil) []xgbutil.CallbackHook { + xu.HooksLck.RLock() + defer xu.HooksLck.RUnlock() + + return xu.Hooks +} + +// RedirectKeyEvents, when set to a window id (greater than 0), will force +// *all* Key{Press,Release} to callbacks attached to the specified window. +// This is close to emulating a Keyboard grab without the racing. +// To stop redirecting key events, use window identifier '0'. +func RedirectKeyEvents(xu *xgbutil.XUtil, wid xproto.Window) { + xu.KeyRedirect = wid +} + +// RedirectKeyGet gets the window that key events are being redirected to. +// If 0, then no redirection occurs. +func RedirectKeyGet(xu *xgbutil.XUtil) xproto.Window { + return xu.KeyRedirect +} + +// Quit elegantly exits out of the main event loop. +// "Elegantly" in this case means that it finishes processing the current +// event, and breaks out of the loop afterwards. +// There is no particular reason to use this instead of something like os.Exit +// other than you might have code to run after the main event loop exits to +// "clean up." +func Quit(xu *xgbutil.XUtil) { + xu.Quit = true +} + +// Quitting returns whether it's time to quit. +// This is only used in the main event loop in xevent. +func Quitting(xu *xgbutil.XUtil) bool { + return xu.Quit +} + +// attachCallback associates a (event, window) tuple with an event. +// Use copy on write since we run callbacks *a lot* more than attaching them. +// (The copy on write only applies to the slice of callbacks rather than +// the map itself, since the initial allocation is guaranteed to come before +// any use of it.) +func attachCallback(xu *xgbutil.XUtil, evtype int, win xproto.Window, + fun xgbutil.Callback) { + + xu.CallbacksLck.Lock() + defer xu.CallbacksLck.Unlock() + + if _, ok := xu.Callbacks[evtype]; !ok { + xu.Callbacks[evtype] = make(map[xproto.Window][]xgbutil.Callback, 20) + } + if _, ok := xu.Callbacks[evtype][win]; !ok { + xu.Callbacks[evtype][win] = make([]xgbutil.Callback, 0) + } + + // COW + newCallbacks := make([]xgbutil.Callback, len(xu.Callbacks[evtype][win])) + copy(newCallbacks, xu.Callbacks[evtype][win]) + newCallbacks = append(newCallbacks, fun) + xu.Callbacks[evtype][win] = newCallbacks +} + +// runCallbacks executes every callback corresponding to a +// particular event/window tuple. +func runCallbacks(xu *xgbutil.XUtil, event interface{}, evtype int, + win xproto.Window) { + + // The callback slice for a particular (event type, window) tuple uses + // copy on write. So just take a pointer to whatever is there and use that. + // We can be sure that the slice won't change from underneathe us. + xu.CallbacksLck.RLock() + cbs := xu.Callbacks[evtype][win] + xu.CallbacksLck.RUnlock() + + for _, cb := range cbs { + cb.Run(xu, event) + } +} + +// Detach removes all callbacks associated with a particular window. +// Note that if you're also using the keybind and mousebind packages, a complete +// detachment should look like: +// +// keybind.Detach(XUtilValue, window-id) +// mousebind.Detach(XUtilValue, window-id) +// xevent.Detach(XUtilValue, window-id) +// +// If a window is no longer receiving events, these methods should be called. +// Otherwise, the memory used to store the handler info for that window will +// never be released. +func Detach(xu *xgbutil.XUtil, win xproto.Window) { + xu.CallbacksLck.Lock() + defer xu.CallbacksLck.Unlock() + + for evtype, _ := range xu.Callbacks { + delete(xu.Callbacks[evtype], win) + } +} + +// SendRootEvent takes a type implementing the xgb.Event interface, converts it +// to raw X bytes, and sends it to the root window using the SendEvent request. +func SendRootEvent(xu *xgbutil.XUtil, ev xgb.Event, evMask uint32) error { + return xproto.SendEventChecked(xu.Conn(), false, xu.RootWin(), evMask, + string(ev.Bytes())).Check() +} + +// ReplayPointer is a quick alias to AllowEvents with 'ReplayPointer' mode. +func ReplayPointer(xu *xgbutil.XUtil) { + xproto.AllowEvents(xu.Conn(), xproto.AllowReplayPointer, 0) +} diff --git a/vendor/github.com/BurntSushi/xgbutil/xgbutil.go b/vendor/github.com/BurntSushi/xgbutil/xgbutil.go new file mode 100644 index 0000000..1539ad3 --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/xgbutil.go @@ -0,0 +1,348 @@ +package xgbutil + +import ( + "log" + "os" + "sync" + + "github.com/BurntSushi/xgb" + "github.com/BurntSushi/xgb/xinerama" + "github.com/BurntSushi/xgb/xproto" +) + +// Logger is used through xgbutil when messages need to be emitted to stderr. +var Logger = log.New(os.Stderr, "[xgbutil] ", log.Lshortfile) + +// The current maximum request size. I think we can expand this with +// BigReq, but it probably isn't worth it at the moment. +const MaxReqSize = (1 << 16) * 4 + +// An XUtil represents the state of xgbutil. It keeps track of the current +// X connection, the root window, event callbacks, key/mouse bindings, etc. +// Regrettably, many of the members are exported, even though they should not +// be used directly by the user. They are exported for use in sub-packages. +// (Namely, xevent, keybind and mousebind.) In fact, there should *never* +// be a reason to access any members of an XUtil value directly. Any +// interaction with an XUtil value should be through its methods. +type XUtil struct { + // conn is the XGB connection object used to issue protocol requests. + conn *xgb.Conn + + // Quit can be set to true, and the main event loop will finish processing + // the current event, and gracefully quit afterwards. + // This is exported for use in the xevent package. Please us xevent.Quit + // to set this value. + Quit bool // when true, the main event loop will stop gracefully + + // setup contains all the setup information retrieved at connection time. + setup *xproto.SetupInfo + + // screen is a simple alias to the default screen info. + screen *xproto.ScreenInfo + + // root is an alias to the default root window. + root xproto.Window + + // Atoms is a cache of atom names to resource identifiers. This minimizes + // round trips to the X server, since atom identifiers never change. + // It is exported for use in the xprop package. It should not be used. + Atoms map[string]xproto.Atom + AtomsLck *sync.RWMutex + + // AtomNames is a cache just like 'atoms', but in the reverse direction. + // It is exported for use in the xprop package. It should not be used. + AtomNames map[xproto.Atom]string + AtomNamesLck *sync.RWMutex + + // Evqueue is the queue that stores the results of xgb.WaitForEvent. + // Namely, each value is either an Event *or* an Error. + // It is exported for use in the xevent package. Do not use it. + // If you need to interact with the event queue, please use the functions + // available in the xevent package: Dequeue, DequeueAt, QueueEmpty + // and QueuePeek. + Evqueue []EventOrError + EvqueueLck *sync.RWMutex + + // Callbacks is a map of event numbers to a map of window identifiers + // to callback functions. + // This is the data structure that stores all callback functions, where + // a callback function is always attached to a (event, window) tuple. + // It is exported for use in the xevent package. Do not use it. + Callbacks map[int]map[xproto.Window][]Callback + CallbacksLck *sync.RWMutex + + // Hooks are called by the XEvent main loop before processing the event + // itself. These are meant for instances when it's not possible / easy + // to use the normal Hook system. You should not modify this yourself. + Hooks []CallbackHook + HooksLck *sync.RWMutex + + // eventTime is the last time recorded by an event. It is automatically + // updated if xgbutil's main event loop is used. + eventTime xproto.Timestamp + + // Keymap corresponds to xgbutil's current conception of the keyboard + // mapping. It is automatically kept up-to-date if xgbutil's event loop + // is used. + // It is exported for use in the keybind package. It should not be + // accessed directly. Instead, use keybind.KeyMapGet. + Keymap *KeyboardMapping + + // Modmap corresponds to xgbutil's current conception of the modifier key + // mapping. It is automatically kept up-to-date if xgbutil's event loop + // is used. + // It is exported for use in the keybind package. It should not be + // accessed directly. Instead, use keybind.ModMapGet. + Modmap *ModifierMapping + + // KeyRedirect corresponds to a window identifier that, when set, + // automatically receives *all* keyboard events. This is a sort-of + // synthetic grab and is helpful in avoiding race conditions. + // It is exported for use in the xevent and keybind packages. Do not use + // it directly. To redirect key events, please use xevent.RedirectKeyEvents. + KeyRedirect xproto.Window + + // Keybinds is the data structure storing all callbacks for key bindings. + // This is extremely similar to the general notion of event callbacks, + // but adds extra support to make handling key bindings easier. (Like + // specifying human readable key sequences to bind to.) + // KeyBindKey is a struct representing the 4-tuple + // (event-type, window-id, modifiers, keycode). + // It is exported for use in the keybind package. Do not access it directly. + Keybinds map[KeyKey][]CallbackKey + KeybindsLck *sync.RWMutex + + // Keygrabs is a frequency count of the number of callbacks associated + // with a particular KeyBindKey. This is necessary because we can only + // grab a particular key *once*, but we may want to attach several callbacks + // to a single keypress. + // It is exported for use in the keybind package. Do not access it directly. + Keygrabs map[KeyKey]int + + // Keystrings is a list of all key strings used to connect keybindings. + // They are used to rebuild key grabs when the keyboard mapping is updated. + // It is exported for use in the keybind package. Do not access it directly. + Keystrings []KeyString + + // Mousebinds is the data structure storing all callbacks for mouse + // bindings.This is extremely similar to the general notion of event + // callbacks,but adds extra support to make handling mouse bindings easier. + // (Like specifying human readable mouse sequences to bind to.) + // MouseBindKey is a struct representing the 4-tuple + // (event-type, window-id, modifiers, button). + // It is exported for use in the mousebind package. Do not use it. + Mousebinds map[MouseKey][]CallbackMouse + MousebindsLck *sync.RWMutex + + // Mousegrabs is a frequency count of the number of callbacks associated + // with a particular MouseBindKey. This is necessary because we can only + // grab a particular mouse button *once*, but we may want to attach + // several callbacks to a single button press. + // It is exported for use in the mousebind package. Do not use it. + Mousegrabs map[MouseKey]int + + // InMouseDrag is true if a drag is currently in progress. + // It is exported for use in the mousebind package. Do not use it. + InMouseDrag bool + + // MouseDragStep is the function executed for each step (i.e., pointer + // movement) in the current mouse drag. Note that this is nil when a drag + // is not in progress. + // It is exported for use in the mousebind package. Do not use it. + MouseDragStepFun MouseDragFun + + // MouseDragEnd is the function executed at the end of the current + // mouse drag. This is nil when a drag is not in progress. + // It is exported for use in the mousebind package. Do not use it. + MouseDragEndFun MouseDragFun + + // gc is a general purpose graphics context; used to paint images. + // Since we don't do any real X drawing, we don't really care about the + // particulars of our graphics context. + gc xproto.Gcontext + + // dummy is a dummy window used for mouse/key GRABs. + // Basically, whenever a grab is instituted, mouse and key events are + // redirected to the dummy the window. + dummy xproto.Window + + // ErrorHandler is the function that handles errors *in the event loop*. + // By default, it simply emits them to stderr. + // It is exported for use in the xevent package. To set the default error + // handler, please use xevent.ErrorHandlerSet. + ErrorHandler ErrorHandlerFun +} + +// NewConn connects to the X server using the DISPLAY environment variable +// and creates a new XUtil. Most environments have the DISPLAY environment +// variable set, so this is probably what you want to use to connect to X. +func NewConn() (*XUtil, error) { + return NewConnDisplay("") +} + +// NewConnDisplay connects to the X server and creates a new XUtil. +// If 'display' is empty, the DISPLAY environment variable is used. Otherwise +// there are several different display formats supported: +// +// 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) (*XUtil, error) { + c, err := xgb.NewConnDisplay(display) + + if err != nil { + return nil, err + } + + return NewConnXgb(c) + +} + +// NewConnXgb use the specific xgb.Conn to create a new XUtil. +// +// NewConn, NewConnDisplay are wrapper of this function. +func NewConnXgb(c *xgb.Conn) (*XUtil, error) { + setup := xproto.Setup(c) + screen := setup.DefaultScreen(c) + + // Initialize our central struct that stores everything. + xu := &XUtil{ + conn: c, + Quit: false, + Evqueue: make([]EventOrError, 0, 1000), + EvqueueLck: &sync.RWMutex{}, + setup: setup, + screen: screen, + root: screen.Root, + eventTime: xproto.Timestamp(0), // last event time + Atoms: make(map[string]xproto.Atom, 50), + AtomsLck: &sync.RWMutex{}, + AtomNames: make(map[xproto.Atom]string, 50), + AtomNamesLck: &sync.RWMutex{}, + Callbacks: make(map[int]map[xproto.Window][]Callback, 33), + CallbacksLck: &sync.RWMutex{}, + Hooks: make([]CallbackHook, 0), + HooksLck: &sync.RWMutex{}, + Keymap: nil, // we don't have anything yet + Modmap: nil, + KeyRedirect: 0, + Keybinds: make(map[KeyKey][]CallbackKey, 10), + KeybindsLck: &sync.RWMutex{}, + Keygrabs: make(map[KeyKey]int, 10), + Keystrings: make([]KeyString, 0, 10), + Mousebinds: make(map[MouseKey][]CallbackMouse, 10), + MousebindsLck: &sync.RWMutex{}, + Mousegrabs: make(map[MouseKey]int, 10), + InMouseDrag: false, + MouseDragStepFun: nil, + MouseDragEndFun: nil, + ErrorHandler: func(err xgb.Error) { Logger.Println(err) }, + } + + var err error = nil + // Create a general purpose graphics context + xu.gc, err = xproto.NewGcontextId(xu.conn) + if err != nil { + return nil, err + } + xproto.CreateGC(xu.conn, xu.gc, xproto.Drawable(xu.root), + xproto.GcForeground, []uint32{xu.screen.WhitePixel}) + + // Create a dummy window + xu.dummy, err = xproto.NewWindowId(xu.conn) + if err != nil { + return nil, err + } + xproto.CreateWindow(xu.conn, xu.Screen().RootDepth, xu.dummy, xu.RootWin(), + -1000, -1000, 1, 1, 0, + xproto.WindowClassInputOutput, xu.Screen().RootVisual, + xproto.CwEventMask|xproto.CwOverrideRedirect, + []uint32{1, xproto.EventMaskPropertyChange}) + xproto.MapWindow(xu.conn, xu.dummy) + + // Register the Xinerama extension... because it doesn't cost much. + err = xinerama.Init(xu.conn) + + // If we can't register Xinerama, that's okay. Output something + // and move on. + if err != nil { + Logger.Printf("WARNING: %s\n", err) + Logger.Printf("MESSAGE: The 'xinerama' package cannot be used " + + "because the XINERAMA extension could not be loaded.") + } + + return xu, nil +} + +// Conn returns the xgb connection object. +func (xu *XUtil) Conn() *xgb.Conn { + return xu.conn +} + +// ExtInitialized returns true if an extension has been initialized. +// This is useful for determining whether an extension is available or not. +func (xu *XUtil) ExtInitialized(extName string) bool { + _, ok := xu.Conn().Extensions[extName] + return ok +} + +// Sync forces XGB to catch up with all events/requests and synchronize. +// This is done by issuing a benign round trip request to X. +func (xu *XUtil) Sync() { + xproto.GetInputFocus(xu.Conn()).Reply() +} + +// Setup returns the setup information retrieved during connection time. +func (xu *XUtil) Setup() *xproto.SetupInfo { + return xu.setup +} + +// Screen returns the default screen +func (xu *XUtil) Screen() *xproto.ScreenInfo { + return xu.screen +} + +// RootWin returns the current root window. +func (xu *XUtil) RootWin() xproto.Window { + return xu.root +} + +// RootWinSet will change the current root window to the one provided. +// N.B. This probably shouldn't be used unless you're desperately trying +// to support multiple X screens. (This is *not* the same as Xinerama/RandR or +// TwinView. All of those have a single root window.) +func (xu *XUtil) RootWinSet(root xproto.Window) { + xu.root = root +} + +// TimeGet gets the most recent time seen by an event. +func (xu *XUtil) TimeGet() xproto.Timestamp { + return xu.eventTime +} + +// TimeSet sets the most recent time seen by an event. +func (xu *XUtil) TimeSet(t xproto.Timestamp) { + xu.eventTime = t +} + +// GC gets a general purpose graphics context that is typically used to simply +// paint images. +func (xu *XUtil) GC() xproto.Gcontext { + return xu.gc +} + +// Dummy gets the id of the dummy window. +func (xu *XUtil) Dummy() xproto.Window { + return xu.dummy +} + +// Grabs the server. Everything becomes synchronous. +func (xu *XUtil) Grab() { + xproto.GrabServer(xu.Conn()) +} + +// Ungrabs the server. +func (xu *XUtil) Ungrab() { + xproto.UngrabServer(xu.Conn()) +} diff --git a/vendor/github.com/BurntSushi/xgbutil/xprop/atom.go b/vendor/github.com/BurntSushi/xgbutil/xprop/atom.go new file mode 100644 index 0000000..e2e0f20 --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/xprop/atom.go @@ -0,0 +1,103 @@ +package xprop + +/* +xprop/atom.go contains functions related to interning atoms and retrieving +atom names from an atom identifier. + +It also manages an atom cache so that once an atom is interned from the X +server, all future atom interns use that value. (So that one and only one +request is sent for interning each atom.) +*/ + +import ( + "fmt" + + "github.com/BurntSushi/xgb/xproto" + + "github.com/BurntSushi/xgbutil" +) + +// Atm is a short alias for Atom in the common case of interning an atom. +// Namely, interning the atom always succeeds. (If the atom does not already +// exist, a new one is created.) +func Atm(xu *xgbutil.XUtil, name string) (xproto.Atom, error) { + aid, err := Atom(xu, name, false) + if err != nil { + return 0, err + } + if aid == 0 { + return 0, fmt.Errorf("Atm: '%s' returned an identifier of 0.", name) + } + + return aid, err +} + +// Atom interns an atom and panics if there is any error. +func Atom(xu *xgbutil.XUtil, name string, + onlyIfExists bool) (xproto.Atom, error) { + + // Check the cache first + if aid, ok := atomGet(xu, name); ok { + return aid, nil + } + + reply, err := xproto.InternAtom(xu.Conn(), onlyIfExists, + uint16(len(name)), name).Reply() + if err != nil { + return 0, fmt.Errorf("Atom: Error interning atom '%s': %s", name, err) + } + + // If we're here, it means we didn't have this atom cached. So cache it! + cacheAtom(xu, name, reply.Atom) + + return reply.Atom, nil +} + +// AtomName fetches a string representation of an ATOM given its integer id. +func AtomName(xu *xgbutil.XUtil, aid xproto.Atom) (string, error) { + // Check the cache first + if atomName, ok := atomNameGet(xu, aid); ok { + return string(atomName), nil + } + + reply, err := xproto.GetAtomName(xu.Conn(), aid).Reply() + if err != nil { + return "", fmt.Errorf("AtomName: Error fetching name for ATOM "+ + "id '%d': %s", aid, err) + } + + // If we're here, it means we didn't have ths ATOM id cached. So cache it. + atomName := string(reply.Name) + cacheAtom(xu, atomName, aid) + + return atomName, nil +} + +// atomGet retrieves an atom identifier from a cache if it exists. +func atomGet(xu *xgbutil.XUtil, name string) (xproto.Atom, bool) { + xu.AtomsLck.RLock() + defer xu.AtomsLck.RUnlock() + + aid, ok := xu.Atoms[name] + return aid, ok +} + +// atomNameGet retrieves an atom name from a cache if it exists. +func atomNameGet(xu *xgbutil.XUtil, aid xproto.Atom) (string, bool) { + xu.AtomNamesLck.RLock() + defer xu.AtomNamesLck.RUnlock() + + name, ok := xu.AtomNames[aid] + return name, ok +} + +// cacheAtom puts an atom into the cache. +func cacheAtom(xu *xgbutil.XUtil, name string, aid xproto.Atom) { + xu.AtomsLck.Lock() + xu.AtomNamesLck.Lock() + defer xu.AtomsLck.Unlock() + defer xu.AtomNamesLck.Unlock() + + xu.Atoms[name] = aid + xu.AtomNames[aid] = name +} diff --git a/vendor/github.com/BurntSushi/xgbutil/xprop/doc.go b/vendor/github.com/BurntSushi/xgbutil/xprop/doc.go new file mode 100644 index 0000000..6dcf761 --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/xprop/doc.go @@ -0,0 +1,40 @@ +/* +Package xprop provides a cache for interning atoms and helper functions for +dealing with GetProperty and ChangeProperty X requests. + +Atoms + +Atoms in X are interned, meaning that strings are assigned unique integer +identifiers. This minimizes the amount of data transmitted over an X connection. + +Once atoms have been interned, they are never changed while the X server is +running. xgbutil takes advantage of this invariant and will only issue an +intern atom request once and cache the result. + +To use the xprop package to intern an atom, use Atom: + + atom, err := xprop.Atom(XUtilValue, "THE_ATOM_NAME", false) + if err == nil { + println("The atom number: ", atom.Atom) + } + +The 'false' parameter corresponds to the 'only_if_exists' parameter of the +X InternAtom request. When it's false, the atom being interned always returns +a non-zero atom number---even if the string being interned hasn't been interned +before. If 'only_if_exists' is true, the atom being interned will return a 0 +atom number if it hasn't already been interned. + +The typical case is to set 'only_if_exists' to false. To this end, xprop.Atm is +an alias that always sets this value to false. + +The reverse can also be done: getting an atom string if you have an atom +number. This can be done with the xprop.AtomName function. + +Properties + +The other facility of xprop is to help with the use of GetProperty and +ChangeProperty. Please see the source code of the ewmh package for plenty of +examples. + +*/ +package xprop diff --git a/vendor/github.com/BurntSushi/xgbutil/xprop/xprop.go b/vendor/github.com/BurntSushi/xgbutil/xprop/xprop.go new file mode 100644 index 0000000..950013b --- /dev/null +++ b/vendor/github.com/BurntSushi/xgbutil/xprop/xprop.go @@ -0,0 +1,270 @@ +package xprop + +import ( + "fmt" + + "github.com/BurntSushi/xgb" + "github.com/BurntSushi/xgb/xproto" + + "github.com/BurntSushi/xgbutil" +) + +// GetProperty abstracts the messiness of calling xgb.GetProperty. +func GetProperty(xu *xgbutil.XUtil, win xproto.Window, atom string) ( + *xproto.GetPropertyReply, error) { + + atomId, err := Atm(xu, atom) + if err != nil { + return nil, err + } + + reply, err := xproto.GetProperty(xu.Conn(), false, win, atomId, + xproto.GetPropertyTypeAny, 0, (1<<32)-1).Reply() + + if err != nil { + return nil, fmt.Errorf("GetProperty: Error retrieving property '%s' "+ + "on window %x: %s", atom, win, err) + } + + if reply.Format == 0 { + return nil, fmt.Errorf("GetProperty: No such property '%s' on "+ + "window %x.", atom, win) + } + + return reply, nil +} + +// ChangeProperty abstracts the semi-nastiness of xgb.ChangeProperty. +func ChangeProp(xu *xgbutil.XUtil, win xproto.Window, format byte, prop string, + typ string, data []byte) error { + + propAtom, err := Atm(xu, prop) + if err != nil { + return err + } + + typAtom, err := Atm(xu, typ) + if err != nil { + return err + } + + return xproto.ChangePropertyChecked(xu.Conn(), xproto.PropModeReplace, win, + propAtom, typAtom, format, + uint32(len(data)/(int(format)/8)), data).Check() +} + +// ChangeProperty32 makes changing 32 bit formatted properties easier +// by constructing the raw X data for you. +func ChangeProp32(xu *xgbutil.XUtil, win xproto.Window, prop string, typ string, + data ...uint) error { + + buf := make([]byte, len(data)*4) + for i, datum := range data { + xgb.Put32(buf[(i*4):], uint32(datum)) + } + + return ChangeProp(xu, win, 32, prop, typ, buf) +} + +// WindowToUint is a covenience function for converting []xproto.Window +// to []uint. +func WindowToInt(ids []xproto.Window) []uint { + ids32 := make([]uint, len(ids)) + for i, v := range ids { + ids32[i] = uint(v) + } + return ids32 +} + +// AtomToInt is a covenience function for converting []xproto.Atom +// to []uint. +func AtomToUint(ids []xproto.Atom) []uint { + ids32 := make([]uint, len(ids)) + for i, v := range ids { + ids32[i] = uint(v) + } + return ids32 +} + +// StrToAtoms is a convenience function for converting +// []string to []uint32 atoms. +// NOTE: If an atom name in the list doesn't exist, it will be created. +func StrToAtoms(xu *xgbutil.XUtil, atomNames []string) ([]uint, error) { + var err error + atoms := make([]uint, len(atomNames)) + for i, atomName := range atomNames { + a, err := Atom(xu, atomName, false) + if err != nil { + return nil, err + } + atoms[i] = uint(a) + } + return atoms, err +} + +// PropValAtom transforms a GetPropertyReply struct into an ATOM name. +// The property reply must be in 32 bit format. +func PropValAtom(xu *xgbutil.XUtil, reply *xproto.GetPropertyReply, + err error) (string, error) { + + if err != nil { + return "", err + } + if reply.Format != 32 { + return "", fmt.Errorf("PropValAtom: Expected format 32 but got %d", + reply.Format) + } + + return AtomName(xu, xproto.Atom(xgb.Get32(reply.Value))) +} + +// PropValAtoms is the same as PropValAtom, except that it returns a slice +// of atom names. Also must be 32 bit format. +// This is a method of an XUtil struct, unlike the other 'PropVal...' functions. +func PropValAtoms(xu *xgbutil.XUtil, reply *xproto.GetPropertyReply, + err error) ([]string, error) { + + if err != nil { + return nil, err + } + if reply.Format != 32 { + return nil, fmt.Errorf("PropValAtoms: Expected format 32 but got %d", + reply.Format) + } + + ids := make([]string, reply.ValueLen) + vals := reply.Value + for i := 0; len(vals) >= 4; i++ { + ids[i], err = AtomName(xu, xproto.Atom(xgb.Get32(vals))) + if err != nil { + return nil, err + } + + vals = vals[4:] + } + return ids, nil +} + +// PropValWindow transforms a GetPropertyReply struct into an X resource +// window identifier. +// The property reply must be in 32 bit format. +func PropValWindow(reply *xproto.GetPropertyReply, + err error) (xproto.Window, error) { + + if err != nil { + return 0, err + } + if reply.Format != 32 { + return 0, fmt.Errorf("PropValId: Expected format 32 but got %d", + reply.Format) + } + return xproto.Window(xgb.Get32(reply.Value)), nil +} + +// PropValWindows is the same as PropValWindow, except that it returns a slice +// of identifiers. Also must be 32 bit format. +func PropValWindows(reply *xproto.GetPropertyReply, + err error) ([]xproto.Window, error) { + + if err != nil { + return nil, err + } + if reply.Format != 32 { + return nil, fmt.Errorf("PropValIds: Expected format 32 but got %d", + reply.Format) + } + + ids := make([]xproto.Window, reply.ValueLen) + vals := reply.Value + for i := 0; len(vals) >= 4; i++ { + ids[i] = xproto.Window(xgb.Get32(vals)) + vals = vals[4:] + } + return ids, nil +} + +// PropValNum transforms a GetPropertyReply struct into an unsigned +// integer. Useful when the property value is a single integer. +func PropValNum(reply *xproto.GetPropertyReply, err error) (uint, error) { + if err != nil { + return 0, err + } + if reply.Format != 32 { + return 0, fmt.Errorf("PropValNum: Expected format 32 but got %d", + reply.Format) + } + return uint(xgb.Get32(reply.Value)), nil +} + +// PropValNums is the same as PropValNum, except that it returns a slice +// of integers. Also must be 32 bit format. +func PropValNums(reply *xproto.GetPropertyReply, err error) ([]uint, error) { + if err != nil { + return nil, err + } + if reply.Format != 32 { + return nil, fmt.Errorf("PropValIds: Expected format 32 but got %d", + reply.Format) + } + + nums := make([]uint, reply.ValueLen) + vals := reply.Value + for i := 0; len(vals) >= 4; i++ { + nums[i] = uint(xgb.Get32(vals)) + vals = vals[4:] + } + return nums, nil +} + +// PropValNum64 transforms a GetPropertyReply struct into a 64 bit +// integer. Useful when the property value is a single integer. +func PropValNum64(reply *xproto.GetPropertyReply, err error) (int64, error) { + if err != nil { + return 0, err + } + if reply.Format != 32 { + return 0, fmt.Errorf("PropValNum: Expected format 32 but got %d", + reply.Format) + } + return int64(xgb.Get32(reply.Value)), nil +} + +// PropValStr transforms a GetPropertyReply struct into a string. +// Useful when the property value is a null terminated string represented +// by integers. Also must be 8 bit format. +func PropValStr(reply *xproto.GetPropertyReply, err error) (string, error) { + if err != nil { + return "", err + } + if reply.Format != 8 { + return "", fmt.Errorf("PropValStr: Expected format 8 but got %d", + reply.Format) + } + return string(reply.Value), nil +} + +// PropValStrs is the same as PropValStr, except that it returns a slice +// of strings. The raw byte string is a sequence of null terminated strings, +// which is translated into a slice of strings. +func PropValStrs(reply *xproto.GetPropertyReply, err error) ([]string, error) { + if err != nil { + return nil, err + } + if reply.Format != 8 { + return nil, fmt.Errorf("PropValStrs: Expected format 8 but got %d", + reply.Format) + } + + var strs []string + sstart := 0 + for i, c := range reply.Value { + if c == 0 { + strs = append(strs, string(reply.Value[sstart:i])) + sstart = i + 1 + } + } + if sstart < int(reply.ValueLen) { + strs = append(strs, string(reply.Value[sstart:])) + } + return strs, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 1b5e45a..2d4f9d1 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -41,8 +41,17 @@ github.com/BurntSushi/toml # github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 github.com/BurntSushi/xgb github.com/BurntSushi/xgb/render +github.com/BurntSushi/xgb/shape github.com/BurntSushi/xgb/shm +github.com/BurntSushi/xgb/xinerama github.com/BurntSushi/xgb/xproto +# github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 +## explicit +github.com/BurntSushi/xgbutil +github.com/BurntSushi/xgbutil/ewmh +github.com/BurntSushi/xgbutil/icccm +github.com/BurntSushi/xgbutil/xevent +github.com/BurntSushi/xgbutil/xprop # github.com/aarzilli/nucular v0.0.0-20200615134801-81910c722bba ## explicit github.com/aarzilli/nucular