// Ungrab undoes Grab. It will handle all combinations of modifiers found // in xevent.IgnoreMods. func Ungrab(xu *xgbutil.XUtil, win xproto.Window, mods uint16, button xproto.Button) { for _, m := range xevent.IgnoreMods { xproto.UngrabButtonChecked(xu.Conn(), byte(button), win, mods|m).Check() } }
// 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 }
// Ungrab undoes Grab. It will handle all combinations od modifiers found // in xevent.IgnoreMods. func Ungrab(xu *xgbutil.XUtil, win xproto.Window, mods uint16, key xproto.Keycode) { for _, m := range xevent.IgnoreMods { xproto.UngrabKeyChecked(xu.Conn(), key, win, mods|m).Check() } }
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 }
// GrabKeyboard grabs the entire keyboard. // Returns whether GrabStatus is successful and an error if one is reported by // XGB. It is possible to not get an error and the grab to be unsuccessful. // The purpose of 'win' is that after a grab is successful, ALL Key*Events will // be sent to that window. Make sure you have a callback attached :-) func GrabKeyboard(xu *xgbutil.XUtil, win xproto.Window) error { reply, err := xproto.GrabKeyboard(xu.Conn(), false, win, 0, xproto.GrabModeAsync, xproto.GrabModeAsync).Reply() if err != nil { return fmt.Errorf("GrabKeyboard: Error grabbing keyboard on "+ "window '%x': %s", win, err) } switch reply.Status { case xproto.GrabStatusSuccess: // all is well case xproto.GrabStatusAlreadyGrabbed: return fmt.Errorf("GrabKeyboard: Could not grab keyboard. " + "Status: AlreadyGrabbed.") case xproto.GrabStatusInvalidTime: return fmt.Errorf("GrabKeyboard: Could not grab keyboard. " + "Status: InvalidTime.") case xproto.GrabStatusNotViewable: return fmt.Errorf("GrabKeyboard: Could not grab keyboard. " + "Status: NotViewable.") case xproto.GrabStatusFrozen: return fmt.Errorf("GrabKeyboard: Could not grab keyboard. " + "Status: Frozen.") } return nil }
// NewDrawable converts an X drawable into a xgraphics.Image. // This is used in NewIcccmIcon. func NewDrawable(X *xgbutil.XUtil, did xproto.Drawable) (*Image, error) { // Get the geometry of the pixmap for use in the GetImage request. pgeom, err := xwindow.RawGeometry(X, xproto.Drawable(did)) if err != nil { return nil, err } // Get the image data for each pixmap. pixmapData, err := xproto.GetImage(X.Conn(), xproto.ImageFormatZPixmap, did, 0, 0, uint16(pgeom.Width()), uint16(pgeom.Height()), (1<<32)-1).Reply() if err != nil { return nil, err } // Now create the xgraphics.Image and populate it with data from // pixmapData and maskData. ximg := New(X, image.Rect(0, 0, pgeom.Width(), pgeom.Height())) // We'll try to be a little flexible with the image format returned, // but not completely flexible. err = readDrawableData(X, ximg, did, pixmapData, pgeom.Width(), pgeom.Height()) if err != nil { return nil, err } return ximg, nil }
// CreateCursorExtra features all available parameters to creating a cursor. // It will return an error if there is a problem with any of the requests // made to create the cursor. // (This implies each request is a checked request. The performance loss is // probably acceptable since cursors should be created once and reused.) func CreateCursorExtra(xu *xgbutil.XUtil, cursor, foreRed, foreGreen, foreBlue, backRed, backGreen, backBlue uint16) (xproto.Cursor, error) { fontId, err := xproto.NewFontId(xu.Conn()) if err != nil { return 0, err } cursorId, err := xproto.NewCursorId(xu.Conn()) if err != nil { return 0, err } err = xproto.OpenFontChecked(xu.Conn(), fontId, uint16(len("cursor")), "cursor").Check() if err != nil { return 0, err } err = xproto.CreateGlyphCursorChecked(xu.Conn(), cursorId, fontId, fontId, cursor, cursor+1, foreRed, foreGreen, foreBlue, backRed, backGreen, backBlue).Check() if err != nil { return 0, err } err = xproto.CloseFontChecked(xu.Conn(), fontId).Check() if err != nil { return 0, err } return cursorId, nil }
// Generate is just like New, but generates a new X resource id for you. // Geom is initialized to (0, 0) 1x1. // It is possible for id generation to return an error, in which case, an // error is returned here. func Generate(xu *xgbutil.XUtil) (*Window, error) { wid, err := xproto.NewWindowId(xu.Conn()) if err != nil { return nil, err } return New(xu, wid), nil }
// PhyiscalHeads returns the list of heads in a physical ordering. // Namely, left to right then top to bottom. (Defined by (X, Y).) // Xinerama must have been initialized, otherwise the xinerama.QueryScreens // request will panic. // PhysicalHeads also checks to make sure each rectangle has a unique (x, y) // tuple, so as not to return the geometry of cloned displays. // (At present moment, xgbutil initializes Xinerama automatically during // initial connection.) func PhysicalHeads(xu *xgbutil.XUtil) (Heads, error) { xinfo, err := xinerama.QueryScreens(xu.Conn()).Reply() if err != nil { return nil, err } hds := make(Heads, 0) for _, info := range xinfo.ScreenInfo { head := xrect.New(int(info.XOrg), int(info.YOrg), int(info.Width), int(info.Height)) // Maybe Xinerama is enabled, but we have cloned displays... unique := true for _, h := range hds { if h.X() == head.X() && h.Y() == head.Y() { unique = false break } } if unique { hds = append(hds, head) } } sort.Sort(hds) return hds, nil }
// Grab grabs a key with mods on a particular window. // This will also grab all combinations of modifiers found in xevent.IgnoreMods. func Grab(xu *xgbutil.XUtil, win xproto.Window, mods uint16, key xproto.Keycode) { for _, m := range xevent.IgnoreMods { xproto.GrabKey(xu.Conn(), true, win, mods|m, key, xproto.GrabModeAsync, xproto.GrabModeAsync) } }
// RawGeometry isn't smart. It just queries the window given for geometry. func RawGeometry(xu *xgbutil.XUtil, win xproto.Drawable) (xrect.Rect, error) { xgeom, err := xproto.GetGeometry(xu.Conn(), win).Reply() if err != nil { return nil, err } return xrect.New(int(xgeom.X), int(xgeom.Y), int(xgeom.Width), int(xgeom.Height)), nil }
func clientMessage(X *xgbutil.XUtil, event xgb.Event) { ev := event.(xproto.ClientMessageEvent) cookie := xproto.GetAtomName(X.Conn(), ev.Type) reply, _ := cookie.Reply() log.WithFields(log.Fields{ "name": reply.Name, "data": ev.Data, }).Info("client Message:", ev) }
// translate an X, Y value from one window to the euclidean space of another. Useful for translating // the arbitrary position of some window's (0,0) point into root window space func TranslateCoordinatesSync(X *xgbutil.XUtil, src, dest xproto.Window, x, y int) (dest_x, dest_y int, err error) { Xx, Xy := int16(x), int16(y) cookie := xproto.TranslateCoordinates(X.Conn(), src, dest, Xx, Xy) reply, err := cookie.Reply() if err != nil { return 0, 0, err } dest_x, dest_y = int(reply.DstX), int(reply.DstY) return }
// wrapper around xproto.QueryPointer that performs a simple synchronous query func FindNextUnderMouse(X *xgbutil.XUtil, parent xproto.Window) (xproto.Window, *xproto.QueryPointerReply, error) { // start query pointer request cookie := xproto.QueryPointer(X.Conn(), parent) // block and get reply for client reply, err := cookie.Reply() if err != nil { return 0, nil, err } return reply.Child, reply, nil }
// 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") }
// GrabPointer grabs the entire pointer. // Returns whether GrabStatus is successful and an error if one is reported by // XGB. It is possible to not get an error and the grab to be unsuccessful. // The purpose of 'win' is that after a grab is successful, ALL Button*Events // will be sent to that window. Make sure you have a callback attached :-) func GrabPointer(xu *xgbutil.XUtil, win xproto.Window, confine xproto.Window, cursor xproto.Cursor) (bool, error) { reply, err := xproto.GrabPointer(xu.Conn(), false, win, pointerMasks, xproto.GrabModeAsync, xproto.GrabModeAsync, confine, cursor, 0).Reply() if err != nil { return false, fmt.Errorf("GrabPointer: Error grabbing pointer on "+ "window '%x': %s", win, err) } return reply.Status == xproto.GrabStatusSuccess, nil }
// Grab grabs a button with mods on a particular window. // Will also grab all combinations of modifiers found in xevent.IgnoreMods // If 'sync' is True, then no further events can be processed until the // grabbing client allows them to be. (Which is done via AllowEvents. Thus, // if sync is True, you *must* make some call to AllowEvents at some // point, or else your client will lock.) func Grab(xu *xgbutil.XUtil, win xproto.Window, mods uint16, button xproto.Button, sync bool) { var pSync byte = xproto.GrabModeAsync if sync { pSync = xproto.GrabModeSync } for _, m := range xevent.IgnoreMods { xproto.GrabButton(xu.Conn(), true, win, pointerMasks, pSync, xproto.GrabModeAsync, 0, 0, byte(button), mods|m) } }
// GrabChecked Grabs a key with mods on a particular window. // This is the same as Grab, except that it issue a checked request. // Which means that an error could be returned and handled on the spot. // (Checked requests are slower than unchecked requests.) // This will also grab all combinations of modifiers found in xevent.IgnoreMods. func GrabChecked(xu *xgbutil.XUtil, win xproto.Window, mods uint16, key xproto.Keycode) error { var err error for _, m := range xevent.IgnoreMods { err = xproto.GrabKeyChecked(xu.Conn(), true, win, mods|m, key, xproto.GrabModeAsync, xproto.GrabModeAsync).Check() if err != nil { return err } } return nil }
func GetOutputs(X *xgbutil.XUtil) (outputs []*randr.GetOutputInfoReply) { resources, err := randr.GetScreenResources(X.Conn(), X.RootWin()).Reply() if err != nil { return nil } for _, output := range resources.Outputs { oinfo, err := randr.GetOutputInfo(X.Conn(), output, 0).Reply() if err != nil { return nil } outputs = append(outputs, oinfo) } return outputs }
func handleMotionNotify(X *xgbutil.XUtil, ev xevent.MotionNotifyEvent) { qp, err := xproto.QueryPointer(X.Conn(), X.RootWin()).Reply() if err != nil { logger.Warning.Printf("Could not query pointer: %s", err) return } geom := xrect.New(int(qp.RootX), int(qp.RootY), 1, 1) if wrk := wm.Heads.FindMostOverlap(geom); wrk != nil { if wrk != wm.Workspace() { wm.SetWorkspace(wrk, false) wm.FocusFallback() } } }
// NewBackground creates an xgraphics.Image which spans the entire screen, // initialized to black. func NewBackground(X *xgbutil.XUtil) (*xgraphics.Image, error) { res, err := randr.GetScreenResources(X.Conn(), X.RootWin()).Reply() if err != nil { return nil, err } var bgRect image.Rectangle for _, output := range res.Outputs { r, err := util.OutputRect(X, output) // NOTE: It doesn't really matter if this returns a Zero Rectangle. if err != nil { return nil, err } bgRect = bgRect.Union(r) } return xgraphics.New(X, bgRect), nil }
// announce sends a ClientMessage event to the root window to let everyone // know that Wingo is the boss. (As per ICCCM 2.8.) func announce(X *xgbutil.XUtil) { typAtom, err := xprop.Atm(X, "MANAGER") if err != nil { logger.Warning.Println(err) return } manSelAtom, err := managerAtom(X) if err != nil { logger.Warning.Println(err) return } cm, err := xevent.NewClientMessage(32, X.RootWin(), typAtom, int(X.TimeGet()), int(manSelAtom), int(X.Dummy())) xproto.SendEvent(X.Conn(), false, X.RootWin(), xproto.EventMaskStructureNotify, string(cm.Bytes())) }
func (b *brightness) adjust(Xu *xgbutil.XUtil, increase bool) error { X := Xu.Conn() root := xproto.Setup(X).DefaultScreen(X).Root screens, err := randr.GetScreenResources(X, root).Reply() if err != nil { return fmt.Errorf("getting screen: %v", err) } for _, output := range screens.Outputs { query, err := randr.QueryOutputProperty(X, output, b.prop).Reply() if err != nil { if _, ok := err.(xproto.NameError); ok { // this output has no backlight continue } return fmt.Errorf("query backlight: %v", err) } if !query.Range { return errors.New("backlight brightness range not specified") } if len(query.ValidValues) != 2 { return fmt.Errorf("expected min and max, got: %v", query.ValidValues) } min, max := query.ValidValues[0], query.ValidValues[1] // log.Printf("backlight range: %d .. %d", min, max) get, err := randr.GetOutputProperty(X, output, b.prop, xproto.AtomNone, 0, 4, false, false).Reply() if err != nil { return fmt.Errorf("get backlight property: %v", err) } if get.Type != xproto.AtomInteger || get.NumItems != 1 || get.Format != 32 { return fmt.Errorf("backlight property value looks wrong") } old := *(*int32)(unsafe.Pointer(&get.Data[0])) // log.Printf("backlight data: %d", old) bri := delta5(old, min, max, increase) data := (*[4]byte)(unsafe.Pointer(&bri))[:] if err := randr.ChangeOutputPropertyChecked(X, output, b.prop, xproto.AtomInteger, 32, xproto.PropModeReplace, 1, data).Check(); err != nil { return err } } return 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() }
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.") } // clientAttrs, err := xproto.GetWindowAttributes(X.Conn(), cid).Reply() // if err != nil { // return nil, fmt.Errorf("Could not get window attributes: %s", err) // } // visual := clientAttrs.Visual // vdepth := getVisualDepth(X, visual) visual := X.Screen().RootVisual vdepth := X.Screen().RootDepth // logger.Debug.Printf("Visualid: %x, Depth: %d", visual, vdepth) err = xproto.CreateWindowChecked(X.Conn(), vdepth, parent.Id, X.RootWin(), 0, 0, 1, 1, 0, xproto.WindowClassInputOutput, visual, xproto.CwEventMask, []uint32{ xproto.EventMaskSubstructureRedirect | xproto.EventMaskButtonPress | xproto.EventMaskButtonRelease | xproto.EventMaskFocusChange, }).Check() if err != nil { return nil, fmt.Errorf("Could not create window: %s", err) } err = xproto.ReparentWindowChecked(X.Conn(), cid, parent.Id, 0, 0).Check() if err != nil { return nil, fmt.Errorf("Could not reparent window: %s", err) } return &Parent{ Window: parent, MoveState: &MoveState{}, ResizeState: &ResizeState{}, isMapped: false, }, 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 }
// GrabChecked grabs a button with mods on a particular window. It does the // same thing as Grab, but issues a checked request and returns an error // on failure. // Will also grab all combinations of modifiers found in xevent.IgnoreMods // If 'sync' is True, then no further events can be processed until the // grabbing client allows them to be. (Which is done via AllowEvents. Thus, // if sync is True, you *must* make some call to AllowEvents at some // point, or else your client will lock.) func GrabChecked(xu *xgbutil.XUtil, win xproto.Window, mods uint16, button xproto.Button, sync bool) error { var pSync byte = xproto.GrabModeAsync if sync { pSync = xproto.GrabModeSync } var err error for _, m := range xevent.IgnoreMods { err = xproto.GrabButtonChecked(xu.Conn(), true, win, pointerMasks, pSync, xproto.GrabModeAsync, 0, 0, byte(button), mods|m).Check() if err != nil { return err } } return nil }
func socketFilePath(X *xgbutil.XUtil) string { xc := X.Conn() name := fmt.Sprintf(":%d.%d", xc.DisplayNumber, xc.DefaultScreen) var runtimeDir string xdgRuntime := os.Getenv("XDG_RUNTIME_DIR") if len(xdgRuntime) > 0 { runtimeDir = path.Join(xdgRuntime, "wingo") } else { runtimeDir = path.Join(os.TempDir(), "wingo") } if err := os.MkdirAll(runtimeDir, 0777); err != nil { logger.Error.Fatalf("Could not create directory '%s': %s", runtimeDir, err) } return path.Join(runtimeDir, name) }
// 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 }
func newBrightness(Xu *xgbutil.XUtil) (*brightness, error) { const atomName = "Backlight" atomReply, err := xproto.InternAtom(Xu.Conn(), true, uint16(len(atomName)), atomName).Reply() if err != nil { return nil, fmt.Errorf("no backlight: %v", err) } b := &brightness{ prop: atomReply.Atom, } if err := bind(Xu, binding{"XF86MonBrightnessUp", b.up}, binding{"XF86MonBrightnessDown", b.down}, ); err != nil { return nil, err } return b, nil }