~sircmpwn/aerc

152f8c9519ac1b7b35c3789b03f3d1cc3b1e8881 — Ben Burwell 1 year, 2 months ago 2804f00 0.2.0
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.
M config/aerc.conf.in => config/aerc.conf.in +6 -0
@@ 37,6 37,12 @@ empty-dirlist=(no folders)
# Default: false
mouse-enabled=false

#
# Ring the bell when new messages are received
#
# Default: yes
new-message-bell=true

[viewer]
#
# Specifies the pager to use when displaying emails. Note that some filters

M config/config.go => config/config.go +2 -0
@@ 32,6 32,7 @@ type UIConfig struct {
	EmptyMessage      string   `ini:"empty-message"`
	EmptyDirlist      string   `ini:"empty-dirlist"`
	MouseEnabled      bool     `ini:"mouse-enabled"`
	NewMessageBell    bool     `ini:"new-message-bell"`
}

const (


@@ 344,6 345,7 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
			EmptyMessage:      "(no messages)",
			EmptyDirlist:      "(no folders)",
			MouseEnabled:      false,
			NewMessageBell:    true,
		},

		Viewer: ViewerConfig{

M doc/aerc-config.5.scd => doc/aerc-config.5.scd +5 -0
@@ 105,6 105,11 @@ These options are configured in the *[ui]* section of aerc.conf.

	Default: false

*new-message-bell*
	Ring the bell when a new message is received.

	Default: true

## VIEWER

These options are configured in the *[viewer]* section of aerc.conf.

M lib/msgstore.go => lib/msgstore.go +12 -3
@@ 33,12 33,14 @@ type MessageStore struct {
	pendingHeaders map[uint32]interface{}
	worker         *types.Worker

	triggerNewEmail func(*models.MessageInfo)
	triggerNewEmail        func(*models.MessageInfo)
	triggerDirectoryChange func()
}

func NewMessageStore(worker *types.Worker,
	dirInfo *models.DirectoryInfo,
	triggerNewEmail func(*models.MessageInfo)) *MessageStore {
	triggerNewEmail func(*models.MessageInfo),
	triggerDirectoryChange func()) *MessageStore {

	return &MessageStore{
		Deleted: make(map[uint32]interface{}),


@@ 52,7 54,8 @@ func NewMessageStore(worker *types.Worker,
		pendingHeaders: make(map[uint32]interface{}),
		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) {
	update := false
	directoryChange := false
	switch msg := msg.(type) {
	case *types.DirectoryInfo:
		store.DirInfo = *msg.Info


@@ 159,6 163,7 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
				newMap[uid] = msg
			} else {
				newMap[uid] = nil
				directoryChange = true
			}
		}
		store.Messages = newMap


@@ 225,6 230,10 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
	if update {
		store.update()
	}

	if directoryChange && store.triggerDirectoryChange != nil {
		store.triggerDirectoryChange()
	}
}

func (store *MessageStore) OnUpdate(fn func(store *MessageStore)) {

M lib/ui/interfaces.go => lib/ui/interfaces.go +9 -0
@@ 23,6 23,10 @@ type Interactive interface {
	Focus(focus bool)
}

type Beeper interface {
	OnBeep(func() error)
}

type Simulator interface {
	// Queues up the given input events for simulation
	Simulate(events []tcell.Event)


@@ 33,6 37,11 @@ type DrawableInteractive interface {
	Interactive
}

type DrawableInteractiveBeeper interface {
	DrawableInteractive
	Beeper
}

// A drawable which contains other drawables
type Container interface {
	Drawable

M lib/ui/ui.go => lib/ui/ui.go +2 -1
@@ 19,7 19,7 @@ type UI struct {
}

func Initialize(conf *config.AercConfig,
	content DrawableInteractive) (*UI, error) {
	content DrawableInteractiveBeeper) (*UI, error) {

	screen, err := tcell.NewScreen()
	if err != nil {


@@ 57,6 57,7 @@ func Initialize(conf *config.AercConfig,
	content.OnInvalidate(func(_ Drawable) {
		atomic.StoreInt32(&state.invalid, 1)
	})
	content.OnBeep(screen.Beep)
	content.Focus(true)

	return &state, nil

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

M widgets/aerc.go => widgets/aerc.go +15 -0
@@ 30,6 30,7 @@ type Aerc struct {
	statusline  *StatusLine
	pendingKeys []config.KeyStroke
	tabs        *libui.Tabs
	beep        func() error
}

func NewAerc(conf *config.AercConfig, logger *log.Logger,


@@ 84,6 85,20 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger,
	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 {
	more := false
	for _, acct := range aerc.accounts {

M widgets/tabhost.go => widgets/tabhost.go +1 -0
@@ 8,4 8,5 @@ type TabHost interface {
	BeginExCommand()
	SetStatus(status string) *StatusMessage
	PushStatus(text string, expiry time.Duration) *StatusMessage
	Beep()
}