package sway import ( "context" "encoding/binary" "encoding/json" "fmt" "io" "net" "git.milar.in/milarin/slices" ) // Subscribe subscribes to the given event types // and returns a channel in which all events get written into. // A new socket connection will be established // so other requests can still be used during a subscription. // The subscription connection will be closed as soon as ctx is closed. func (c *Client) Subscribe(ctx context.Context, events ...EventType) (<-chan Event, error) { conn, err := net.Dial("unix", c.socket) if err != nil { return nil, err } if _, err := fmt.Fprint(conn, "i3-ipc"); err != nil { return nil, err } payload, err := json.Marshal(slices.Map(events, EventType.String)) if err != nil { return nil, err } if err := binary.Write(conn, binary.LittleEndian, uint32(len(payload))); err != nil { return nil, err } if err := binary.Write(conn, binary.LittleEndian, uint32(2)); err != nil { return nil, err } if _, err := conn.Write(payload); err != nil { return nil, err } if _, err := conn.Read(make([]byte, 6)); err != nil { return nil, err } var length uint32 if err := binary.Read(conn, binary.LittleEndian, &length); err != nil { return nil, err } var messageType uint32 if err := binary.Read(conn, binary.LittleEndian, &messageType); err != nil { return nil, err } result := commandResult{} if err := json.NewDecoder(io.LimitReader(conn, int64(length))).Decode(&result); err != nil { return nil, err } if result.HasError() { return nil, result.GetError() } return parseEvents(ctx, conn) } func parseEvents(ctx context.Context, conn net.Conn) (<-chan Event, error) { out := make(chan Event, 10) go func() { defer conn.Close() <-ctx.Done() }() go func() { defer close(out) for ctx.Err() == nil { if _, err := conn.Read(make([]byte, 6)); err != nil { return } var length uint32 if err := binary.Read(conn, binary.LittleEndian, &length); err != nil { return } var messageType EventType if err := binary.Read(conn, binary.LittleEndian, &messageType); err != nil { return } event, err := parseEvent(conn, length, messageType) if err != nil { return } out <- event } }() return out, nil } func parseEvent(conn net.Conn, length uint32, messageType EventType) (Event, error) { var event Event switch messageType { case EventTypeWorkspace: event = &WorkspaceEvent{} case EventTypeMode: event = &ModeEvent{} case EventTypeWindow: event = &WindowEvent{} case EventTypeBinding: event = &BindingEvent{} case EventTypeShutdown: event = &ShutdownEvent{} case EventTypeTick: event = &TickEvent{} case EventTypeBarStateUpdate: event = &BarStateUpdateEvent{} case EventTypeInput: event = &InputEvent{} default: return nil, fmt.Errorf("invalid event type: %x", messageType) } if err := json.NewDecoder(io.LimitReader(conn, int64(length))).Decode(event); err != nil { return nil, err } return event, nil }