Example #1
0
// 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
}
Example #2
0
// 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()))
}
Example #3
0
// 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)
	}
}
Example #4
0
// DummyGrab grabs the keyboard and sends all key events to the dummy window.
func DummyGrab(xu *xgbutil.XUtil) error {
	return SmartGrab(xu, xu.Dummy())
}
Example #5
0
// 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())
}