Ring bell when new messages arrive

Add a "new-message-bell" option to the UI section of aerc.conf. A new
hook into the message store allows the msglist widget to detect new
messages being added to the displayed list. When new messages are
delivered, and the new-message-bell option is enabled (as it is by
default), the terminal will beep.
This commit is contained in:
Ben Burwell 2019-07-29 10:50:02 -04:00 committed by Drew DeVault
parent 2804f00001
commit 152f8c9519
9 changed files with 56 additions and 4 deletions

View File

@ -37,6 +37,12 @@ empty-dirlist=(no folders)
# Default: false # Default: false
mouse-enabled=false mouse-enabled=false
#
# Ring the bell when new messages are received
#
# Default: yes
new-message-bell=true
[viewer] [viewer]
# #
# Specifies the pager to use when displaying emails. Note that some filters # Specifies the pager to use when displaying emails. Note that some filters

View File

@ -32,6 +32,7 @@ type UIConfig struct {
EmptyMessage string `ini:"empty-message"` EmptyMessage string `ini:"empty-message"`
EmptyDirlist string `ini:"empty-dirlist"` EmptyDirlist string `ini:"empty-dirlist"`
MouseEnabled bool `ini:"mouse-enabled"` MouseEnabled bool `ini:"mouse-enabled"`
NewMessageBell bool `ini:"new-message-bell"`
} }
const ( const (
@ -344,6 +345,7 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
EmptyMessage: "(no messages)", EmptyMessage: "(no messages)",
EmptyDirlist: "(no folders)", EmptyDirlist: "(no folders)",
MouseEnabled: false, MouseEnabled: false,
NewMessageBell: true,
}, },
Viewer: ViewerConfig{ Viewer: ViewerConfig{

View File

@ -105,6 +105,11 @@ These options are configured in the *[ui]* section of aerc.conf.
Default: false Default: false
*new-message-bell*
Ring the bell when a new message is received.
Default: true
## VIEWER ## VIEWER
These options are configured in the *[viewer]* section of aerc.conf. These options are configured in the *[viewer]* section of aerc.conf.

View File

@ -33,12 +33,14 @@ type MessageStore struct {
pendingHeaders map[uint32]interface{} pendingHeaders map[uint32]interface{}
worker *types.Worker worker *types.Worker
triggerNewEmail func(*models.MessageInfo) triggerNewEmail func(*models.MessageInfo)
triggerDirectoryChange func()
} }
func NewMessageStore(worker *types.Worker, func NewMessageStore(worker *types.Worker,
dirInfo *models.DirectoryInfo, dirInfo *models.DirectoryInfo,
triggerNewEmail func(*models.MessageInfo)) *MessageStore { triggerNewEmail func(*models.MessageInfo),
triggerDirectoryChange func()) *MessageStore {
return &MessageStore{ return &MessageStore{
Deleted: make(map[uint32]interface{}), Deleted: make(map[uint32]interface{}),
@ -52,7 +54,8 @@ func NewMessageStore(worker *types.Worker,
pendingHeaders: make(map[uint32]interface{}), pendingHeaders: make(map[uint32]interface{}),
worker: worker, worker: worker,
triggerNewEmail: triggerNewEmail, triggerNewEmail: triggerNewEmail,
triggerDirectoryChange: triggerDirectoryChange,
} }
} }
@ -147,6 +150,7 @@ func merge(to *models.MessageInfo, from *models.MessageInfo) {
func (store *MessageStore) Update(msg types.WorkerMessage) { func (store *MessageStore) Update(msg types.WorkerMessage) {
update := false update := false
directoryChange := false
switch msg := msg.(type) { switch msg := msg.(type) {
case *types.DirectoryInfo: case *types.DirectoryInfo:
store.DirInfo = *msg.Info store.DirInfo = *msg.Info
@ -159,6 +163,7 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
newMap[uid] = msg newMap[uid] = msg
} else { } else {
newMap[uid] = nil newMap[uid] = nil
directoryChange = true
} }
} }
store.Messages = newMap store.Messages = newMap
@ -225,6 +230,10 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
if update { if update {
store.update() store.update()
} }
if directoryChange && store.triggerDirectoryChange != nil {
store.triggerDirectoryChange()
}
} }
func (store *MessageStore) OnUpdate(fn func(store *MessageStore)) { func (store *MessageStore) OnUpdate(fn func(store *MessageStore)) {

View File

@ -23,6 +23,10 @@ type Interactive interface {
Focus(focus bool) Focus(focus bool)
} }
type Beeper interface {
OnBeep(func() error)
}
type Simulator interface { type Simulator interface {
// Queues up the given input events for simulation // Queues up the given input events for simulation
Simulate(events []tcell.Event) Simulate(events []tcell.Event)
@ -33,6 +37,11 @@ type DrawableInteractive interface {
Interactive Interactive
} }
type DrawableInteractiveBeeper interface {
DrawableInteractive
Beeper
}
// A drawable which contains other drawables // A drawable which contains other drawables
type Container interface { type Container interface {
Drawable Drawable

View File

@ -19,7 +19,7 @@ type UI struct {
} }
func Initialize(conf *config.AercConfig, func Initialize(conf *config.AercConfig,
content DrawableInteractive) (*UI, error) { content DrawableInteractiveBeeper) (*UI, error) {
screen, err := tcell.NewScreen() screen, err := tcell.NewScreen()
if err != nil { if err != nil {
@ -57,6 +57,7 @@ func Initialize(conf *config.AercConfig,
content.OnInvalidate(func(_ Drawable) { content.OnInvalidate(func(_ Drawable) {
atomic.StoreInt32(&state.invalid, 1) atomic.StoreInt32(&state.invalid, 1)
}) })
content.OnBeep(screen.Beep)
content.Focus(true) content.Focus(true)
return &state, nil return &state, nil

View File

@ -205,6 +205,10 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
func(msg *models.MessageInfo) { func(msg *models.MessageInfo) {
acct.conf.Triggers.ExecNewEmail(acct.acct, acct.conf.Triggers.ExecNewEmail(acct.acct,
acct.conf, msg) acct.conf, msg)
}, func() {
if acct.conf.Ui.NewMessageBell {
acct.host.Beep()
}
}) })
acct.dirlist.SetMsgStore(msg.Info.Name, store) acct.dirlist.SetMsgStore(msg.Info.Name, store)
store.OnUpdate(func(_ *lib.MessageStore) { store.OnUpdate(func(_ *lib.MessageStore) {

View File

@ -30,6 +30,7 @@ type Aerc struct {
statusline *StatusLine statusline *StatusLine
pendingKeys []config.KeyStroke pendingKeys []config.KeyStroke
tabs *libui.Tabs tabs *libui.Tabs
beep func() error
} }
func NewAerc(conf *config.AercConfig, logger *log.Logger, func NewAerc(conf *config.AercConfig, logger *log.Logger,
@ -84,6 +85,20 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger,
return aerc return aerc
} }
func (aerc *Aerc) OnBeep(f func() error) {
aerc.beep = f
}
func (aerc *Aerc) Beep() {
if aerc.beep == nil {
aerc.logger.Printf("should beep, but no beeper")
return
}
if err := aerc.beep(); err != nil {
aerc.logger.Printf("tried to beep, but could not: %v", err)
}
}
func (aerc *Aerc) Tick() bool { func (aerc *Aerc) Tick() bool {
more := false more := false
for _, acct := range aerc.accounts { for _, acct := range aerc.accounts {

View File

@ -8,4 +8,5 @@ type TabHost interface {
BeginExCommand() BeginExCommand()
SetStatus(status string) *StatusMessage SetStatus(status string) *StatusMessage
PushStatus(text string, expiry time.Duration) *StatusMessage PushStatus(text string, expiry time.Duration) *StatusMessage
Beep()
} }