// NewCycle creates a new prompt. As many prompts as you want can be created, // and they could even technically be shown simultaneously so long as at most // one of them is using a grab. (The grab will fail for the others and they // will not be shown.) // // CycleTheme and CycleConfig values can either use DefaultCycle{Theme,Config} // values found in this package, or custom ones can be created using // composite literals. func NewCycle(X *xgbutil.XUtil, theme *CycleTheme, config CycleConfig) *Cycle { cycle := &Cycle{ X: X, theme: theme, config: config, showing: false, selected: -1, grabMods: 0, } // Create all windows used for the base of the cycle prompt. // This obviously doesn't include the windows representing the items. cwin := func(p xproto.Window) *xwindow.Window { return xwindow.Must(xwindow.Create(X, p)) } cycle.win = cwin(X.RootWin()) cycle.bTop, cycle.bBot = cwin(cycle.win.Id), cwin(cycle.win.Id) cycle.bLft, cycle.bRht = cwin(cycle.win.Id), cwin(cycle.win.Id) // Make the top-level window override redirect so the window manager // doesn't mess with us. cycle.win.Change(xproto.CwOverrideRedirect, 1) // Set the colors of each window. cclr := func(w *xwindow.Window, clr render.Color) { w.Change(xproto.CwBackPixel, uint32(clr.Int())) } cclr(cycle.win, cycle.theme.BgColor) cclr(cycle.bTop, cycle.theme.BorderColor) cclr(cycle.bBot, cycle.theme.BorderColor) cclr(cycle.bLft, cycle.theme.BorderColor) cclr(cycle.bRht, cycle.theme.BorderColor) // Map the sub-windows once. (Real mapping only happens when // cycle.win is mapped.) cycle.bTop.Map() cycle.bBot.Map() cycle.bLft.Map() cycle.bRht.Map() // Connect the key response handler (i.e., the alt-tab'ing, canceling, etc.) cycle.keyResponse().Connect(X, X.Dummy()) // Guess the maximum font height. _, cycle.fontHeight = xgraphics.TextMaxExtents( cycle.theme.Font, cycle.theme.FontSize, "A") cycle.fontHeight += misc.TextBreathe return cycle }
// 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())) }
// Drag is the public interface that will make the appropriate connections // to register a drag event for three functions: the begin function, the // step function and the end function. // The 'grabwin' is the window that the grab is placed on (and therefore the // window where all button events are redirected to after the drag has started), // and the 'win' is the window that the initial 'begin' callback is set on. // In typical use cases, these windows should be the same. // If 'grab' is false, then no pointer grab is issued. func Drag(xu *xgbutil.XUtil, grabwin xproto.Window, win xproto.Window, buttonStr string, grab bool, begin xgbutil.MouseDragBeginFun, step xgbutil.MouseDragFun, end xgbutil.MouseDragFun) { ButtonPressFun( func(xu *xgbutil.XUtil, ev xevent.ButtonPressEvent) { DragBegin(xu, ev, grabwin, win, begin, step, end) }).Connect(xu, win, buttonStr, false, grab) // If the grab win isn't the dummy, then setup event handlers for the // grab window. if grabwin != xu.Dummy() { xevent.MotionNotifyFun(dragStep).Connect(xu, grabwin) xevent.ButtonReleaseFun(DragEnd).Connect(xu, grabwin) } }
// DummyGrab grabs the keyboard and sends all key events to the dummy window. func DummyGrab(xu *xgbutil.XUtil) error { return SmartGrab(xu, xu.Dummy()) }
// own requests ownership over the role of window manager in the current // X environment. It can fail if it does not successfully get ownership. // // When 'replace' is true, Wingo will attempt to replace an window manager // that is currently running. Otherwise, Wingo will quit if a window manager // is running. func own(X *xgbutil.XUtil, replace bool) error { otherWmRunning := false otherWmName := "" otherWmOwner := xproto.Window(xproto.WindowNone) xTime, err := currentTime(X) if err != nil { return err } selAtom, err := managerAtom(X) if err != nil { return err } // Check to see if we need to replace. If so, determine whether to // continue based on `replace`. reply, err := xproto.GetSelectionOwner(X.Conn(), selAtom).Reply() if err != nil { return err } if reply.Owner != xproto.WindowNone { otherWmRunning = true otherWmOwner = reply.Owner // Look for the window manager's name for a nicer error message. otherWmName, err = ewmh.GetEwmhWM(X) if err != nil || len(otherWmName) == 0 { otherWmName = "Unknown" } // We need to listen for DestroyNotify events on the selection // owner in case we need to replace the WM. owner := xwindow.New(X, reply.Owner) if err = owner.Listen(xproto.EventMaskStructureNotify); err != nil { return err } } if otherWmRunning { if !replace { return fmt.Errorf( "Another window manager (%s) is already running. Please use "+ "the '--replace' option to replace the current window "+ "manager with Wingo.", otherWmName) } else { logger.Message.Printf( "Waiting for %s to shutdown and transfer ownership to us.", otherWmName) } } logger.Message.Printf("Setting selection owner...") err = xproto.SetSelectionOwnerChecked( X.Conn(), X.Dummy(), selAtom, xTime).Check() if err != nil { return err } // Now we've got to make sure that we *actually* got ownership. logger.Message.Printf("Getting selection owner...") reply, err = xproto.GetSelectionOwner(X.Conn(), selAtom).Reply() if err != nil { return err } if reply.Owner != X.Dummy() { return fmt.Errorf( "Could not acquire ownership with SetSelectionOwner. "+ "GetSelectionOwner claims that '%d' is the owner, but '%d' "+ "needs to be.", reply.Owner, X.Dummy()) } // While X now acknowledges us as the selection owner, it's possible // that the window manager is misbehaving. ICCCM 2.8 calls for the previous // manager to destroy the selection owner when it's OK for us to take // over. Otherwise, listening to SubstructureRedirect on the root window // might fail if we move too quickly. timeout := time.After(3 * time.Second) if otherWmRunning { OTHER_WM_SHUTDOWN: for { select { case <-timeout: return fmt.Errorf( "Wingo failed to replace the currently running window "+ "manager (%s). Namely, Wingo was not able to detect "+ "that the current window manager had shut down.", otherWmName) default: logger.Message.Printf("Polling for event...") ev, err := X.Conn().PollForEvent() if err != nil { continue } logger.Message.Printf("Got event, error: %s -- %s", ev, err) if destNotify, ok := ev.(xproto.DestroyNotifyEvent); ok { if destNotify.Window == otherWmOwner { break OTHER_WM_SHUTDOWN } } time.Sleep(100 * time.Millisecond) } } } logger.Message.Println("Wingo has window manager ownership!") announce(X) // Listen for SelectionClear events. When we get one of these, then we // know a window manager is trying to replace us. xevent.SelectionClearFun(disown).Connect(X, X.Dummy()) return nil }
// Initialize attaches the appropriate callbacks to make mouse bindings easier. // i.e., prep the dummy window to handle mouse dragging events func Initialize(xu *xgbutil.XUtil) { xevent.MotionNotifyFun(dragStep).Connect(xu, xu.Dummy()) xevent.ButtonReleaseFun(DragEnd).Connect(xu, xu.Dummy()) }