more work on notifications kitten

This commit is contained in:
Kovid Goyal
2024-07-29 07:13:32 +05:30
parent 00737ebf30
commit 1b1f5656ee
3 changed files with 124 additions and 2 deletions

View File

@@ -51,7 +51,7 @@ the notification is done, and should be displayed. You can specify the title or
body multiple times and the terminal emulator will concatenate them, thereby body multiple times and the terminal emulator will concatenate them, thereby
allowing arbitrarily long text (terminal emulators are free to impose a sensible allowing arbitrarily long text (terminal emulators are free to impose a sensible
limit to avoid Denial-of-Service attacks). The size of the payload must be no limit to avoid Denial-of-Service attacks). The size of the payload must be no
longer than ``2048`` bytes, *before being encoded*. longer than ``4096`` bytes, *before being encoded*.
Both the ``title`` and ``body`` payloads must be either UTF-8 encoded plain Both the ``title`` and ``body`` payloads must be either UTF-8 encoded plain
text with no embedded escape codes, or UTF-8 text that is :rfc:`base64 <4648>` text with no embedded escape codes, or UTF-8 text that is :rfc:`base64 <4648>`

View File

@@ -1,14 +1,128 @@
package notify package notify
import ( import (
"encoding/base64"
"fmt" "fmt"
"os"
"strconv"
"strings"
"kitty/tools/cli" "kitty/tools/cli"
"kitty/tools/tui/loop"
"kitty/tools/utils"
) )
var _ = fmt.Print var _ = fmt.Print
func main(_ *cli.Command, opts_ *Options, args []string) (rc int, err error) { const ESC_CODE_PREFIX = "\x1b]99;"
const ESC_CODE_SUFFIX = "\x1b\\"
const CHUNK_SIZE = 4096
func b64encode(x string) string {
return base64.RawStdEncoding.EncodeToString(utils.UnsafeStringToBytes(x))
}
func create_metadata(opts *Options) string {
ans := []string{}
if opts.AppName != "" {
ans = append(ans, "f="+b64encode(opts.AppName))
}
switch opts.Urgency {
case "low":
ans = append(ans, "u=0")
case "critical":
ans = append(ans, "u=2")
}
if opts.ExpireTime >= 0 {
ans = append(ans, "w="+strconv.Itoa(opts.ExpireTime))
}
if opts.Type != "" {
ans = append(ans, "t="+b64encode(opts.Type))
}
if opts.WaitTillClosed {
ans = append(ans, "c=1")
}
m := strings.Join(ans, ":")
if m != "" {
m = ":" + m
}
return m
}
func generate_chunks(title, body, identifier string, opts *Options, wait_till_closed bool, callback func(string)) {
prefix := ESC_CODE_PREFIX + "i=" + identifier
write_chunk := func(middle string) {
callback(prefix + middle + ESC_CODE_SUFFIX)
}
add_payload := func(payload_type, payload string) {
p := utils.IfElse(payload_type == "title", "", ":p="+payload_type)
for len(payload) > 0 {
chunk := payload[:min(CHUNK_SIZE, len(payload))]
payload = utils.IfElse(len(payload) > len(chunk), payload[len(chunk):], "")
enc := b64encode(chunk)
write_chunk(":d=0" + p + ";" + enc)
}
}
metadata := create_metadata(opts)
write_chunk(":d=0" + metadata + ";")
add_payload("title", title)
if body != "" {
add_payload("body", body)
}
write_chunk(";")
}
func run_loop(title, body, identifier string, opts *Options, wait_till_closed bool) (err error) {
lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking)
if err != nil {
return err
}
lp.OnInitialize = func() (string, error) {
generate_chunks(title, body, identifier, opts, wait_till_closed, func(x string) { lp.QueueWriteString(x) })
return "", nil
}
}
func random_ident() (string, error) {
return utils.HumanUUID4()
}
func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) {
if len(args) == 0 {
return 1, fmt.Errorf("Must specify a TITLE for the notification")
}
title := args[0]
if len(title) == 0 {
return 1, fmt.Errorf("Must specify a non-empty TITLE for the notification")
}
body := ""
if len(args) > 1 {
body = strings.Join(args[1:], " ")
}
ident := opts.Identifier
if ident == "" {
if ident, err = random_ident(); err != nil {
return 1, fmt.Errorf("Failed to generate a random identifier with error: %w", err)
}
}
wait_till_closed := opts.WaitTillClosed
if opts.OnlyPrintEscapeCode {
generate_chunks(title, body, ident, opts, wait_till_closed, func(x string) {
if err == nil {
_, err = os.Stdout.WriteString(x)
}
})
} else {
if opts.PrintIdentifier {
fmt.Println(ident)
}
err = run_loop(title, body, ident, opts, wait_till_closed)
}
if err != nil {
rc = 1
}
return return
} }

View File

@@ -37,6 +37,7 @@ is already displayed, it is replaced/updated.
--print-identifier -p --print-identifier -p
type=bool-set
Print the identifier for the notification to STDOUT. Useful when not specifying Print the identifier for the notification to STDOUT. Useful when not specifying
your own identifier via the --identifier option. your own identifier via the --identifier option.
@@ -45,6 +46,13 @@ your own identifier via the --identifier option.
type=bool-set type=bool-set
Wait until the notification is closed. If the user activates the notification, Wait until the notification is closed. If the user activates the notification,
"activated" is printed to STDOUT before quitting. "activated" is printed to STDOUT before quitting.
--only-print-escape-code
type=bool-set
Only print the escape code to STDOUT. Useful if using this kitten as part
of a larger application. If this is specified, the --wait-till-closed option
will be used for escape code generation, but no actual waiting will be done.
'''.format '''.format
help_text = '''\ help_text = '''\
Send notifications to the user that are displayed to them via the Send notifications to the user that are displayed to them via the