Add Unix socket for communicating with aerc

This commit is contained in:
Drew DeVault 2019-07-19 14:15:48 -04:00
parent b3a66866b9
commit 7a489cb001
4 changed files with 139 additions and 0 deletions

10
aerc.go
View file

@ -18,6 +18,7 @@ import (
"git.sr.ht/~sircmpwn/aerc/commands/msgview" "git.sr.ht/~sircmpwn/aerc/commands/msgview"
"git.sr.ht/~sircmpwn/aerc/commands/terminal" "git.sr.ht/~sircmpwn/aerc/commands/terminal"
"git.sr.ht/~sircmpwn/aerc/config" "git.sr.ht/~sircmpwn/aerc/config"
"git.sr.ht/~sircmpwn/aerc/lib"
libui "git.sr.ht/~sircmpwn/aerc/lib/ui" libui "git.sr.ht/~sircmpwn/aerc/lib/ui"
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
@ -149,6 +150,15 @@ func main() {
} }
defer ui.Close() defer ui.Close()
logger.Println("Starting Unix server")
as, err := lib.StartServer(logger)
if err != nil {
logger.Printf("Failed to start Unix server: %v (non-fatal)", err)
} else {
defer as.Close()
as.OnMailto = aerc.Mailto
}
for !ui.ShouldExit() { for !ui.ShouldExit() {
for aerc.Tick() { for aerc.Tick() {
// Continue updating our internal state // Continue updating our internal state

82
lib/socket.go Normal file
View file

@ -0,0 +1,82 @@
package lib
import (
"bufio"
"fmt"
"log"
"net"
"net/url"
"path"
"strings"
"sync/atomic"
"time"
"github.com/kyoh86/xdg"
)
type AercServer struct {
logger *log.Logger
listener net.Listener
OnMailto func(addr *url.URL) error
}
func StartServer(logger *log.Logger) (*AercServer, error) {
sockpath := path.Join(xdg.RuntimeDir(), "aerc.sock")
l, err := net.Listen("unix", sockpath)
if err != nil {
return nil, err
}
as := &AercServer{
logger: logger,
listener: l,
}
// TODO: stash clients and close them on exit... bleh racey
go func() {
for {
conn, err := l.Accept()
if err != nil {
// TODO: Something more useful, in some cases, on wednesdays,
// after 2 PM, I guess?
as.logger.Println("Closing Unix server: %v", err)
return
}
go as.handleClient(conn)
}
}()
return as, nil
}
func (as *AercServer) Close() {
as.listener.Close()
}
var lastId int64 = 0 // access via atomic
func (as *AercServer) handleClient(conn net.Conn) {
clientId := atomic.AddInt64(&lastId, 1)
as.logger.Printf("Accepted Unix connection %d", clientId)
scanner := bufio.NewScanner(conn)
conn.SetDeadline(time.Now().Add(1 * time.Minute))
for scanner.Scan() {
conn.SetDeadline(time.Now().Add(1 * time.Minute))
msg := scanner.Text()
if !strings.ContainsRune(msg, ':') {
conn.Write([]byte("error: invalid command\n"))
}
as.logger.Printf("unix:%d: got message %s", clientId, msg)
prefix := msg[:strings.IndexRune(msg, ':')]
switch prefix {
case "mailto":
mailto, err := url.Parse(msg)
if err != nil {
conn.Write([]byte(fmt.Sprintf("error: %v\n", err)))
break
}
if as.OnMailto != nil {
err = as.OnMailto(mailto)
}
conn.Write([]byte(fmt.Sprintf("result: %v\n", err)))
}
}
as.logger.Printf("Closed Unix connection %d", clientId)
}

View file

@ -1,7 +1,10 @@
package widgets package widgets
import ( import (
"errors"
"log" "log"
"net/url"
"strings"
"time" "time"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
@ -302,3 +305,40 @@ func (aerc *Aerc) BeginExCommand() {
aerc.statusbar.Push(exline) aerc.statusbar.Push(exline)
aerc.focus(exline) aerc.focus(exline)
} }
func (aerc *Aerc) Mailto(addr *url.URL) error {
acct := aerc.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
}
defaults := make(map[string]string)
defaults["To"] = addr.Opaque
headerMap := map[string]string{
"cc": "Cc",
"in-reply-to": "In-Reply-To",
"subject": "Subject",
}
for key, vals := range addr.Query() {
if header, ok := headerMap[strings.ToLower(key)]; ok {
defaults[header] = strings.Join(vals, ",")
}
}
composer := NewComposer(aerc.Config(),
acct.AccountConfig(), acct.Worker()).Defaults(defaults)
composer.FocusSubject()
title := "New email"
if subj, ok := defaults["Subject"]; ok {
title = subj
composer.FocusTerminal()
}
tab := aerc.NewTab(composer, title)
composer.OnSubjectChange(func(subject string) {
if subject == "" {
tab.Name = "New email"
} else {
tab.Name = subject
}
tab.Content.Invalidate()
})
return nil
}

View file

@ -138,6 +138,13 @@ func (c *Composer) FocusTerminal() *Composer {
return c return c
} }
func (c *Composer) FocusSubject() *Composer {
c.focusable[c.focused].Focus(false)
c.focused = 2
c.focusable[c.focused].Focus(true)
return c
}
func (c *Composer) OnSubjectChange(fn func(subject string)) { func (c *Composer) OnSubjectChange(fn func(subject string)) {
c.headers.subject.OnChange(func() { c.headers.subject.OnChange(func() {
fn(c.headers.subject.input.String()) fn(c.headers.subject.input.String())