func newWindow(X *xgbutil.XUtil) { win, err := xwindow.Generate(X) if err != nil { log.Fatal(err) } win.Create(X.RootWin(), 0, 0, 200, 200, xproto.CwBackPixel|xproto.CwEventMask, 0, xproto.EventMaskButtonRelease) win.WMGracefulClose( func(w *xwindow.Window) { xevent.Detach(w.X, w.Id) mousebind.Detach(w.X, w.Id) w.Destroy() xevent.Quit(X) Done <- true }) win.Map() err = mousebind.ButtonReleaseFun( func(X *xgbutil.XUtil, ev xevent.ButtonReleaseEvent) { newWindow(X) }).Connect(X, win.Id, "1", false, false) if err != nil { log.Fatal(err) } }
// _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) }
// RootGeometry gets the geometry of the root window. It will panic on failure. func RootGeometry(xu *xgbutil.XUtil) xrect.Rect { geom, err := RawGeometry(xu, xproto.Drawable(xu.RootWin())) if err != nil { panic(err) } return geom }
// compose a number of rectabgles into a window shape func ComposeShape(X *xgbutil.XUtil, dst xproto.Window, rects []xrect.Rect) (err error) { combine_bounds := make([]shape.CombineCookie, len(rects)) combine_clip := make([]shape.CombineCookie, len(rects)) var operation shape.Op for i, rect := range rects { // make rectangular window of correct goemetry win, err := xwindow.Generate(X) if err != nil { log.Fatalf("ComposeShape: Error creating rectange %v window.", rect) return err } win.Create(X.RootWin(), rect.X(), rect.Y(), rect.Width(), rect.Height(), xproto.CwBackPixel, 0xffffff) // choose operation. on the first one, we want to set the shape. if i == 0 { operation = shape.SoSet } else { operation = shape.SoUnion } // combine window request x, y := int16(rect.X()), int16(rect.Y()) combine_kind := shape.Kind(shape.SkBounding) combine_bounds[i] = shape.CombineChecked(X.Conn(), operation, combine_kind, combine_kind, dst, x, y, win.Id) combine_kind = shape.Kind(shape.SkClip) combine_clip[i] = shape.CombineChecked(X.Conn(), operation, combine_kind, combine_kind, dst, x, y, win.Id) } return nil }
// New allocates and initializes a new DockApp. NewDockApp does not initialize // the window contents and does not map the window to the display screen. The // window is mapped to the screen when the Main method is called on the // returned DockApp. func New(x *xgbutil.XUtil, rect image.Rectangle) (*DockApp, error) { win, err := xwindow.Generate(x) if err != nil { log.Fatalf("generate window: %v", err) } win.Create(x.RootWin(), 0, 0, rect.Size().X, rect.Size().Y, 0) // Set WM hints so that Openbox puts the window into the dock. hints := &icccm.Hints{ Flags: icccm.HintState | icccm.HintIconWindow, InitialState: icccm.StateWithdrawn, IconWindow: win.Id, WindowGroup: win.Id, } err = icccm.WmHintsSet(x, win.Id, hints) if err != nil { win.Destroy() return nil, fmt.Errorf("wm hints: %v", err) } img := xgraphics.New(x, rect) err = img.XSurfaceSet(win.Id) if err != nil { img.Destroy() win.Destroy() return nil, fmt.Errorf("xsurface set: %v", err) } app := &DockApp{ x: x, img: img, win: win, } return app, nil }
func newParent(X *xgbutil.XUtil, cid xproto.Window) (*Parent, error) { parent, err := xwindow.Generate(X) if err != nil { logger.Error.Printf("Could not create a parent window for client "+ "with id '%d' because: %s", cid, err) logger.Error.Fatalf("In a state where no new windows can be created. " + "Unfortunately, we must exit.") } err = parent.CreateChecked(X.RootWin(), 0, 0, 1, 1, xproto.CwEventMask, xproto.EventMaskSubstructureRedirect| xproto.EventMaskButtonPress|xproto.EventMaskButtonRelease) if err != nil { return nil, err } err = xproto.ReparentWindowChecked(X.Conn(), cid, parent.Id, 0, 0).Check() if err != nil { return nil, err } return &Parent{parent}, nil }
func newWindow(controlCh *controlCh, X *xgbutil.XUtil, width, height int) *xwindow.Window { var ( err error win *xwindow.Window ) win, err = xwindow.Generate(X) if err != nil { panic(err) } win.Create(X.RootWin(), 0, 0, width, height, xproto.CwBackPixel|xproto.CwEventMask, 0, xproto.EventMaskButtonRelease) // Xorg application exits when the window is closed. win.WMGracefulClose( func(w *xwindow.Window) { xevent.Detach(w.X, w.Id) mousebind.Detach(w.X, w.Id) w.Destroy() xevent.Quit(X) controlCh.exit <- true }) // In order to get ConfigureNotify events, we must listen to the window // using the 'StructureNotify' mask. win.Listen(xproto.EventMaskButtonPress | xproto.EventMaskButtonRelease | xproto.EventMaskKeyPress | xproto.EventMaskKeyRelease | xproto.EventMaskStructureNotify) win.Map() return win }
// find the EWHM window under the mouse cursor func FindManagedWindowUnderMouse(X *xgbutil.XUtil) (xproto.Window, error) { // construct a hashset of the managed windows clients, err := ewmh.ClientListGet(X) if err != nil { return 0, fmt.Errorf("FindUnderMouse: could not retrieve EWHM client list: %v", err) } managed := make(map[xproto.Window]bool, len(clients)) for _, win := range clients { managed[win] = true } cur_window := X.RootWin() // descend the QueryTree to the first child that is a EWMH managed window for { // return the parent if it is an EWHM window if _, ok := managed[cur_window]; ok { return cur_window, nil } cur_window, _, err = FindNextUnderMouse(X, cur_window) if err != nil { break } } // we didn't find the window :( return 0, errors.New("FindUnderMouse: no EWMH window found under mouse") }
func newAudio(Xu *xgbutil.XUtil) (*audio, error) { m, err := OpenMixer() if err != nil { return nil, err } defer func() { // set m to nil on final success if m != nil { _ = m.Close() } }() a := &audio{ mixer: m, } for _, k := range []struct { key string fn keybind.KeyPressFun }{ {"XF86AudioRaiseVolume", a.up}, {"XF86AudioLowerVolume", a.down}, {"XF86AudioMute", a.mute}, } { if err := k.fn.Connect(Xu, Xu.RootWin(), k.key, true); err != nil { return nil, err } } m = nil return a, nil }
func newWindow(X *xgbutil.XUtil, width, height int) *xwindow.Window { var ( err error win *xwindow.Window ) win, err = xwindow.Generate(X) if err != nil { log.Fatal(err) } win.Create(X.RootWin(), 0, 0, width, height, xproto.CwBackPixel|xproto.CwEventMask, 0, xproto.EventMaskButtonRelease) win.WMGracefulClose( func(w *xwindow.Window) { xevent.Detach(w.X, w.Id) mousebind.Detach(w.X, w.Id) w.Destroy() xevent.Quit(X) }) win.Map() if err != nil { log.Fatal(err) } return win }
// When a cross is declared in its object literal form, it may not have the appropriate window. // this function creates a new X11 window for the cross with the correct geometry depending // on its Icon* parameters. func (c *Cross) CreateWindow(X *xgbutil.XUtil, icons_per_direction int, bg_color uint32) (*xwindow.Window, error) { // calculate the dimensions of the spars of our cross + // width/height reflect the vertical-orientation rectangle width := c.IconMargin*2 + c.IconSize // padding between the icons, margin on the edges height := c.IconSize*icons_per_direction + c.IconPadding*(icons_per_direction-1) + c.IconMargin*2 // intitialize a basic window for the cross win, err := xwindow.Generate(X) if err != nil { return nil, err } win.Create(X.RootWin(), 0, 0, height, height, xproto.CwBackPixel|xproto.CwOverrideRedirect, bg_color, 1) // the rects we will be adding together to form the shape of the cross vert := xrect.New(0, 0, width, height) horiz := xrect.New(0, 0, height, width) struts := []xrect.Rect{vert, horiz} geom, err := win.Geometry() if err != nil { return nil, err } // center struts over window x, y := util.CenterChild(vert, geom) vert.XSet(x) vert.YSet(y) x, y = util.CenterChild(horiz, geom) horiz.XSet(x) horiz.YSet(y) // build the cross shape from our friendly rectangles err = ComposeShape(X, win.Id, struts) if err != nil { return nil, err } // add the window to our cross struct c.Window = win // create icons from our images clr := RGB(bg_color) if c.imagesToBecomeIcons != nil { icons := make(map[string]*Icon, len(c.imagesToBecomeIcons)) for name, img := range c.imagesToBecomeIcons { icon := NewIcon(X, img, win.Id) icon.Background = clr icons[name] = icon } c.Icons = icons } else { return nil, errors.New("Cross: you must create crosses using the NewCross function (this cross has now iconsToBecomeImage)") } return win, nil }
func keyHandlers(X *xgbutil.XUtil, cycle *prompt.Cycle, items []*prompt.CycleItem) { geom := headGeom(X) keybind.KeyPressFun( func(X *xgbutil.XUtil, ev xevent.KeyPressEvent) { shown := cycle.Show(geom, cycleNext, items) if !shown { log.Fatal("Did not show cycle prompt.") } cycle.Next() }).Connect(X, X.RootWin(), cycleNext, true) keybind.KeyPressFun( func(X *xgbutil.XUtil, ev xevent.KeyPressEvent) { cycle.Next() }).Connect(X, cycle.GrabId(), cycleNext, true) keybind.KeyPressFun( func(X *xgbutil.XUtil, ev xevent.KeyPressEvent) { shown := cycle.Show(geom, cyclePrev, items) if !shown { log.Fatal("Did not show cycle prompt.") } cycle.Prev() }).Connect(X, X.RootWin(), cyclePrev, true) keybind.KeyPressFun( func(X *xgbutil.XUtil, ev xevent.KeyPressEvent) { cycle.Prev() }).Connect(X, cycle.GrabId(), cyclePrev, true) }
func bind(Xu *xgbutil.XUtil, bindings ...binding) error { for _, k := range bindings { if err := k.fn.Connect(Xu, Xu.RootWin(), k.key, true); err != nil { return err } } return nil }
// sendClientMessages is a goroutine that sends client messages to the root // window. We then listen to them later as a demonstration of responding to // X events. (They are sent with SubstructureNotify and SubstructureRedirect // masks set. So in order to receive them, we'll have to explicitly listen // to events of that type on the root window.) func xSource(X *xgbutil.XUtil) { i := 1 for { ewmh.ClientEvent(X, X.RootWin(), "NOOP", i) i++ time.Sleep(200 * time.Millisecond) } }
// _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_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) }
// _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_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) }
func NewInput(X *xgbutil.XUtil, theme *InputTheme, config InputConfig) *Input { input := &Input{ X: X, theme: theme, config: config, showing: false, do: nil, history: make([]string, 0, 100), } // Create all windows used for the base of the input prompt. cwin := func(p xproto.Window) *xwindow.Window { return xwindow.Must(xwindow.Create(X, p)) } input.win = cwin(X.RootWin()) input.label = cwin(input.win.Id) input.input = text.NewInput(X, input.win.Id, 1000, theme.Padding, theme.Font, theme.FontSize, theme.FontColor, theme.BgColor) input.bInp = cwin(input.win.Id) input.bTop, input.bBot = cwin(input.win.Id), cwin(input.win.Id) input.bLft, input.bRht = cwin(input.win.Id), cwin(input.win.Id) // Make the top-level window override redirect so the window manager // doesn't mess with us. input.win.Change(xproto.CwOverrideRedirect, 1) input.win.Listen(xproto.EventMaskFocusChange) input.input.Listen(xproto.EventMaskKeyPress) // Colorize the windows. cclr := func(w *xwindow.Window, clr render.Color) { w.Change(xproto.CwBackPixel, clr.Uint32()) } cclr(input.win, input.theme.BgColor) cclr(input.bInp, input.theme.BorderColor) cclr(input.bTop, input.theme.BorderColor) cclr(input.bBot, input.theme.BorderColor) cclr(input.bLft, input.theme.BorderColor) cclr(input.bRht, input.theme.BorderColor) // Map the sub-windows once. input.label.Map() input.input.Map() input.bInp.Map() input.bTop.Map() input.bBot.Map() input.bLft.Map() input.bRht.Map() // Connect the key response handler. // The handler is responsible for tab completion and quitting if the // cancel key has been pressed. input.keyResponse().Connect(X, input.input.Id) input.focusResponse().Connect(X, input.win.Id) return input }
func (hotkey Hotkey) attach(X *xgbutil.XUtil) { log.Println(hotkey.Key) err := keybind.KeyPressFun( func(X *xgbutil.XUtil, e xevent.KeyPressEvent) { go exec.Command("/bin/sh", "-c", hotkey.Cmd).Run() }).Connect(X, X.RootWin(), hotkey.Key, true) if err != nil { log.Fatalf("Could not bind %s: %s", hotkey.Key, err.Error()) } }
// _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) }
// _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_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) }
func headGeom(X *xgbutil.XUtil) xrect.Rect { if X.ExtInitialized("XINERAMA") { heads, err := xinerama.PhysicalHeads(X) if err == nil { return heads[0] } } geom, err := xwindow.New(X, X.RootWin()).Geometry() fatal(err) return geom }
func StatusSet(X *xgbutil.XUtil, status bool) { var statusStr string if status { statusStr = "Success" } else { statusStr = "Error" } // throw away the error xprop.ChangeProp(X, X.RootWin(), 8, "_WINGO_CMD_STATUS", "UTF8_STRING", []byte(statusStr)) }
// _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...) }
// newWindow creates a new window with a random background color. It sets the // WM_PROTOCOLS property to contain the WM_DELETE_WINDOW atom. It also sets // up a ClientMessage event handler so that we know when to destroy the window. // We also set up a mouse binding so that clicking inside a window will // create another one. func newWindow(X *xgbutil.XUtil) { counter++ win, err := xwindow.Generate(X) if err != nil { log.Fatal(err) } // Get a random background color, create the window (ask to receive button // release events while we're at it) and map the window. bgColor := rand.Intn(0xffffff + 1) win.Create(X.RootWin(), 0, 0, 200, 200, xproto.CwBackPixel|xproto.CwEventMask, uint32(bgColor), xproto.EventMaskButtonRelease) // WMGracefulClose does all of the work for us. It sets the appropriate // values for WM_PROTOCOLS, and listens for ClientMessages that implement // the WM_DELETE_WINDOW protocol. When one is found, the provided callback // is executed. win.WMGracefulClose( func(w *xwindow.Window) { // Detach all event handlers. // This should always be done when a window can no longer // receive events. xevent.Detach(w.X, w.Id) mousebind.Detach(w.X, w.Id) w.Destroy() // Exit if there are no more windows left. counter-- if counter == 0 { os.Exit(0) } }) // It's important that the map comes after setting WMGracefulClose, since // the WM isn't obliged to watch updates to the WM_PROTOCOLS property. win.Map() // A mouse binding so that a left click will spawn a new window. // Note that we don't issue a grab here. Typically, window managers will // grab a button press on the client window (which usually activates the // window), so that we'd end up competing with the window manager if we // tried to grab it. // Instead, we set a ButtonRelease mask when creating the window and attach // a mouse binding *without* a grab. err = mousebind.ButtonReleaseFun( func(X *xgbutil.XUtil, ev xevent.ButtonReleaseEvent) { newWindow(X) }).Connect(X, win.Id, "1", false, false) if err != nil { log.Fatal(err) } }
// currentTime forcefully causes a PropertyNotify event to fire on the root // window, then scans the event queue and picks up the time. // // It is NOT SAFE to call this function in a place other than Wingo's // initialization. Namely, this function subverts xevent's queue and reads // events directly from X. func currentTime(X *xgbutil.XUtil) (xproto.Timestamp, error) { wmClassAtom, err := xprop.Atm(X, "WM_CLASS") if err != nil { return 0, err } stringAtom, err := xprop.Atm(X, "STRING") if err != nil { return 0, err } // Make sure we're listening to PropertyChange events on the root window. err = xwindow.New(X, X.RootWin()).Listen(xproto.EventMaskPropertyChange) if err != nil { return 0, fmt.Errorf( "Could not listen to Root window events (PropertyChange): %s", err) } // Do a zero-length append on a property as suggested by ICCCM 2.1. err = xproto.ChangePropertyChecked( X.Conn(), xproto.PropModeAppend, X.RootWin(), wmClassAtom, stringAtom, 8, 0, nil).Check() if err != nil { return 0, err } // Now look for the PropertyNotify generated by that zero-length append // and return the timestamp attached to that event. // Note that we do this outside of xgbutil/xevent, since ownership // is literally the first thing we do after connecting to X. // (i.e., we don't have our event handling system initialized yet.) timeout := time.After(3 * time.Second) for { select { case <-timeout: return 0, fmt.Errorf( "Expected a PropertyNotify event to get a valid timestamp, " + "but never received one.") default: ev, err := X.Conn().PollForEvent() if err != nil { continue } if propNotify, ok := ev.(xproto.PropertyNotifyEvent); ok { X.TimeSet(propNotify.Time) // why not? return propNotify.Time, nil } time.Sleep(100 * time.Millisecond) } } panic("unreachable") }
// SetRoot sets the given background as the root window background. func SetRoot(X *xgbutil.XUtil, bg *xgraphics.Image) error { root := X.RootWin() if err := bg.XSurfaceSet(root); err != nil { return err } bg.XDraw() bg.XPaint(root) // FIXME: This doesn't set the pixmap persistently. As soon as the program // exits, the pixmap is destroyed. Find a way to make it persistent. xprop.ChangeProp32(X, root, "_XROOTPMAP_ID", "PIXMAP", uint(bg.Pixmap)) xprop.ChangeProp32(X, root, "ESETROOT_PMAP_ID", "PIXMAP", uint(bg.Pixmap)) return nil }
// _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 }