mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-19 06:54:58 +02:00
more work on notifications kitten
This commit is contained in:
@@ -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>`
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user