Better loop/socket management.
This commit is contained in:
parent
c3a504fcbf
commit
d2899385a8
82
main.go
82
main.go
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
205
session.go
205
session.go
@ -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
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user