2018-02-28 03:17:26 +01:00
|
|
|
package widgets
|
|
|
|
|
|
|
|
import (
|
2019-07-19 20:15:48 +02:00
|
|
|
"errors"
|
2020-03-03 22:20:07 +01:00
|
|
|
"fmt"
|
2019-08-07 08:21:15 +02:00
|
|
|
"io"
|
2019-07-19 20:15:48 +02:00
|
|
|
"net/url"
|
2022-08-08 22:04:04 +02:00
|
|
|
"sort"
|
2019-07-19 20:15:48 +02:00
|
|
|
"strings"
|
2019-03-17 21:19:15 +01:00
|
|
|
"time"
|
2018-02-28 03:17:26 +01:00
|
|
|
|
2021-12-30 10:25:07 +01:00
|
|
|
"github.com/ProtonMail/go-crypto/openpgp"
|
2020-11-10 20:27:30 +01:00
|
|
|
"github.com/emersion/go-message/mail"
|
2020-11-30 23:07:03 +01:00
|
|
|
"github.com/gdamore/tcell/v2"
|
2019-07-21 22:01:51 +02:00
|
|
|
"github.com/google/shlex"
|
2018-02-28 03:17:26 +01:00
|
|
|
|
2021-11-05 10:19:46 +01:00
|
|
|
"git.sr.ht/~rjarry/aerc/config"
|
|
|
|
"git.sr.ht/~rjarry/aerc/lib"
|
2022-04-25 15:30:43 +02:00
|
|
|
"git.sr.ht/~rjarry/aerc/lib/crypto"
|
2021-11-05 10:19:46 +01:00
|
|
|
"git.sr.ht/~rjarry/aerc/lib/ui"
|
2022-07-19 22:31:51 +02:00
|
|
|
"git.sr.ht/~rjarry/aerc/logging"
|
2021-11-05 10:19:46 +01:00
|
|
|
"git.sr.ht/~rjarry/aerc/models"
|
2018-02-28 03:17:26 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type Aerc struct {
|
2019-03-17 21:19:15 +01:00
|
|
|
accounts map[string]*AccountView
|
2019-07-21 22:01:51 +02:00
|
|
|
cmd func(cmd []string) error
|
2019-07-23 18:52:33 +02:00
|
|
|
cmdHistory lib.History
|
2019-06-27 19:33:11 +02:00
|
|
|
complete func(cmd string) []string
|
2019-03-17 21:19:15 +01:00
|
|
|
conf *config.AercConfig
|
2019-09-13 04:16:37 +02:00
|
|
|
focused ui.Interactive
|
|
|
|
grid *ui.Grid
|
2019-03-21 22:49:59 +01:00
|
|
|
simulating int
|
2019-09-13 04:16:37 +02:00
|
|
|
statusbar *ui.Stack
|
2019-03-17 21:19:15 +01:00
|
|
|
statusline *StatusLine
|
2022-09-14 21:36:42 +02:00
|
|
|
pasting bool
|
2019-03-17 21:19:15 +01:00
|
|
|
pendingKeys []config.KeyStroke
|
2019-09-13 04:16:37 +02:00
|
|
|
prompts *ui.Stack
|
|
|
|
tabs *ui.Tabs
|
2020-03-03 22:20:07 +01:00
|
|
|
ui *ui.UI
|
2019-07-29 16:50:02 +02:00
|
|
|
beep func() error
|
2020-05-19 13:06:49 +02:00
|
|
|
dialog ui.DrawableInteractive
|
2022-04-25 15:30:43 +02:00
|
|
|
|
|
|
|
Crypto crypto.Provider
|
2018-02-28 03:17:26 +01:00
|
|
|
}
|
|
|
|
|
2020-04-24 11:36:16 +02:00
|
|
|
type Choice struct {
|
|
|
|
Key string
|
|
|
|
Text string
|
|
|
|
Command []string
|
|
|
|
}
|
|
|
|
|
2022-07-19 22:31:51 +02:00
|
|
|
func NewAerc(conf *config.AercConfig,
|
2022-04-25 15:30:43 +02:00
|
|
|
crypto crypto.Provider, cmd func(cmd []string) error,
|
|
|
|
complete func(cmd string) []string, cmdHistory lib.History,
|
2022-07-31 22:16:40 +02:00
|
|
|
deferLoop chan struct{},
|
|
|
|
) *Aerc {
|
2020-03-07 17:42:41 +01:00
|
|
|
tabs := ui.NewTabs(&conf.Ui)
|
2018-02-28 03:17:26 +01:00
|
|
|
|
2020-07-27 10:03:55 +02:00
|
|
|
statusbar := ui.NewStack(conf.Ui)
|
|
|
|
statusline := NewStatusLine(conf.Ui)
|
2019-03-17 21:19:15 +01:00
|
|
|
statusbar.Push(statusline)
|
|
|
|
|
2019-09-13 04:16:37 +02:00
|
|
|
grid := ui.NewGrid().Rows([]ui.GridSpec{
|
2022-03-18 09:53:02 +01:00
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)},
|
|
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)},
|
2019-09-13 04:16:37 +02:00
|
|
|
}).Columns([]ui.GridSpec{
|
2022-03-18 09:53:02 +01:00
|
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
2018-02-28 03:17:26 +01:00
|
|
|
})
|
2019-03-30 19:12:04 +01:00
|
|
|
grid.AddChild(tabs.TabStrip)
|
|
|
|
grid.AddChild(tabs.TabContent).At(1, 0)
|
|
|
|
grid.AddChild(statusbar).At(2, 0)
|
2018-02-28 03:17:26 +01:00
|
|
|
|
2019-01-14 14:14:03 +01:00
|
|
|
aerc := &Aerc{
|
2019-03-17 21:19:15 +01:00
|
|
|
accounts: make(map[string]*AccountView),
|
|
|
|
conf: conf,
|
|
|
|
cmd: cmd,
|
2019-07-23 18:52:33 +02:00
|
|
|
cmdHistory: cmdHistory,
|
2019-06-27 19:33:11 +02:00
|
|
|
complete: complete,
|
2019-03-17 21:19:15 +01:00
|
|
|
grid: grid,
|
|
|
|
statusbar: statusbar,
|
|
|
|
statusline: statusline,
|
2020-07-27 10:03:55 +02:00
|
|
|
prompts: ui.NewStack(conf.Ui),
|
2019-03-17 21:19:15 +01:00
|
|
|
tabs: tabs,
|
2022-04-25 15:30:43 +02:00
|
|
|
Crypto: crypto,
|
2019-01-14 14:14:03 +01:00
|
|
|
}
|
2019-01-13 19:25:56 +01:00
|
|
|
|
2019-07-16 19:43:08 +02:00
|
|
|
statusline.SetAerc(aerc)
|
2019-07-21 22:01:51 +02:00
|
|
|
conf.Triggers.ExecuteCommand = cmd
|
2019-07-16 19:43:08 +02:00
|
|
|
|
2019-05-22 16:39:52 +02:00
|
|
|
for i, acct := range conf.Accounts {
|
2022-07-19 22:31:51 +02:00
|
|
|
view, err := NewAccountView(aerc, conf, &conf.Accounts[i], aerc, deferLoop)
|
2020-08-08 11:38:38 +02:00
|
|
|
if err != nil {
|
2022-04-17 01:03:49 +02:00
|
|
|
tabs.Add(errorScreen(err.Error(), conf.Ui), acct.Name, nil)
|
2020-08-08 11:38:38 +02:00
|
|
|
} else {
|
|
|
|
aerc.accounts[acct.Name] = view
|
2022-04-17 01:03:49 +02:00
|
|
|
conf := view.UiConfig()
|
2022-07-03 17:11:12 +02:00
|
|
|
tabs.Add(view, acct.Name, conf)
|
2020-08-08 11:38:38 +02:00
|
|
|
}
|
2018-06-12 01:23:09 +02:00
|
|
|
}
|
2018-02-28 03:17:26 +01:00
|
|
|
|
2019-05-22 17:35:55 +02:00
|
|
|
if len(conf.Accounts) == 0 {
|
|
|
|
wizard := NewAccountWizard(aerc.Config(), aerc)
|
|
|
|
wizard.Focus(true)
|
|
|
|
aerc.NewTab(wizard, "New account")
|
|
|
|
}
|
|
|
|
|
2022-07-14 18:29:56 +02:00
|
|
|
tabs.Select(0)
|
|
|
|
|
2019-09-06 00:32:36 +02:00
|
|
|
tabs.CloseTab = func(index int) {
|
2022-07-14 18:29:56 +02:00
|
|
|
tab := aerc.tabs.Get(index)
|
|
|
|
if tab == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
switch content := tab.Content.(type) {
|
2019-09-06 00:32:36 +02:00
|
|
|
case *AccountView:
|
|
|
|
return
|
|
|
|
case *AccountWizard:
|
|
|
|
return
|
|
|
|
case *Composer:
|
|
|
|
aerc.RemoveTab(content)
|
|
|
|
content.Close()
|
|
|
|
case *Terminal:
|
|
|
|
content.Close(nil)
|
|
|
|
case *MessageViewer:
|
|
|
|
aerc.RemoveTab(content)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-14 14:14:03 +01:00
|
|
|
return aerc
|
2018-02-28 03:17:26 +01:00
|
|
|
}
|
|
|
|
|
2019-07-29 16:50:02 +02:00
|
|
|
func (aerc *Aerc) OnBeep(f func() error) {
|
|
|
|
aerc.beep = f
|
|
|
|
}
|
|
|
|
|
|
|
|
func (aerc *Aerc) Beep() {
|
|
|
|
if aerc.beep == nil {
|
2022-07-19 22:31:51 +02:00
|
|
|
logging.Warnf("should beep, but no beeper")
|
2019-07-29 16:50:02 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := aerc.beep(); err != nil {
|
2022-07-19 22:31:51 +02:00
|
|
|
logging.Errorf("tried to beep, but could not: %v", err)
|
2019-07-29 16:50:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-19 11:49:57 +02:00
|
|
|
func (aerc *Aerc) Tick() bool {
|
|
|
|
more := false
|
|
|
|
for _, acct := range aerc.accounts {
|
|
|
|
more = acct.Tick() || more
|
|
|
|
}
|
2019-08-20 03:56:12 +02:00
|
|
|
|
|
|
|
if len(aerc.prompts.Children()) > 0 {
|
|
|
|
more = true
|
|
|
|
previous := aerc.focused
|
|
|
|
prompt := aerc.prompts.Pop().(*ExLine)
|
|
|
|
prompt.finish = func() {
|
|
|
|
aerc.statusbar.Pop()
|
|
|
|
aerc.focus(previous)
|
|
|
|
}
|
|
|
|
|
|
|
|
aerc.statusbar.Push(prompt)
|
|
|
|
aerc.focus(prompt)
|
|
|
|
}
|
|
|
|
|
2019-05-19 11:49:57 +02:00
|
|
|
return more
|
|
|
|
}
|
|
|
|
|
2019-09-13 04:16:37 +02:00
|
|
|
func (aerc *Aerc) OnInvalidate(onInvalidate func(d ui.Drawable)) {
|
|
|
|
aerc.grid.OnInvalidate(func(_ ui.Drawable) {
|
2019-01-13 19:25:56 +01:00
|
|
|
onInvalidate(aerc)
|
|
|
|
})
|
2018-02-28 03:17:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (aerc *Aerc) Invalidate() {
|
|
|
|
aerc.grid.Invalidate()
|
|
|
|
}
|
|
|
|
|
2019-03-17 19:02:33 +01:00
|
|
|
func (aerc *Aerc) Focus(focus bool) {
|
|
|
|
// who cares
|
|
|
|
}
|
|
|
|
|
2019-09-13 04:16:37 +02:00
|
|
|
func (aerc *Aerc) Draw(ctx *ui.Context) {
|
2018-02-28 03:17:26 +01:00
|
|
|
aerc.grid.Draw(ctx)
|
2020-05-19 13:06:49 +02:00
|
|
|
if aerc.dialog != nil {
|
2022-05-24 19:12:35 +02:00
|
|
|
if w, h := ctx.Width(), ctx.Height(); w > 8 && h > 4 {
|
2022-08-08 22:04:02 +02:00
|
|
|
if d, ok := aerc.dialog.(Dialog); ok {
|
|
|
|
start, height := d.ContextHeight()
|
|
|
|
aerc.dialog.Draw(
|
|
|
|
ctx.Subcontext(4, start(h),
|
|
|
|
w-8, height(h)))
|
|
|
|
} else {
|
|
|
|
aerc.dialog.Draw(ctx.Subcontext(4, h/2-2, w-8, 4))
|
|
|
|
}
|
2022-05-24 19:12:35 +02:00
|
|
|
}
|
2020-03-03 22:20:07 +01:00
|
|
|
}
|
2018-02-28 03:17:26 +01:00
|
|
|
}
|
|
|
|
|
2022-08-08 22:04:04 +02:00
|
|
|
func (aerc *Aerc) HumanReadableBindings() []string {
|
|
|
|
var result []string
|
|
|
|
binds := aerc.getBindings()
|
|
|
|
format := func(s string) string {
|
|
|
|
s = strings.ReplaceAll(s, "<space>", " ")
|
|
|
|
return strings.ReplaceAll(s, "%", "%%")
|
|
|
|
}
|
|
|
|
fmtStr := "%10s %s"
|
|
|
|
for _, bind := range binds.Bindings {
|
|
|
|
result = append(result, fmt.Sprintf(fmtStr,
|
|
|
|
format(config.FormatKeyStrokes(bind.Input)),
|
|
|
|
format(config.FormatKeyStrokes(bind.Output)),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
if binds.Globals && aerc.conf.Bindings.Global != nil {
|
|
|
|
for _, bind := range aerc.conf.Bindings.Global.Bindings {
|
|
|
|
result = append(result, fmt.Sprintf(fmtStr+" (Globals)",
|
|
|
|
format(config.FormatKeyStrokes(bind.Input)),
|
|
|
|
format(config.FormatKeyStrokes(bind.Output)),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result = append(result, fmt.Sprintf(fmtStr,
|
|
|
|
"$ex",
|
|
|
|
fmt.Sprintf("'%c'", binds.ExKey.Rune),
|
|
|
|
))
|
|
|
|
result = append(result, fmt.Sprintf(fmtStr,
|
|
|
|
"Globals",
|
|
|
|
fmt.Sprintf("%v", binds.Globals),
|
|
|
|
))
|
|
|
|
sort.Strings(result)
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2019-03-21 22:36:42 +01:00
|
|
|
func (aerc *Aerc) getBindings() *config.KeyBindings {
|
2021-12-10 22:27:29 +01:00
|
|
|
selectedAccountName := ""
|
|
|
|
if aerc.SelectedAccount() != nil {
|
|
|
|
selectedAccountName = aerc.SelectedAccount().acct.Name
|
|
|
|
}
|
2022-07-18 12:54:55 +02:00
|
|
|
switch view := aerc.SelectedTabContent().(type) {
|
2019-03-21 22:36:42 +01:00
|
|
|
case *AccountView:
|
2022-06-19 23:41:15 +02:00
|
|
|
binds := aerc.conf.MergeContextualBinds(aerc.conf.Bindings.MessageList, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "messages")
|
|
|
|
return aerc.conf.MergeContextualBinds(binds, config.BIND_CONTEXT_FOLDER, view.SelectedDirectory(), "messages")
|
2019-05-21 22:31:04 +02:00
|
|
|
case *AccountWizard:
|
|
|
|
return aerc.conf.Bindings.AccountWizard
|
2019-05-12 06:06:09 +02:00
|
|
|
case *Composer:
|
2019-05-14 20:27:28 +02:00
|
|
|
switch view.Bindings() {
|
|
|
|
case "compose::editor":
|
2021-12-10 22:27:29 +01:00
|
|
|
return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.ComposeEditor, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "compose::editor")
|
2019-05-14 20:27:28 +02:00
|
|
|
case "compose::review":
|
2021-12-10 22:27:29 +01:00
|
|
|
return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.ComposeReview, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "compose::review")
|
2019-05-14 20:27:28 +02:00
|
|
|
default:
|
2021-12-10 22:27:29 +01:00
|
|
|
return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.Compose, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "compose")
|
2019-05-14 20:27:28 +02:00
|
|
|
}
|
2019-03-31 03:45:41 +02:00
|
|
|
case *MessageViewer:
|
2022-03-14 04:03:34 +01:00
|
|
|
switch view.Bindings() {
|
|
|
|
case "view::passthrough":
|
|
|
|
return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.MessageViewPassthrough, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "view::passthrough")
|
|
|
|
default:
|
|
|
|
return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.MessageView, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "view")
|
|
|
|
}
|
2019-03-30 19:12:04 +01:00
|
|
|
case *Terminal:
|
2019-03-21 22:44:44 +01:00
|
|
|
return aerc.conf.Bindings.Terminal
|
2019-03-21 22:36:42 +01:00
|
|
|
default:
|
|
|
|
return aerc.conf.Bindings.Global
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (aerc *Aerc) simulate(strokes []config.KeyStroke) {
|
|
|
|
aerc.pendingKeys = []config.KeyStroke{}
|
2019-03-21 22:49:59 +01:00
|
|
|
aerc.simulating += 1
|
2019-03-21 22:36:42 +01:00
|
|
|
for _, stroke := range strokes {
|
|
|
|
simulated := tcell.NewEventKey(
|
|
|
|
stroke.Key, stroke.Rune, tcell.ModNone)
|
|
|
|
aerc.Event(simulated)
|
|
|
|
}
|
2019-03-21 22:49:59 +01:00
|
|
|
aerc.simulating -= 1
|
2022-09-28 20:17:21 +02:00
|
|
|
// If we are still focused on the exline, turn on tab complete
|
|
|
|
if exline, ok := aerc.focused.(*ExLine); ok {
|
|
|
|
exline.TabComplete(func(cmd string) ([]string, string) {
|
|
|
|
return aerc.complete(cmd), ""
|
|
|
|
})
|
|
|
|
}
|
2019-03-21 22:36:42 +01:00
|
|
|
}
|
|
|
|
|
2018-06-01 09:58:00 +02:00
|
|
|
func (aerc *Aerc) Event(event tcell.Event) bool {
|
2020-05-19 13:06:49 +02:00
|
|
|
if aerc.dialog != nil {
|
|
|
|
return aerc.dialog.Event(event)
|
2020-03-03 22:20:07 +01:00
|
|
|
}
|
|
|
|
|
2019-03-17 21:19:15 +01:00
|
|
|
if aerc.focused != nil {
|
|
|
|
return aerc.focused.Event(event)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch event := event.(type) {
|
|
|
|
case *tcell.EventKey:
|
2022-09-14 21:36:42 +02:00
|
|
|
// If we are in a bracketed paste, don't process the keys for
|
|
|
|
// bindings
|
|
|
|
if aerc.pasting {
|
|
|
|
interactive, ok := aerc.SelectedTabContent().(ui.Interactive)
|
|
|
|
if ok {
|
|
|
|
return interactive.Event(event)
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2019-03-22 02:34:12 +01:00
|
|
|
aerc.statusline.Expire()
|
2019-03-17 21:19:15 +01:00
|
|
|
aerc.pendingKeys = append(aerc.pendingKeys, config.KeyStroke{
|
2021-10-25 15:00:43 +02:00
|
|
|
Modifiers: event.Modifiers(),
|
2021-10-26 22:42:07 +02:00
|
|
|
Key: event.Key(),
|
|
|
|
Rune: event.Rune(),
|
2019-03-17 21:19:15 +01:00
|
|
|
})
|
2019-07-16 19:43:08 +02:00
|
|
|
aerc.statusline.Invalidate()
|
2019-03-21 22:36:42 +01:00
|
|
|
bindings := aerc.getBindings()
|
|
|
|
incomplete := false
|
|
|
|
result, strokes := bindings.GetBinding(aerc.pendingKeys)
|
2019-03-17 21:19:15 +01:00
|
|
|
switch result {
|
|
|
|
case config.BINDING_FOUND:
|
2019-03-21 22:36:42 +01:00
|
|
|
aerc.simulate(strokes)
|
|
|
|
return true
|
2019-03-17 21:19:15 +01:00
|
|
|
case config.BINDING_INCOMPLETE:
|
2019-03-21 22:36:42 +01:00
|
|
|
incomplete = true
|
2019-03-17 21:19:15 +01:00
|
|
|
case config.BINDING_NOT_FOUND:
|
2019-03-21 22:36:42 +01:00
|
|
|
}
|
|
|
|
if bindings.Globals {
|
|
|
|
result, strokes = aerc.conf.Bindings.Global.
|
|
|
|
GetBinding(aerc.pendingKeys)
|
|
|
|
switch result {
|
|
|
|
case config.BINDING_FOUND:
|
|
|
|
aerc.simulate(strokes)
|
|
|
|
return true
|
|
|
|
case config.BINDING_INCOMPLETE:
|
|
|
|
incomplete = true
|
|
|
|
case config.BINDING_NOT_FOUND:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !incomplete {
|
2019-03-17 21:19:15 +01:00
|
|
|
aerc.pendingKeys = []config.KeyStroke{}
|
2019-03-21 22:49:59 +01:00
|
|
|
exKey := bindings.ExKey
|
|
|
|
if aerc.simulating > 0 {
|
|
|
|
// Keybindings still use : even if you change the ex key
|
|
|
|
exKey = aerc.conf.Bindings.Global.ExKey
|
|
|
|
}
|
|
|
|
if event.Key() == exKey.Key && event.Rune() == exKey.Rune {
|
2019-11-15 21:28:34 +01:00
|
|
|
aerc.BeginExCommand("")
|
2019-03-17 21:19:15 +01:00
|
|
|
return true
|
|
|
|
}
|
2022-07-14 18:29:56 +02:00
|
|
|
interactive, ok := aerc.SelectedTabContent().(ui.Interactive)
|
2019-03-17 22:39:22 +01:00
|
|
|
if ok {
|
|
|
|
return interactive.Event(event)
|
|
|
|
}
|
|
|
|
return false
|
2019-03-17 21:19:15 +01:00
|
|
|
}
|
2019-07-12 00:15:15 +02:00
|
|
|
case *tcell.EventMouse:
|
2019-09-06 00:32:36 +02:00
|
|
|
x, y := event.Position()
|
|
|
|
aerc.grid.MouseEvent(x, y, event)
|
|
|
|
return true
|
2022-09-14 21:36:42 +02:00
|
|
|
case *tcell.EventPaste:
|
|
|
|
if event.Start() {
|
|
|
|
aerc.pasting = true
|
|
|
|
}
|
|
|
|
if event.End() {
|
|
|
|
aerc.pasting = false
|
|
|
|
}
|
|
|
|
interactive, ok := aerc.SelectedTabContent().(ui.Interactive)
|
|
|
|
if ok {
|
|
|
|
return interactive.Event(event)
|
|
|
|
}
|
|
|
|
return false
|
2019-03-17 21:19:15 +01:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (aerc *Aerc) Config() *config.AercConfig {
|
|
|
|
return aerc.conf
|
2018-02-28 03:17:26 +01:00
|
|
|
}
|
2019-01-14 14:14:03 +01:00
|
|
|
|
2019-03-11 02:15:24 +01:00
|
|
|
func (aerc *Aerc) SelectedAccount() *AccountView {
|
2022-07-18 12:54:55 +02:00
|
|
|
return aerc.account(aerc.SelectedTabContent())
|
2022-04-17 01:03:49 +02:00
|
|
|
}
|
|
|
|
|
2022-08-15 21:58:42 +02:00
|
|
|
func (aerc *Aerc) Account(name string) (*AccountView, error) {
|
|
|
|
if acct, ok := aerc.accounts[name]; ok {
|
|
|
|
return acct, nil
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("account <%s> not found", name)
|
|
|
|
}
|
|
|
|
|
2022-09-19 23:58:24 +02:00
|
|
|
func (aerc *Aerc) PrevAccount() (*AccountView, error) {
|
|
|
|
cur := aerc.SelectedAccount()
|
|
|
|
if cur == nil {
|
|
|
|
return nil, fmt.Errorf("no account selected, cannot get prev")
|
|
|
|
}
|
|
|
|
for i, conf := range aerc.conf.Accounts {
|
|
|
|
if conf.Name == cur.Name() {
|
|
|
|
i -= 1
|
|
|
|
if i == -1 {
|
|
|
|
i = len(aerc.conf.Accounts) - 1
|
|
|
|
}
|
|
|
|
conf = aerc.conf.Accounts[i]
|
|
|
|
return aerc.Account(conf.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("no prev account")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (aerc *Aerc) NextAccount() (*AccountView, error) {
|
|
|
|
cur := aerc.SelectedAccount()
|
|
|
|
if cur == nil {
|
|
|
|
return nil, fmt.Errorf("no account selected, cannot get next")
|
|
|
|
}
|
|
|
|
for i, conf := range aerc.conf.Accounts {
|
|
|
|
if conf.Name == cur.Name() {
|
|
|
|
i += 1
|
|
|
|
if i == len(aerc.conf.Accounts) {
|
|
|
|
i = 0
|
|
|
|
}
|
|
|
|
conf = aerc.conf.Accounts[i]
|
|
|
|
return aerc.Account(conf.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("no next account")
|
|
|
|
}
|
|
|
|
|
2022-08-15 21:58:42 +02:00
|
|
|
func (aerc *Aerc) AccountNames() []string {
|
|
|
|
results := make([]string, 0)
|
|
|
|
for name := range aerc.accounts {
|
|
|
|
results = append(results, name)
|
|
|
|
}
|
|
|
|
return results
|
|
|
|
}
|
|
|
|
|
2022-04-17 01:03:49 +02:00
|
|
|
func (aerc *Aerc) account(d ui.Drawable) *AccountView {
|
|
|
|
switch tab := d.(type) {
|
2019-10-01 19:01:49 +02:00
|
|
|
case *AccountView:
|
|
|
|
return tab
|
|
|
|
case *MessageViewer:
|
|
|
|
return tab.SelectedAccount()
|
2020-04-24 11:42:21 +02:00
|
|
|
case *Composer:
|
|
|
|
return tab.Account()
|
2019-03-17 19:57:05 +01:00
|
|
|
}
|
2019-10-01 19:01:49 +02:00
|
|
|
return nil
|
2019-01-14 14:14:03 +01:00
|
|
|
}
|
2019-03-17 21:19:15 +01:00
|
|
|
|
2022-07-03 17:11:12 +02:00
|
|
|
func (aerc *Aerc) SelectedAccountUiConfig() *config.UIConfig {
|
2022-02-25 00:21:06 +01:00
|
|
|
acct := aerc.SelectedAccount()
|
|
|
|
if acct == nil {
|
2022-07-03 17:11:12 +02:00
|
|
|
return &aerc.conf.Ui
|
2022-02-25 00:21:06 +01:00
|
|
|
}
|
|
|
|
return acct.UiConfig()
|
|
|
|
}
|
|
|
|
|
2022-07-18 12:54:55 +02:00
|
|
|
func (aerc *Aerc) SelectedTabContent() ui.Drawable {
|
2022-07-14 18:29:56 +02:00
|
|
|
tab := aerc.tabs.Selected()
|
|
|
|
if tab == nil {
|
aerc: fix panic when backend is unknown
A panic occurs when an unknown backend is used. This regression was
introduced by commit a34be9eb36d2 ("status: use contextual ui styleset
for statusline"). Before this commit, an error screen for the unknown
backend was displayed. The contextual ui requires an account-specific ui
config but when the backend throws an error in the constructor of the
account view, the call to aerc.SelectedAccountUiConfig() panics:
panic: runtime error: index out of range [0] with length 0 [recovered]
panic: runtime error: index out of range [0] with length 0
goroutine 1 [running]:
git.sr.ht/~rjarry/aerc/logging.PanicHandler()
git.sr.ht/~rjarry/aerc/logging/panic-logger.go:47 +0x6de
panic({0xa42760, 0xc000427068})
runtime/panic.go:844 +0x258
git.sr.ht/~rjarry/aerc/widgets.(*Aerc).SelectedTab(...)
git.sr.ht/~rjarry/aerc/widgets/aerc.go:337
git.sr.ht/~rjarry/aerc/widgets.(*Aerc).SelectedAccount(...)
git.sr.ht/~rjarry/aerc/widgets/aerc.go:313
git.sr.ht/~rjarry/aerc/widgets.(*Aerc).SelectedAccountUiConfig(0x9c99c0?)
git.sr.ht/~rjarry/aerc/widgets/aerc.go:329 +0xe9
git.sr.ht/~rjarry/aerc/widgets.(*StatusLine).uiConfig(...)
git.sr.ht/~rjarry/aerc/widgets/status.go:112
git.sr.ht/~rjarry/aerc/widgets.(*StatusLine).SetError(0xc00043a420,
{0xc000429220, 0x1b})
git.sr.ht/~rjarry/aerc/widgets/status.go:66 +0x4d
git.sr.ht/~rjarry/aerc/widgets.(*Aerc).SetError(0xa7c4d7?,
{0xc000429220?, 0xc00035ec80?})
git.sr.ht/~rjarry/aerc/widgets/aerc.go:440 +0x25
git.sr.ht/~rjarry/aerc/widgets.NewAccountView(0xc000502000,
0xc0002b8000, 0xc000440700, 0xc000098960, {0xb72d58?, 0xc000502000},
0xc00042c3c0)
git.sr.ht/~rjarry/aerc/widgets/account.go:75 +0xafa
git.sr.ht/~rjarry/aerc/widgets.NewAerc(0xc0002b8000, 0xc000098960,
{0xb73300?, 0xc0004380f0}, 0xc000420108, 0xc000430630, {0xb71580?,
0xfae9a0}, 0x2?)
git.sr.ht/~rjarry/aerc/widgets/aerc.go:92 +0x8e5
main.main()
git.sr.ht/~rjarry/aerc/aerc.go:176 +0x5ff
This can be reproduced by adding the following as the first (!) backend
to your accounts.conf:
[test]
source = test
from = test
Expected behavior would be to see the error screen with the "Unknown
Backend" text.
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
2022-07-04 20:15:42 +02:00
|
|
|
return nil
|
|
|
|
}
|
2022-07-14 18:29:56 +02:00
|
|
|
return tab.Content
|
2019-03-17 22:23:53 +01:00
|
|
|
}
|
|
|
|
|
2022-07-14 18:29:56 +02:00
|
|
|
func (aerc *Aerc) SelectedTab() *ui.Tab {
|
|
|
|
return aerc.tabs.Selected()
|
2020-03-02 20:54:42 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 00:32:36 +02:00
|
|
|
func (aerc *Aerc) NewTab(clickable ui.Drawable, name string) *ui.Tab {
|
2022-04-17 01:03:49 +02:00
|
|
|
var uiConf *config.UIConfig = nil
|
|
|
|
if acct := aerc.account(clickable); acct != nil {
|
|
|
|
conf := acct.UiConfig()
|
2022-07-03 17:11:12 +02:00
|
|
|
uiConf = conf
|
2022-04-17 01:03:49 +02:00
|
|
|
}
|
|
|
|
tab := aerc.tabs.Add(clickable, name, uiConf)
|
2022-03-18 22:35:33 +01:00
|
|
|
aerc.UpdateStatus()
|
2019-03-17 21:19:15 +01:00
|
|
|
return tab
|
|
|
|
}
|
|
|
|
|
2019-03-17 22:23:53 +01:00
|
|
|
func (aerc *Aerc) RemoveTab(tab ui.Drawable) {
|
|
|
|
aerc.tabs.Remove(tab)
|
2022-03-24 21:42:05 +01:00
|
|
|
aerc.UpdateStatus()
|
2019-03-17 22:23:53 +01:00
|
|
|
}
|
|
|
|
|
2019-06-11 07:05:56 +02:00
|
|
|
func (aerc *Aerc) ReplaceTab(tabSrc ui.Drawable, tabTarget ui.Drawable, name string) {
|
|
|
|
aerc.tabs.Replace(tabSrc, tabTarget, name)
|
|
|
|
}
|
|
|
|
|
2022-07-14 18:29:56 +02:00
|
|
|
func (aerc *Aerc) MoveTab(i int, relative bool) {
|
|
|
|
aerc.tabs.MoveTab(i, relative)
|
2020-03-02 20:54:42 +01:00
|
|
|
}
|
|
|
|
|
2020-03-07 17:42:41 +01:00
|
|
|
func (aerc *Aerc) PinTab() {
|
|
|
|
aerc.tabs.PinTab()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (aerc *Aerc) UnpinTab() {
|
|
|
|
aerc.tabs.UnpinTab()
|
|
|
|
}
|
|
|
|
|
2019-03-17 21:24:17 +01:00
|
|
|
func (aerc *Aerc) NextTab() {
|
2019-09-06 00:32:36 +02:00
|
|
|
aerc.tabs.NextTab()
|
2019-03-17 21:24:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (aerc *Aerc) PrevTab() {
|
2019-09-06 00:32:36 +02:00
|
|
|
aerc.tabs.PrevTab()
|
2019-03-17 21:24:17 +01:00
|
|
|
}
|
|
|
|
|
2019-07-19 19:12:57 +02:00
|
|
|
func (aerc *Aerc) SelectTab(name string) bool {
|
2022-07-14 18:29:56 +02:00
|
|
|
ok := aerc.tabs.SelectName(name)
|
|
|
|
if ok {
|
|
|
|
aerc.UpdateStatus()
|
2019-07-19 19:12:57 +02:00
|
|
|
}
|
2022-07-14 18:29:56 +02:00
|
|
|
return ok
|
2019-07-19 19:12:57 +02:00
|
|
|
}
|
|
|
|
|
2019-07-26 23:41:13 +02:00
|
|
|
func (aerc *Aerc) SelectTabIndex(index int) bool {
|
2022-07-14 18:29:56 +02:00
|
|
|
ok := aerc.tabs.Select(index)
|
|
|
|
if ok {
|
|
|
|
aerc.UpdateStatus()
|
2019-07-26 23:41:13 +02:00
|
|
|
}
|
2022-07-14 18:29:56 +02:00
|
|
|
return ok
|
2019-07-26 23:41:13 +02:00
|
|
|
}
|
|
|
|
|
2019-07-19 19:12:57 +02:00
|
|
|
func (aerc *Aerc) TabNames() []string {
|
2022-07-14 18:29:56 +02:00
|
|
|
return aerc.tabs.Names()
|
2019-07-19 19:12:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (aerc *Aerc) SelectPreviousTab() bool {
|
|
|
|
return aerc.tabs.SelectPrevious()
|
|
|
|
}
|
|
|
|
|
2019-03-17 21:19:15 +01:00
|
|
|
func (aerc *Aerc) SetStatus(status string) *StatusMessage {
|
|
|
|
return aerc.statusline.Set(status)
|
|
|
|
}
|
|
|
|
|
2022-03-18 22:35:33 +01:00
|
|
|
func (aerc *Aerc) UpdateStatus() {
|
|
|
|
if acct := aerc.SelectedAccount(); acct != nil {
|
|
|
|
acct.UpdateStatus()
|
|
|
|
} else {
|
|
|
|
aerc.ClearStatus()
|
|
|
|
}
|
2022-02-22 20:10:54 +01:00
|
|
|
}
|
|
|
|
|
2022-03-18 22:35:33 +01:00
|
|
|
func (aerc *Aerc) ClearStatus() {
|
|
|
|
aerc.statusline.Set("")
|
2022-02-22 20:10:54 +01:00
|
|
|
}
|
|
|
|
|
2020-07-27 10:03:55 +02:00
|
|
|
func (aerc *Aerc) SetError(status string) *StatusMessage {
|
|
|
|
return aerc.statusline.SetError(status)
|
|
|
|
}
|
|
|
|
|
2020-05-28 16:32:32 +02:00
|
|
|
func (aerc *Aerc) PushStatus(text string, expiry time.Duration) *StatusMessage {
|
|
|
|
return aerc.statusline.Push(text, expiry)
|
2019-03-17 21:19:15 +01:00
|
|
|
}
|
|
|
|
|
2020-07-27 10:03:55 +02:00
|
|
|
func (aerc *Aerc) PushError(text string) *StatusMessage {
|
|
|
|
return aerc.statusline.PushError(text)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (aerc *Aerc) PushSuccess(text string) *StatusMessage {
|
|
|
|
return aerc.statusline.PushSuccess(text)
|
2019-05-26 23:37:39 +02:00
|
|
|
}
|
|
|
|
|
2019-09-13 04:16:37 +02:00
|
|
|
func (aerc *Aerc) focus(item ui.Interactive) {
|
2019-03-17 21:19:15 +01:00
|
|
|
if aerc.focused == item {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if aerc.focused != nil {
|
|
|
|
aerc.focused.Focus(false)
|
|
|
|
}
|
|
|
|
aerc.focused = item
|
2022-07-14 18:29:56 +02:00
|
|
|
interactive, ok := aerc.SelectedTabContent().(ui.Interactive)
|
2019-03-17 21:19:15 +01:00
|
|
|
if item != nil {
|
|
|
|
item.Focus(true)
|
2019-03-22 02:34:12 +01:00
|
|
|
if ok {
|
|
|
|
interactive.Focus(false)
|
|
|
|
}
|
2022-07-31 14:32:48 +02:00
|
|
|
} else if ok {
|
|
|
|
interactive.Focus(true)
|
2019-03-17 21:19:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-15 21:28:34 +01:00
|
|
|
func (aerc *Aerc) BeginExCommand(cmd string) {
|
2019-03-17 21:19:15 +01:00
|
|
|
previous := aerc.focused
|
2022-09-28 20:17:21 +02:00
|
|
|
var tabComplete func(string) ([]string, string)
|
|
|
|
if aerc.simulating != 0 {
|
|
|
|
// Don't try to draw completions for simulated events
|
|
|
|
tabComplete = nil
|
|
|
|
} else {
|
|
|
|
tabComplete = func(cmd string) ([]string, string) {
|
|
|
|
return aerc.complete(cmd), ""
|
|
|
|
}
|
|
|
|
}
|
2019-12-20 19:21:33 +01:00
|
|
|
exline := NewExLine(aerc.conf, cmd, func(cmd string) {
|
2019-07-21 22:01:51 +02:00
|
|
|
parts, err := shlex.Split(cmd)
|
|
|
|
if err != nil {
|
2021-01-30 13:51:32 +01:00
|
|
|
aerc.PushError(err.Error())
|
2019-07-21 22:01:51 +02:00
|
|
|
}
|
|
|
|
err = aerc.cmd(parts)
|
2019-03-17 21:19:15 +01:00
|
|
|
if err != nil {
|
2021-01-30 13:51:32 +01:00
|
|
|
aerc.PushError(err.Error())
|
2019-03-17 21:19:15 +01:00
|
|
|
}
|
2019-07-23 18:52:33 +02:00
|
|
|
// only add to history if this is an unsimulated command,
|
|
|
|
// ie one not executed from a keybinding
|
|
|
|
if aerc.simulating == 0 {
|
|
|
|
aerc.cmdHistory.Add(cmd)
|
|
|
|
}
|
2019-03-17 21:19:15 +01:00
|
|
|
}, func() {
|
|
|
|
aerc.statusbar.Pop()
|
|
|
|
aerc.focus(previous)
|
2022-09-28 20:17:21 +02:00
|
|
|
}, tabComplete, aerc.cmdHistory)
|
2019-03-17 21:19:15 +01:00
|
|
|
aerc.statusbar.Push(exline)
|
|
|
|
aerc.focus(exline)
|
|
|
|
}
|
2019-07-19 20:15:48 +02:00
|
|
|
|
2019-08-20 03:56:12 +02:00
|
|
|
func (aerc *Aerc) RegisterPrompt(prompt string, cmd []string) {
|
2019-12-20 19:21:33 +01:00
|
|
|
p := NewPrompt(aerc.conf, prompt, func(text string) {
|
2019-08-20 03:56:12 +02:00
|
|
|
if text != "" {
|
|
|
|
cmd = append(cmd, text)
|
|
|
|
}
|
|
|
|
err := aerc.cmd(cmd)
|
|
|
|
if err != nil {
|
2021-01-30 13:51:32 +01:00
|
|
|
aerc.PushError(err.Error())
|
2019-08-20 03:56:12 +02:00
|
|
|
}
|
2022-01-07 05:54:28 +01:00
|
|
|
}, func(cmd string) ([]string, string) {
|
|
|
|
return nil, "" // TODO: completions
|
2019-08-20 03:56:12 +02:00
|
|
|
})
|
|
|
|
aerc.prompts.Push(p)
|
|
|
|
}
|
|
|
|
|
2020-04-24 11:36:16 +02:00
|
|
|
func (aerc *Aerc) RegisterChoices(choices []Choice) {
|
|
|
|
cmds := make(map[string][]string)
|
|
|
|
texts := []string{}
|
|
|
|
for _, c := range choices {
|
|
|
|
text := fmt.Sprintf("[%s] %s", c.Key, c.Text)
|
|
|
|
if strings.Contains(c.Text, c.Key) {
|
2020-04-24 18:59:40 +02:00
|
|
|
text = strings.Replace(c.Text, c.Key, "["+c.Key+"]", 1)
|
2020-04-24 11:36:16 +02:00
|
|
|
}
|
|
|
|
texts = append(texts, text)
|
|
|
|
cmds[c.Key] = c.Command
|
|
|
|
}
|
|
|
|
prompt := strings.Join(texts, ", ") + "? "
|
|
|
|
p := NewPrompt(aerc.conf, prompt, func(text string) {
|
|
|
|
cmd, ok := cmds[text]
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err := aerc.cmd(cmd)
|
|
|
|
if err != nil {
|
2021-01-30 13:51:32 +01:00
|
|
|
aerc.PushError(err.Error())
|
2020-04-24 11:36:16 +02:00
|
|
|
}
|
2022-01-07 05:54:28 +01:00
|
|
|
}, func(cmd string) ([]string, string) {
|
|
|
|
return nil, "" // TODO: completions
|
2020-04-24 11:36:16 +02:00
|
|
|
})
|
|
|
|
aerc.prompts.Push(p)
|
|
|
|
}
|
|
|
|
|
2019-07-19 20:15:48 +02:00
|
|
|
func (aerc *Aerc) Mailto(addr *url.URL) error {
|
2020-11-10 20:27:30 +01:00
|
|
|
var subject string
|
2022-03-18 17:05:26 +01:00
|
|
|
var body string
|
2022-07-27 13:34:46 +02:00
|
|
|
var acctName string
|
2020-11-10 20:27:30 +01:00
|
|
|
h := &mail.Header{}
|
2020-11-28 15:45:46 +01:00
|
|
|
to, err := mail.ParseAddressList(addr.Opaque)
|
2022-03-18 01:36:19 +01:00
|
|
|
if err != nil && addr.Opaque != "" {
|
2022-07-31 15:15:27 +02:00
|
|
|
return fmt.Errorf("Could not parse to: %w", err)
|
2020-11-28 15:45:46 +01:00
|
|
|
}
|
|
|
|
h.SetAddressList("to", to)
|
2019-07-19 20:15:48 +02:00
|
|
|
for key, vals := range addr.Query() {
|
2020-11-10 20:27:30 +01:00
|
|
|
switch strings.ToLower(key) {
|
2022-07-27 13:34:46 +02:00
|
|
|
case "account":
|
|
|
|
acctName = strings.Join(vals, "")
|
2022-03-18 17:05:26 +01:00
|
|
|
case "bcc":
|
|
|
|
list, err := mail.ParseAddressList(strings.Join(vals, ","))
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
h.SetAddressList("Bcc", list)
|
|
|
|
case "body":
|
|
|
|
body = strings.Join(vals, "\n")
|
2020-11-10 20:27:30 +01:00
|
|
|
case "cc":
|
|
|
|
list, err := mail.ParseAddressList(strings.Join(vals, ","))
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
h.SetAddressList("Cc", list)
|
|
|
|
case "in-reply-to":
|
2021-01-12 20:21:06 +01:00
|
|
|
for i, msgID := range vals {
|
|
|
|
if len(msgID) > 1 && msgID[0] == '<' &&
|
|
|
|
msgID[len(msgID)-1] == '>' {
|
|
|
|
vals[i] = msgID[1 : len(msgID)-1]
|
|
|
|
}
|
|
|
|
}
|
2020-11-10 20:27:30 +01:00
|
|
|
h.SetMsgIDList("In-Reply-To", vals)
|
|
|
|
case "subject":
|
|
|
|
subject = strings.Join(vals, ",")
|
|
|
|
h.SetText("Subject", subject)
|
|
|
|
default:
|
|
|
|
// any other header gets ignored on purpose to avoid control headers
|
|
|
|
// being injected
|
2019-07-19 20:15:48 +02:00
|
|
|
}
|
|
|
|
}
|
2020-11-10 20:27:30 +01:00
|
|
|
|
2022-07-27 13:34:46 +02:00
|
|
|
acct := aerc.SelectedAccount()
|
|
|
|
if acctName != "" {
|
|
|
|
if a, ok := aerc.accounts[acctName]; ok && a != nil {
|
|
|
|
acct = a
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if acct == nil {
|
|
|
|
return errors.New("No account selected")
|
|
|
|
}
|
|
|
|
|
2020-04-24 11:42:21 +02:00
|
|
|
composer, err := NewComposer(aerc, acct, aerc.Config(),
|
2020-11-10 20:27:30 +01:00
|
|
|
acct.AccountConfig(), acct.Worker(), "", h, models.OriginalMail{})
|
2019-11-03 13:51:14 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
2022-03-18 17:05:26 +01:00
|
|
|
composer.SetContents(strings.NewReader(body))
|
2022-03-22 00:23:47 +01:00
|
|
|
composer.FocusEditor("subject")
|
2019-07-19 20:15:48 +02:00
|
|
|
title := "New email"
|
2020-11-10 20:27:30 +01:00
|
|
|
if subject != "" {
|
|
|
|
title = subject
|
2019-07-19 20:15:48 +02:00
|
|
|
composer.FocusTerminal()
|
|
|
|
}
|
2022-03-18 01:36:19 +01:00
|
|
|
if to == nil {
|
2022-03-22 00:23:47 +01:00
|
|
|
composer.FocusEditor("to")
|
2022-03-18 01:36:19 +01:00
|
|
|
}
|
2019-07-19 20:15:48 +02:00
|
|
|
tab := aerc.NewTab(composer, title)
|
2019-07-23 01:29:07 +02:00
|
|
|
composer.OnHeaderChange("Subject", func(subject string) {
|
2019-07-19 20:15:48 +02:00
|
|
|
if subject == "" {
|
|
|
|
tab.Name = "New email"
|
|
|
|
} else {
|
|
|
|
tab.Name = subject
|
|
|
|
}
|
|
|
|
tab.Content.Invalidate()
|
|
|
|
})
|
|
|
|
return nil
|
|
|
|
}
|
2019-08-07 08:21:15 +02:00
|
|
|
|
2022-07-11 20:11:19 +02:00
|
|
|
func (aerc *Aerc) Mbox(source string) error {
|
|
|
|
acctConf := config.AccountConfig{}
|
|
|
|
if selectedAcct := aerc.SelectedAccount(); selectedAcct != nil {
|
|
|
|
acctConf = *selectedAcct.acct
|
|
|
|
info := fmt.Sprintf("Loading outgoing mbox mail settings from account [%s]", selectedAcct.Name())
|
|
|
|
aerc.PushStatus(info, 10*time.Second)
|
2022-07-19 22:31:51 +02:00
|
|
|
logging.Infof(info)
|
2022-07-11 20:11:19 +02:00
|
|
|
} else {
|
|
|
|
acctConf.From = "<user@localhost>"
|
|
|
|
}
|
|
|
|
acctConf.Name = "mbox"
|
|
|
|
acctConf.Source = source
|
|
|
|
acctConf.Default = "INBOX"
|
|
|
|
acctConf.Archive = "Archive"
|
|
|
|
acctConf.Postpone = "Drafts"
|
|
|
|
acctConf.CopyTo = "Sent"
|
|
|
|
|
2022-07-19 22:31:51 +02:00
|
|
|
mboxView, err := NewAccountView(aerc, aerc.conf, &acctConf, aerc, nil)
|
2022-07-11 20:11:19 +02:00
|
|
|
if err != nil {
|
|
|
|
aerc.NewTab(errorScreen(err.Error(), aerc.conf.Ui), acctConf.Name)
|
|
|
|
} else {
|
|
|
|
aerc.accounts[acctConf.Name] = mboxView
|
|
|
|
aerc.NewTab(mboxView, acctConf.Name)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-08-07 08:21:15 +02:00
|
|
|
func (aerc *Aerc) CloseBackends() error {
|
|
|
|
var returnErr error
|
|
|
|
for _, acct := range aerc.accounts {
|
|
|
|
var raw interface{} = acct.worker.Backend
|
|
|
|
c, ok := raw.(io.Closer)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
err := c.Close()
|
|
|
|
if err != nil {
|
|
|
|
returnErr = err
|
2022-07-19 22:31:51 +02:00
|
|
|
logging.Errorf("Closing backend failed for %s: %v", acct.Name(), err)
|
2019-08-07 08:21:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return returnErr
|
|
|
|
}
|
2020-03-03 22:20:07 +01:00
|
|
|
|
2020-05-19 13:06:49 +02:00
|
|
|
func (aerc *Aerc) AddDialog(d ui.DrawableInteractive) {
|
|
|
|
aerc.dialog = d
|
|
|
|
aerc.dialog.OnInvalidate(func(_ ui.Drawable) {
|
2020-03-03 22:20:07 +01:00
|
|
|
aerc.Invalidate()
|
|
|
|
})
|
|
|
|
aerc.Invalidate()
|
2020-05-19 13:06:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (aerc *Aerc) CloseDialog() {
|
|
|
|
aerc.dialog = nil
|
|
|
|
aerc.Invalidate()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (aerc *Aerc) GetPassword(title string, prompt string) (chText chan string, chErr chan error) {
|
|
|
|
chText = make(chan string, 1)
|
|
|
|
chErr = make(chan error, 1)
|
2020-07-27 10:03:55 +02:00
|
|
|
getPasswd := NewGetPasswd(title, prompt, aerc.conf, func(pw string, err error) {
|
2020-05-19 13:06:49 +02:00
|
|
|
defer func() {
|
|
|
|
close(chErr)
|
|
|
|
close(chText)
|
|
|
|
aerc.CloseDialog()
|
|
|
|
}()
|
|
|
|
if err != nil {
|
|
|
|
chErr <- err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
chErr <- nil
|
|
|
|
chText <- pw
|
|
|
|
})
|
|
|
|
aerc.AddDialog(getPasswd)
|
|
|
|
|
|
|
|
return
|
2020-03-03 22:20:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (aerc *Aerc) Initialize(ui *ui.UI) {
|
|
|
|
aerc.ui = ui
|
|
|
|
}
|
|
|
|
|
2020-05-19 13:06:47 +02:00
|
|
|
func (aerc *Aerc) DecryptKeys(keys []openpgp.Key, symmetric bool) (b []byte, err error) {
|
2020-03-03 22:20:07 +01:00
|
|
|
for _, key := range keys {
|
2020-05-19 13:06:49 +02:00
|
|
|
ident := key.Entity.PrimaryIdentity()
|
|
|
|
chPass, chErr := aerc.GetPassword("Decrypt PGP private key",
|
2020-05-19 13:06:47 +02:00
|
|
|
fmt.Sprintf("Enter password for %s (%8X)\nPress <ESC> to cancel",
|
2020-05-19 13:06:49 +02:00
|
|
|
ident.Name, key.PublicKey.KeyId))
|
|
|
|
|
2022-09-14 21:09:02 +02:00
|
|
|
for err := range chErr {
|
|
|
|
if err != nil {
|
2020-05-19 13:06:49 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
2022-09-14 21:09:02 +02:00
|
|
|
pass := <-chPass
|
|
|
|
err = key.PrivateKey.Decrypt([]byte(pass))
|
|
|
|
return nil, err
|
2020-03-03 22:20:07 +01:00
|
|
|
}
|
|
|
|
}
|
2020-05-19 13:06:47 +02:00
|
|
|
return nil, err
|
2020-03-03 22:20:07 +01:00
|
|
|
}
|
2020-08-08 11:38:38 +02:00
|
|
|
|
|
|
|
// errorScreen is a widget that draws an error in the middle of the context
|
|
|
|
func errorScreen(s string, conf config.UIConfig) ui.Drawable {
|
|
|
|
errstyle := conf.GetStyle(config.STYLE_ERROR)
|
|
|
|
text := ui.NewText(s, errstyle).Strategy(ui.TEXT_CENTER)
|
|
|
|
grid := ui.NewGrid().Rows([]ui.GridSpec{
|
2022-03-18 09:53:02 +01:00
|
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)},
|
|
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
2020-08-08 11:38:38 +02:00
|
|
|
}).Columns([]ui.GridSpec{
|
2022-03-18 09:53:02 +01:00
|
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
2020-08-08 11:38:38 +02:00
|
|
|
})
|
2021-10-26 22:42:07 +02:00
|
|
|
grid.AddChild(ui.NewFill(' ', tcell.StyleDefault)).At(0, 0)
|
2020-08-08 11:38:38 +02:00
|
|
|
grid.AddChild(text).At(1, 0)
|
2021-10-26 22:42:07 +02:00
|
|
|
grid.AddChild(ui.NewFill(' ', tcell.StyleDefault)).At(2, 0)
|
2020-08-08 11:38:38 +02:00
|
|
|
return grid
|
|
|
|
}
|