From 7a489cb0011a34a68d3e77d0174076857cc37902 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 19 Jul 2019 14:15:48 -0400 Subject: [PATCH] Add Unix socket for communicating with aerc --- aerc.go | 10 ++++++ lib/socket.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++ widgets/aerc.go | 40 ++++++++++++++++++++++ widgets/compose.go | 7 ++++ 4 files changed, 139 insertions(+) create mode 100644 lib/socket.go diff --git a/aerc.go b/aerc.go index 40e6605..d44d3ba 100644 --- a/aerc.go +++ b/aerc.go @@ -18,6 +18,7 @@ import ( "git.sr.ht/~sircmpwn/aerc/commands/msgview" "git.sr.ht/~sircmpwn/aerc/commands/terminal" "git.sr.ht/~sircmpwn/aerc/config" + "git.sr.ht/~sircmpwn/aerc/lib" libui "git.sr.ht/~sircmpwn/aerc/lib/ui" "git.sr.ht/~sircmpwn/aerc/widgets" ) @@ -149,6 +150,15 @@ func main() { } 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 aerc.Tick() { // Continue updating our internal state diff --git a/lib/socket.go b/lib/socket.go new file mode 100644 index 0000000..c256579 --- /dev/null +++ b/lib/socket.go @@ -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) +} diff --git a/widgets/aerc.go b/widgets/aerc.go index a73caec..14cf3c4 100644 --- a/widgets/aerc.go +++ b/widgets/aerc.go @@ -1,7 +1,10 @@ package widgets import ( + "errors" "log" + "net/url" + "strings" "time" "github.com/gdamore/tcell" @@ -302,3 +305,40 @@ func (aerc *Aerc) BeginExCommand() { aerc.statusbar.Push(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 +} diff --git a/widgets/compose.go b/widgets/compose.go index f1c8014..401815c 100644 --- a/widgets/compose.go +++ b/widgets/compose.go @@ -138,6 +138,13 @@ func (c *Composer) FocusTerminal() *Composer { 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)) { c.headers.subject.OnChange(func() { fn(c.headers.subject.input.String())