diff --git a/main.go b/main.go index 1fda674..461e9cf 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "net" "os" "strings" + "time" ) var ( @@ -13,10 +14,16 @@ var ( sessionSubscription I3Session workspaces []I3Workspace outputIndices map[string]int + subscriptions []string ) func init() { outputIndices = make(map[string]int) + subscriptions = []string{ + "binding", + "shutdown", + } + workspaces = []I3Workspace{ { Num: 0, Name: "Development" }, { Num: 1, Name: "Terminal" }, @@ -34,23 +41,7 @@ func init() { func main() { var err error - // session is used for interactively communicating with i3, - // like getting workspace and output data. - if session, err = NewI3Session(); err != nil { - fmt.Printf("%s\n", err) - os.Exit(1) - } - defer session.Close() - - // sessionSubscription is used for listening to subscribed events - // from i3. - if sessionSubscription, err = NewI3Session(); err != nil { - fmt.Printf("%s\n", err) - os.Exit(1) - } - sessionSubscription.Subscribe([]string{"binding", "output"}) - defer sessionSubscription.Close() - go sessionSubscription.Loop() + go SessionLoop() // Socket server reading commands and acting upon them var listener net.Listener @@ -60,13 +51,6 @@ func main() { os.Exit(1) } - // Find outputs and assign an index to them - err = session.UpdateOutputIndices() - if err != nil { - fmt.Printf("Output error: %s\n", err) - os.Exit(1) - } - // Listen for external commands var conn net.Conn var n int @@ -82,13 +66,61 @@ func main() { } cmd := strings.TrimSpace(string(buf[:n])) - if cmd == "fix workspaces" { + switch(cmd) { + case "fix workspaces": err = session.FixWorkspaces() if err != nil { fmt.Printf("Fix workspaces: %s\n", err) } + + case "outputs": + res, err := session.Socket.Request(GET_OUTPUTS, "") + if err != nil { + fmt.Printf("test error: %s\n", err) + } + fmt.Printf("test: %s\n", res) } conn.Close() } } + +func SessionLoop() { + var err error + for { + fmt.Printf("-------- SessionLoop --------\n") + + // session is used for interactively communicating with i3, + // like getting workspace and output data. + if err = session.Open(); err != nil { + fmt.Printf("%s\n", err) + continue + } + + // sessionSubscription is used for listening to subscribed + // events from i3. + if err = sessionSubscription.Open(); err != nil { + fmt.Printf("%s\n", err) + continue + } + + // i3-session-manager reacts to keybindings and i3 restarts. + sessionSubscription.Subscribe(subscriptions) + + // Find outputs and assign an index to them + err = session.UpdateOutputIndices() + if err != nil { + fmt.Printf("Output error: %s\n", err) + os.Exit(1) + } + + + /* Loop will return on socket errors, for example when an i3 + * restart occurs. The socket will not be immediately available + * then. Some connection errors can thus occur until i3 has + * initialized the socket again. */ + err = sessionSubscription.Loop() + fmt.Printf("Loop error: %s\n", err) + time.Sleep(time.Millisecond*100) + } +} diff --git a/session.go b/session.go index ee7388b..a831643 100644 --- a/session.go +++ b/session.go @@ -4,20 +4,18 @@ import ( // Standard "encoding/json" "fmt" - "os" "os/exec" "regexp" "strconv" "strings" ) -// NewI3Session finds the i3 socket path and connects to it. -func NewI3Session() (session I3Session, err error) { +func (sess *I3Session) Open() (err error) { // Find path to i3 socket i3SocketPathCmd := exec.Command("i3", "--get-socketpath") i3SocketPathBytes, _ := i3SocketPathCmd.CombinedOutput() i3SocketPath := strings.TrimSpace(string(i3SocketPathBytes)) - session.Socket, err = NewI3Socket(i3SocketPath) + sess.Socket, err = NewI3Socket(i3SocketPath) return } @@ -112,98 +110,6 @@ func (sess I3Session) Config() (config string, err error) { return } -func (sess I3Session) Loop() { - var msg []byte - var err error - var cmdBinding I3EventBinding - - cmdOutput := regexp.MustCompile(`(?i)^\s*nop\s*output\s*(.*?)\s*$`) - cmdWS := regexp.MustCompile(`(?i)^\s*nop\s*workspace\s*(\d+)\s*$`) - cmdSession := regexp.MustCompile(`(?i)^\s*nop\s*manage\s+session`) - cmdMarkTag := regexp.MustCompile(`(?i)^\s*nop\s*mark\s+tag`) - cmdMarkMove := regexp.MustCompile(`(?i)^\s*nop\s*mark\s+move`) - cmdMarkClear := regexp.MustCompile(`(?i)^\s*nop\s*mark\s+clear`) - - for { - msg, err = I3ReadMessage(sess.Socket) - if err != nil { - fmt.Printf("Loop error: %s\n", err) - os.Exit(1) - } - - cmdBinding = I3EventBinding{} - err = json.Unmarshal(msg, &cmdBinding) - if err != nil { - fmt.Printf( - "Loop JSON parse: %s\n\nMessage:\n(%s)\n", - err, - msg, - ) - } - binding := cmdBinding.Binding - - // Switch output - m := cmdOutput.FindAllStringSubmatch(binding.Command, 1) - if len(m) == 1 && len(m[0]) == 2 { - err = sess.SwitchOutput(m[0][1]) - if err != nil { - fmt.Printf("%s\n", err) - } - continue - } - - // Switch output - m = cmdWS.FindAllStringSubmatch(binding.Command, 1) - if len(m) == 1 && len(m[0]) == 2 { - i, _ := strconv.Atoi(m[0][1]) - err = sess.SwitchWorkspace(i) - if err != nil { - fmt.Printf("%s\n", err) - } - continue - } - - // Manage session - if cmdSession.Match([]byte(binding.Command)) { - err = sess.ManageSession() - if err != nil { - fmt.Printf("Manage session: %s\n", err) - } - continue - } - - // Mark current window with move tag - if cmdMarkTag.Match([]byte(binding.Command)) { - err = sess.MarkTag() - if err != nil { - fmt.Printf("Mark tag: %s\n", err) - } - continue - } - - // Remove all move tags - if cmdMarkClear.Match([]byte(binding.Command)) { - err = sess.MarkClear() - if err != nil { - fmt.Printf("Mark clear: %s\n", err) - } - continue - } - - // Move all windows with move tag - if cmdMarkMove.Match([]byte(binding.Command)) { - err = sess.MarkMove() - if err != nil { - fmt.Printf("Mark move: %s\n", err) - } - continue - } - - fmt.Printf("%s\n", msg) - - } -} - // SwitchWorkspace switches to the given workspace index on the same output // that has the workspace that is currently focused. func (sess I3Session) SwitchWorkspace(workspaceIndex int) error { @@ -270,7 +176,7 @@ func (sess I3Session) SwitchOutput(output string) error { } // ManageSession runs dmenu and asks for reload, restart or exit. -func (sess I3Session) ManageSession() error { +func (sess *I3Session) ManageSession() error { cmd := exec.Command( "sh", "-c", "echo \"reload\nrestart\nexit\" | dmenu -l 3 -i", @@ -283,11 +189,14 @@ func (sess I3Session) ManageSession() error { switch(choice) { case "reload", "restart", "exit": - res, err := sess.Socket.Request(RUN_COMMAND, choice) + _, err = sess.Socket.Request(RUN_COMMAND, choice) if err != nil { return err } - fmt.Printf("Management %s: %s\n", choice, res) + + if choice == "restart" { + return fmt.Errorf("restarted") + } default: return fmt.Errorf("Unknown management command") } @@ -439,4 +348,100 @@ func (sess I3Session) FixWorkspaces() error { return nil } -// vim: foldmethod=marker +// Loop reads the subscription socket and reacts to the implemented messages. +func (sess *I3Session) Loop() error { + var msg []byte + var err error + var cmdBinding I3EventBinding + + cmdOutput := regexp.MustCompile(`(?i)^\s*nop\s*output\s*(.*?)\s*$`) + cmdWS := regexp.MustCompile(`(?i)^\s*nop\s*workspace\s*(\d+)\s*$`) + cmdSession := regexp.MustCompile(`(?i)^\s*nop\s*manage\s+session`) + cmdMarkTag := regexp.MustCompile(`(?i)^\s*nop\s*mark\s+tag`) + cmdMarkMove := regexp.MustCompile(`(?i)^\s*nop\s*mark\s+move`) + cmdMarkClear := regexp.MustCompile(`(?i)^\s*nop\s*mark\s+clear`) + + for { + msg, err = I3ReadMessage(sess.Socket) + if err != nil { + return err + } + + cmdBinding = I3EventBinding{} + err = json.Unmarshal(msg, &cmdBinding) + if err != nil { + fmt.Printf( + "\nLoop JSON parse: %s\nMessage:\n(%s)\n\n", + err, + msg, + ) + } + binding := cmdBinding.Binding + + // Process is restarted + if cmdBinding.Change == "restart" { + return fmt.Errorf("restarted") + } + + // Switch output + m := cmdOutput.FindAllStringSubmatch(binding.Command, 1) + if len(m) == 1 && len(m[0]) == 2 { + err = sess.SwitchOutput(m[0][1]) + if err != nil { + fmt.Printf("%s\n", err) + } + continue + } + + // Switch output + m = cmdWS.FindAllStringSubmatch(binding.Command, 1) + if len(m) == 1 && len(m[0]) == 2 { + i, _ := strconv.Atoi(m[0][1]) + err = sess.SwitchWorkspace(i) + if err != nil { + fmt.Printf("%s\n", err) + } + continue + } + + // Manage session + if cmdSession.Match([]byte(binding.Command)) { + err = sess.ManageSession() + if err != nil { + return err + } + continue + } + + // Mark current window with move tag + if cmdMarkTag.Match([]byte(binding.Command)) { + err = sess.MarkTag() + if err != nil { + fmt.Printf("Mark tag: %s\n", err) + } + continue + } + + // Remove all move tags + if cmdMarkClear.Match([]byte(binding.Command)) { + err = sess.MarkClear() + if err != nil { + fmt.Printf("Mark clear: %s\n", err) + } + continue + } + + // Move all windows with move tag + if cmdMarkMove.Match([]byte(binding.Command)) { + err = sess.MarkMove() + if err != nil { + fmt.Printf("Mark move: %s\n", err) + } + continue + } + + //fmt.Printf("%s\n", msg) + } +} + +// vim: foldmethod=syntax foldnestmax=1 diff --git a/socket.go b/socket.go index cd4c969..01b3dea 100644 --- a/socket.go +++ b/socket.go @@ -26,13 +26,18 @@ func NewI3Socket(filename string) (I3Socket, error) { var i3Socket I3Socket var err error - i3Socket.conn, err = net.Dial("unix", filename) + i3Socket.filename = filename + err = i3Socket.Open() if err != nil { return i3Socket, err } return i3Socket, nil } +func (sock *I3Socket) Open() (err error) { + sock.conn, err = net.Dial("unix", sock.filename) + return +} func (sock I3Socket) Close() error { return sock.conn.Close() diff --git a/types.go b/types.go index 2beeafc..cc1520f 100644 --- a/types.go +++ b/types.go @@ -7,6 +7,7 @@ import ( type I3Session struct { Socket I3Socket + filename string } type I3Socket struct {