Add click event handling
Signed-off-by: AKP <tom@tdpain.net>
This commit is contained in:
parent
43ce8474cb
commit
21e9e1188e
3 changed files with 87 additions and 10 deletions
|
@ -4,12 +4,13 @@
|
|||
|
||||
---
|
||||
|
||||
This is a i3wm status bar that's built as a toy project. It's pretty basic, doesn't have many features and isn't very configurable unless you want to edit the source and recompile it.
|
||||
This is a i3wm status bar that's built as a toy project. ~~It's~~ **It was** pretty basic (I've since added click event support), doesn't have many features and isn't very configurable unless you want to edit the source and recompile it.
|
||||
|
||||
This interacts with i3 using the [i3bar input protocol](https://i3wm.org/docs/i3bar-protocol.html). i3 versions earlier than v4.3 are not supported.
|
||||
|
||||
### Features
|
||||
|
||||
* Supports click events
|
||||
* SIGUSR1 forces a refresh
|
||||
* It has colours
|
||||
* Sometimes it breaks
|
||||
|
|
|
@ -25,7 +25,7 @@ func main() {
|
|||
Filename: logFileName,
|
||||
MaxSize: 1, // MB
|
||||
MaxAge: 14, // days
|
||||
})).Level(zerolog.ErrorLevel)
|
||||
})).Level(zerolog.DebugLevel)
|
||||
|
||||
if err := run(); err != nil {
|
||||
log.Fatal().Err(err).Msg("unhandled error")
|
||||
|
@ -33,7 +33,7 @@ func main() {
|
|||
}
|
||||
|
||||
func run() error {
|
||||
b := i3bar.New(os.Stdout, time.Second*5, syscall.SIGUSR1)
|
||||
b := i3bar.New(os.Stdout, os.Stdin, time.Second*5, syscall.SIGUSR1)
|
||||
if err := b.Initialise(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package i3bar
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -14,17 +16,20 @@ import (
|
|||
|
||||
type I3bar struct {
|
||||
writer io.Writer
|
||||
reader io.Reader
|
||||
updateInterval time.Duration
|
||||
updateSignal syscall.Signal
|
||||
registeredGenerators []BlockGenerator
|
||||
registeredConsumers []ClickEventConsumer
|
||||
|
||||
hasInitialised bool
|
||||
hasSentFirstLine bool
|
||||
}
|
||||
|
||||
func New(writer io.Writer, updateInterval time.Duration, updateSignal syscall.Signal) *I3bar {
|
||||
func New(writer io.Writer, reader io.Reader, updateInterval time.Duration, updateSignal syscall.Signal) *I3bar {
|
||||
return &I3bar{
|
||||
writer: writer,
|
||||
reader: reader,
|
||||
updateInterval: updateInterval,
|
||||
updateSignal: updateSignal,
|
||||
}
|
||||
|
@ -32,13 +37,13 @@ func New(writer io.Writer, updateInterval time.Duration, updateSignal syscall.Si
|
|||
|
||||
func (b *I3bar) Initialise() error {
|
||||
capabilities, err := json.Marshal(map[string]any{
|
||||
"version": 1,
|
||||
"version": 1,
|
||||
"click_events": true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
if _, err := b.writer.Write(append(capabilities, '\n')); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -95,8 +100,15 @@ func (b *I3bar) Emit(generators []BlockGenerator) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// RegisterBlockGenerator registers a block generator with the status bar. This
|
||||
// function should not be called after StartLoop is called.
|
||||
func (b *I3bar) RegisterBlockGenerator(bg ...BlockGenerator) {
|
||||
b.registeredGenerators = append(b.registeredGenerators, bg...)
|
||||
for _, generator := range bg {
|
||||
if g, ok := generator.(ClickEventConsumer); ok {
|
||||
b.registeredConsumers = append(b.registeredConsumers, g)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *I3bar) StartLoop() error {
|
||||
|
@ -110,15 +122,50 @@ func (b *I3bar) StartLoop() error {
|
|||
sigUpdate := make(chan os.Signal, 1)
|
||||
signal.Notify(sigUpdate, os.Signal(b.updateSignal))
|
||||
|
||||
go b.consumerLoop(func() {
|
||||
sigUpdate <- b.updateSignal
|
||||
})
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-sigUpdate:
|
||||
if err := b.Emit(b.registeredGenerators); err != nil {
|
||||
return err
|
||||
log.Error().Err(err).Msg("could not emit registered generators")
|
||||
}
|
||||
case <-ticker.C:
|
||||
if err := b.Emit(b.registeredGenerators); err != nil {
|
||||
return err
|
||||
log.Error().Err(err).Msg("could not emit registered generators")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *I3bar) consumerLoop(requestBarRefresh func()) {
|
||||
r := bufio.NewReader(b.reader)
|
||||
for {
|
||||
inputBytes, err := r.ReadBytes('\n')
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not read from input reader")
|
||||
continue
|
||||
}
|
||||
|
||||
// "ReadBytes reads until the first occurrence of delim in the input,
|
||||
// returning a slice containing the data up to and including the
|
||||
// delimiter."
|
||||
inputBytes = inputBytes[:len(inputBytes)-1]
|
||||
|
||||
// try and parse inputted JSON
|
||||
event := new(ClickEvent)
|
||||
if err := json.Unmarshal(bytes.Trim(inputBytes, ","), event); err != nil {
|
||||
continue // idk what this could be but it's not relevant so BYE!
|
||||
}
|
||||
|
||||
for _, consumer := range b.registeredConsumers {
|
||||
consumerName, consumerInstance := consumer.GetNameAndInstance()
|
||||
if consumerName == event.Name && (consumerName == "" || consumerInstance == event.Instance) {
|
||||
if consumer.OnClick(event) {
|
||||
requestBarRefresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +191,36 @@ type Block struct {
|
|||
Markup string `json:"markup,omitempty"`
|
||||
}
|
||||
|
||||
type BlockGenerator interface {
|
||||
Block(*ColorSet) (*Block, error)
|
||||
type ProvidesNameAndInstance interface {
|
||||
GetNameAndInstance() (name, instance string)
|
||||
}
|
||||
|
||||
type BlockGenerator interface {
|
||||
ProvidesNameAndInstance
|
||||
Block(*ColorSet) (*Block, error)
|
||||
}
|
||||
|
||||
type ClickEvent struct {
|
||||
Name string `json:"name"`
|
||||
Instance string `json:"instance"`
|
||||
Button int `json:"button"`
|
||||
Modifiers []string `json:"modifiers"`
|
||||
X int `json:"x"`
|
||||
Y int `json:"y"`
|
||||
RelativeX int `json:"relative_x"`
|
||||
RelativeY int `json:"relative_y"`
|
||||
OutputX int `json:"output_x"`
|
||||
OutputY int `json:"output_y"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
}
|
||||
|
||||
type ClickEventConsumer interface {
|
||||
ProvidesNameAndInstance
|
||||
// OnClick is called when a new ClickEvent is recieved with the
|
||||
// corresponding name and instance is recieved. If OnClick returns true, a
|
||||
// refresh of the entire statusbar will be performed.
|
||||
//
|
||||
// OnClick must not modify the ClickEvent as it may be reused elsewhere.
|
||||
OnClick(*ClickEvent) (shouldRefresh bool)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue