about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorDrew DeVault <sir@cmpwn.com>2019-07-19 14:15:48 -0400
committerDrew DeVault <sir@cmpwn.com>2019-07-19 14:15:48 -0400
commit7a489cb0011a34a68d3e77d0174076857cc37902 (patch)
treed21df8ea9b872ccc08cff117f203ad0d80b94a26
parentb3a66866b95d77f202f571efedd2f7ec309aacf5 (diff)
downloadaerc-7a489cb0011a34a68d3e77d0174076857cc37902.tar.gz
Add Unix socket for communicating with aerc
-rw-r--r--aerc.go10
-rw-r--r--lib/socket.go82
-rw-r--r--widgets/aerc.go40
-rw-r--r--widgets/compose.go7
4 files changed, 139 insertions, 0 deletions
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())