Better loop/socket management.

This commit is contained in:
Magnus Åhall 2021-12-23 09:06:43 +01:00
parent c3a504fcbf
commit d2899385a8
4 changed files with 169 additions and 126 deletions

82
main.go
View File

@ -6,6 +6,7 @@ import (
"net" "net"
"os" "os"
"strings" "strings"
"time"
) )
var ( var (
@ -13,10 +14,16 @@ var (
sessionSubscription I3Session sessionSubscription I3Session
workspaces []I3Workspace workspaces []I3Workspace
outputIndices map[string]int outputIndices map[string]int
subscriptions []string
) )
func init() { func init() {
outputIndices = make(map[string]int) outputIndices = make(map[string]int)
subscriptions = []string{
"binding",
"shutdown",
}
workspaces = []I3Workspace{ workspaces = []I3Workspace{
{ Num: 0, Name: "Development" }, { Num: 0, Name: "Development" },
{ Num: 1, Name: "Terminal" }, { Num: 1, Name: "Terminal" },
@ -34,23 +41,7 @@ func init() {
func main() { func main() {
var err error var err error
// session is used for interactively communicating with i3, go SessionLoop()
// 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()
// Socket server reading commands and acting upon them // Socket server reading commands and acting upon them
var listener net.Listener var listener net.Listener
@ -60,13 +51,6 @@ func main() {
os.Exit(1) 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 // Listen for external commands
var conn net.Conn var conn net.Conn
var n int var n int
@ -82,13 +66,61 @@ func main() {
} }
cmd := strings.TrimSpace(string(buf[:n])) cmd := strings.TrimSpace(string(buf[:n]))
if cmd == "fix workspaces" { switch(cmd) {
case "fix workspaces":
err = session.FixWorkspaces() err = session.FixWorkspaces()
if err != nil { if err != nil {
fmt.Printf("Fix workspaces: %s\n", err) 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() 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)
}
}

View File

@ -4,20 +4,18 @@ import (
// Standard // Standard
"encoding/json" "encoding/json"
"fmt" "fmt"
"os"
"os/exec" "os/exec"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
) )
// NewI3Session finds the i3 socket path and connects to it. func (sess *I3Session) Open() (err error) {
func NewI3Session() (session I3Session, err error) {
// Find path to i3 socket // Find path to i3 socket
i3SocketPathCmd := exec.Command("i3", "--get-socketpath") i3SocketPathCmd := exec.Command("i3", "--get-socketpath")
i3SocketPathBytes, _ := i3SocketPathCmd.CombinedOutput() i3SocketPathBytes, _ := i3SocketPathCmd.CombinedOutput()
i3SocketPath := strings.TrimSpace(string(i3SocketPathBytes)) i3SocketPath := strings.TrimSpace(string(i3SocketPathBytes))
session.Socket, err = NewI3Socket(i3SocketPath) sess.Socket, err = NewI3Socket(i3SocketPath)
return return
} }
@ -112,98 +110,6 @@ func (sess I3Session) Config() (config string, err error) {
return 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 // SwitchWorkspace switches to the given workspace index on the same output
// that has the workspace that is currently focused. // that has the workspace that is currently focused.
func (sess I3Session) SwitchWorkspace(workspaceIndex int) error { 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. // ManageSession runs dmenu and asks for reload, restart or exit.
func (sess I3Session) ManageSession() error { func (sess *I3Session) ManageSession() error {
cmd := exec.Command( cmd := exec.Command(
"sh", "-c", "sh", "-c",
"echo \"reload\nrestart\nexit\" | dmenu -l 3 -i", "echo \"reload\nrestart\nexit\" | dmenu -l 3 -i",
@ -283,11 +189,14 @@ func (sess I3Session) ManageSession() error {
switch(choice) { switch(choice) {
case "reload", "restart", "exit": case "reload", "restart", "exit":
res, err := sess.Socket.Request(RUN_COMMAND, choice) _, err = sess.Socket.Request(RUN_COMMAND, choice)
if err != nil { if err != nil {
return err return err
} }
fmt.Printf("Management %s: %s\n", choice, res)
if choice == "restart" {
return fmt.Errorf("restarted")
}
default: default:
return fmt.Errorf("Unknown management command") return fmt.Errorf("Unknown management command")
} }
@ -439,4 +348,100 @@ func (sess I3Session) FixWorkspaces() error {
return nil 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

View File

@ -26,13 +26,18 @@ func NewI3Socket(filename string) (I3Socket, error) {
var i3Socket I3Socket var i3Socket I3Socket
var err error var err error
i3Socket.conn, err = net.Dial("unix", filename) i3Socket.filename = filename
err = i3Socket.Open()
if err != nil { if err != nil {
return i3Socket, err return i3Socket, err
} }
return i3Socket, nil return i3Socket, nil
} }
func (sock *I3Socket) Open() (err error) {
sock.conn, err = net.Dial("unix", sock.filename)
return
}
func (sock I3Socket) Close() error { func (sock I3Socket) Close() error {
return sock.conn.Close() return sock.conn.Close()

View File

@ -7,6 +7,7 @@ import (
type I3Session struct { type I3Session struct {
Socket I3Socket Socket I3Socket
filename string
} }
type I3Socket struct { type I3Socket struct {