func (c *Client) handleClientMessage(name string, data []uint32) { switch name { case "WM_CHANGE_STATE": if data[0] == icccm.StateIconic && !c.iconified { c.IconifyToggle() } case "_NET_ACTIVE_WINDOW": c.Focus() c.Raise() case "_NET_CLOSE_WINDOW": c.Close() case "_NET_MOVERESIZE_WINDOW": // The data[0] element contains bit-packed information. See // EWMH _NET_MOVERESIZE_WINDOW for the deets. gravity := int(data[0] & 0xff) xflags := int((data[0] >> 8) & 0xf) x, y, w, h := frame.ClientToFrame(c.frame, gravity, int(data[1]), int(data[2]), int(data[3]), int(data[4])) c.LayoutMROpt(xflags, x, y, w, h) case "_NET_RESTACK_WINDOW": // We basically treat this as a request to stack the window. // We ignore the sibling. Maybe someday we can support that, but eh... c.Raise() case "_NET_WM_DESKTOP": if data[0] == 0xFFFFFFFF { c.stick() return } if wrk := wm.Heads.Workspaces.Get(int(data[0])); wrk != nil { wrk.Add(c) } else { logger.Warning.Printf( "_NET_WM_DESKTOP ClientMessage: No workspace indexed at '%d' "+ "exists.", data[0]) } case "_NET_WM_STATE": prop1, _ := xprop.AtomName(wm.X, xproto.Atom(data[1])) prop2, _ := xprop.AtomName(wm.X, xproto.Atom(data[2])) switch data[0] { case 0: c.updateStates("remove", prop1, prop2) case 1: c.updateStates("add", prop1, prop2) case 2: c.updateStates("toggle", prop1, prop2) default: logger.Warning.Printf( "_NET_WM_STATE: Unknown action '%d'.", data[0]) } default: logger.Warning.Printf("Unknown ClientMessage for '%s': %s.", c, name) } }
func handleClientMessages(X *xgbutil.XUtil, ev xevent.ClientMessageEvent) { name, err := xprop.AtomName(X, ev.Type) if err != nil { logger.Warning.Printf("Could not get atom name for '%s': %s", ev, err) return } switch name { case "_NET_NUMBER_OF_DESKTOPS": logger.Warning.Printf("Wingo does not support adding/removing " + "desktops using the _NET_NUMBER_OF_DESKTOPS property. Please use " + "the Wingo commands 'AddWorkspace' and 'RemoveWorkspace' to add " + "or remove workspaces.") case "_NET_DESKTOP_GEOMETRY": logger.Warning.Printf("Wingo does not support the " + "_NET_DESKTOP_GEOMETRY property. Namely, more than one workspace " + "can be visible at a time, so different workspaces can have " + "different geometries.") case "_NET_DESKTOP_VIEWPORT": logger.Warning.Printf("Wingo does not use viewports, and therefore " + "does not support the _NET_DESKTOP_VIEWPORT property.") case "_NET_CURRENT_DESKTOP": index := int(ev.Data.Data32[0]) if wrk := wm.Heads.Workspaces.Get(index); wrk != nil { wm.SetWorkspace(wrk, false) wm.FocusFallback() } else { logger.Warning.Printf("Desktop index %d is not in the range "+ "[0, %d).", index, len(wm.Heads.Workspaces.Wrks)) } default: logger.Warning.Printf("Unknown root client message: %s", name) } }
// commandHandler responds to client message events that issue commands. func commandHandler(X *xgbutil.XUtil, cm xevent.ClientMessageEvent) { typeName, err := xprop.AtomName(X, cm.Type) if err != nil { logger.Warning.Printf( "Could not get type of ClientMessage event: %s", cm) return } if typeName == "_WINGO_CMD" { cmd, err := cmdusage.CmdGet(X) if err != nil { logger.Warning.Printf("Could not get _WINGO_CMD value: %s", err) return } // Blank out the command cmdusage.CmdSet(X, "") // Parse the command cmdName, args := commandParse(cmd) cmdFun := commandFun("", cmdName, args...) if cmdFun != nil { cmdFun() cmdusage.StatusSet(X, true) } else { cmdusage.StatusSet(X, false) } } }
// 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 }
func (c *Client) cbClientMessage() xevent.ClientMessageFun { f := func(X *xgbutil.XUtil, ev xevent.ClientMessageEvent) { name, err := xprop.AtomName(wm.X, ev.Type) if err != nil { logger.Warning.Printf("Could not get property atom name for "+ "ClientMessage event on '%s': %s.", c, err) return } logger.Lots.Printf( "Handling ClientMessage '%s' on client '%s'.", name, c) c.handleClientMessage(name, ev.Data.Data32) } return xevent.ClientMessageFun(f) }
func (c *Client) cbPropertyNotify() xevent.PropertyNotifyFun { f := func(X *xgbutil.XUtil, ev xevent.PropertyNotifyEvent) { name, err := xprop.AtomName(wm.X, ev.Atom) if err != nil { logger.Warning.Printf("Could not get property atom name for '%s' "+ "because: %s.", ev, err) return } logger.Lots.Printf("Updating property %s with state %v on window %s", name, ev.State, c) c.handleProperty(name) } return xevent.PropertyNotifyFun(f) }
func (c *client) cbPropertyNotify() xevent.PropertyNotifyFun { // helper function to log property vals showVals := func(o, n interface{}) { logger.Lots.Printf("\tOld value: '%s', new value: '%s'", o, n) } f := func(X *xgbutil.XUtil, ev xevent.PropertyNotifyEvent) { name, err := xprop.AtomName(c.X, ev.Atom) if err != nil { logger.Warning.Printf("Could not get property atom name for '%s' "+ "because: %s.", ev, err) } logger.Lots.Printf("Updating property %s with state %v on window %s", name, ev.State, c) switch name { case "_NET_WM_VISIBLE_NAME": fallthrough case "_NET_WM_NAME": fallthrough case "WM_NAME": c.refreshName() case "_NET_WM_ICON": case "WM_HINTS": if hints, err := icccm.WmHintsGet(X, c.Id()); err == nil { c.hints = hints } case "WM_NORMAL_HINTS": if nhints, err := icccm.WmNormalHintsGet(X, c.Id()); err == nil { c.nhints = nhints } case "WM_TRANSIENT_FOR": if trans, err := icccm.WmTransientForGet(X, c.Id()); err == nil { if transCli := wingo.findManagedClient(trans); transCli != nil { c.transientFor = transCli } } case "_NET_WM_USER_TIME": if newTime, err := ewmh.WmUserTimeGet(X, c.Id()); err == nil { showVals(c.time, newTime) c.time = xproto.Timestamp(newTime) } case "_NET_WM_STRUT_PARTIAL": } } return xevent.PropertyNotifyFun(f) }
func main() { X, err := xgbutil.NewConn() if err != nil { log.Fatal(err) } // Start generating other source events. otherChan := otherSource() // Start generating X events (by sending client messages to root window). go xSource(X) // Listen to those X events. xwindow.New(X, X.RootWin()).Listen(xproto.EventMaskSubstructureNotify) // Respond to those X events. xevent.ClientMessageFun( func(X *xgbutil.XUtil, ev xevent.ClientMessageEvent) { atmName, err := xprop.AtomName(X, ev.Type) if err != nil { log.Fatal(err) } fmt.Printf("ClientMessage: %d. %s\n", ev.Data.Data32[0], atmName) }).Connect(X, X.RootWin()) // Instead of using the usual xevent.Main, we use xevent.MainPing. // It runs the main event loop inside a goroutine and returns ping // channels, which are sent benign values right before an event is // dequeued and right after that event has finished running all callbacks // associated with it, respectively. pingBefore, pingAfter, pingQuit := xevent.MainPing(X) for { select { case <-pingBefore: // Wait for the event to finish processing. <-pingAfter case otherVal := <-otherChan: fmt.Printf("Processing other event: %d\n", otherVal) case <-pingQuit: fmt.Printf("xevent loop has quit") return } } }
func (w *Window) handleEvents() { var noX int32 = 1<<31 - 1 noX++ var lastX, lastY int32 = noX, 0 var button wde.Button downKeys := map[string]bool{} for { e, err := w.conn.WaitForEvent() // if err != nil { // fmt.Println("err:", err) // } if err == io.EOF { break } xgbutil.BeSafe(&err) switch e := e.(type) { case xgb.ButtonPressEvent: button = button | buttonForDetail(e.Detail) var bpe wde.MouseDownEvent bpe.Which = buttonForDetail(e.Detail) bpe.Where.X = int(e.EventX) bpe.Where.Y = int(e.EventY) lastX = int32(e.EventX) lastY = int32(e.EventY) w.events <- bpe case xgb.ButtonReleaseEvent: button = button & ^buttonForDetail(e.Detail) var bue wde.MouseUpEvent bue.Which = buttonForDetail(e.Detail) bue.Where.X = int(e.EventX) bue.Where.Y = int(e.EventY) lastX = int32(e.EventX) lastY = int32(e.EventY) w.events <- bue case xgb.LeaveNotifyEvent: var wee wde.MouseExitedEvent wee.Where.X = int(e.EventX) wee.Where.Y = int(e.EventY) if lastX != noX { wee.From.X = int(lastX) wee.From.Y = int(lastY) } else { wee.From.X = wee.Where.X wee.From.Y = wee.Where.Y } lastX = int32(e.EventX) lastY = int32(e.EventY) w.events <- wee case xgb.EnterNotifyEvent: var wee wde.MouseEnteredEvent wee.Where.X = int(e.EventX) wee.Where.Y = int(e.EventY) if lastX != noX { wee.From.X = int(lastX) wee.From.Y = int(lastY) } else { wee.From.X = wee.Where.X wee.From.Y = wee.Where.Y } lastX = int32(e.EventX) lastY = int32(e.EventY) w.events <- wee case xgb.MotionNotifyEvent: var mme wde.MouseMovedEvent mme.Where.X = int(e.EventX) mme.Where.Y = int(e.EventY) if lastX != noX { mme.From.X = int(lastX) mme.From.Y = int(lastY) } else { mme.From.X = mme.Where.X mme.From.Y = mme.Where.Y } lastX = int32(e.EventX) lastY = int32(e.EventY) if button == 0 { w.events <- mme } else { var mde wde.MouseDraggedEvent mde.MouseMovedEvent = mme mde.Which = button w.events <- mde } case xgb.KeyPressEvent: var ke wde.KeyEvent code := keybind.LookupString(w.xu, e.State, e.Detail) ke.Key = keyForCode(code) w.events <- wde.KeyDownEvent(ke) downKeys[ke.Key] = true kpe := wde.KeyTypedEvent{ KeyEvent: ke, Glyph: letterForCode(code), Chord: wde.ConstructChord(downKeys), } w.events <- kpe case xgb.KeyReleaseEvent: var ke wde.KeyUpEvent ke.Key = keyForCode(keybind.LookupString(w.xu, e.State, e.Detail)) delete(downKeys, ke.Key) w.events <- ke case xgb.ConfigureNotifyEvent: var re wde.ResizeEvent re.Width = int(e.Width) re.Height = int(e.Height) if re.Width != w.width || re.Height != w.height { w.width, w.height = re.Width, re.Height w.buffer = image.NewRGBA(image.Rectangle{image.Point{0, 0}, image.Point{re.Width, re.Height}}) w.events <- re } case xgb.ClientMessageEvent: if e.Format != 32 { break } name, err := xprop.AtomName(w.xu, e.Type) if err != nil { // TODO: log break } if name != "WM_PROTOCOLS" { break } name2, err := xprop.AtomName(w.xu, xgb.Id(e.Data.Data32[0])) if err != nil { // TODO: log break } if name2 == "WM_DELETE_WINDOW" { w.events <- wde.CloseEvent{} } case xgb.DestroyNotifyEvent: case xgb.ReparentNotifyEvent: case xgb.MapNotifyEvent: case xgb.UnmapNotifyEvent: case xgb.PropertyNotifyEvent: default: fmt.Printf("unhandled event: type %T\n%+v\n", e, e) } } close(w.events) }
func main() { X, err := xgbutil.Dial("") if err != nil { logger.Error.Println(err) logger.Error.Println("Error connecting to X, quitting...") return } defer X.Conn().Close() // Get command from arguments flag.Parse() if flag.NArg() < 1 { fmt.Fprintln(os.Stderr, "Usage: wingo-cmd CommandName [CommandArgs]") return } commandPieces := flag.Args() cmdName := commandPieces[0] cmdFull := strings.Join(commandPieces, " ") // make sure we start with failure cmdusage.StatusSet(X, false) success := false // Set the command before sending request to run command. err = cmdusage.CmdSet(X, cmdFull) if err != nil { logger.Error.Printf("Could not set command: %s", err) return } // Issue the command! ewmh.ClientEvent(X, X.RootWin(), "_WINGO_CMD") // Now let's set up a handler to detect when the status changes xevent.PropertyNotifyFun( func(X *xgbutil.XUtil, ev xevent.PropertyNotifyEvent) { name, err := xprop.AtomName(X, ev.Atom) if err != nil { logger.Warning.Println( "Could not get property atom name for", ev.Atom) return } if name == "_WINGO_CMD_STATUS" { success = cmdusage.StatusGet(X) if success { os.Exit(0) } else { logger.Warning.Printf("Error running '%s'", cmdFull) cmdusage.ShowUsage(cmdName) os.Exit(1) } } }).Connect(X, X.RootWin()) // Listen to Root property change events xwindow.Listen(X, X.RootWin(), xproto.EventMaskPropertyChange) go xevent.Main(X) time.Sleep(time.Second * 5) logger.Error.Println( "Timed out while trying to issue command to Wingo. " + "Are you sure Wingo is running?") os.Exit(1) }