mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-09 05:35:19 +02:00
192 lines
4.6 KiB
Go
192 lines
4.6 KiB
Go
package notify
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"kitty/tools/cli"
|
|
"kitty/tools/tty"
|
|
"kitty/tools/tui/loop"
|
|
"kitty/tools/utils"
|
|
)
|
|
|
|
var _ = fmt.Print
|
|
|
|
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, wait_till_closed bool, expire_time time.Duration) 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 expire_time >= 0 {
|
|
ans = append(ans, "w="+strconv.FormatInt(expire_time.Milliseconds(), 10))
|
|
}
|
|
if opts.Type != "" {
|
|
ans = append(ans, "t="+b64encode(opts.Type))
|
|
}
|
|
if wait_till_closed {
|
|
ans = append(ans, "c=1")
|
|
}
|
|
m := strings.Join(ans, ":")
|
|
if m != "" {
|
|
m = ":" + m
|
|
}
|
|
return m
|
|
}
|
|
|
|
var debugprintln = tty.DebugPrintln
|
|
|
|
func generate_chunks(title, body, identifier string, opts *Options, wait_till_closed bool, expire_time time.Duration, 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:e=1" + p + ";" + enc)
|
|
}
|
|
}
|
|
metadata := create_metadata(opts, wait_till_closed, expire_time)
|
|
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, expire_time time.Duration) (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, expire_time, func(x string) { lp.QueueWriteString(x) })
|
|
return "", nil
|
|
}
|
|
err = lp.Run()
|
|
ds := lp.DeathSignalName()
|
|
if ds != "" {
|
|
fmt.Println("Killed by signal: ", ds)
|
|
lp.KillIfSignalled()
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func random_ident() (string, error) {
|
|
return utils.HumanUUID4()
|
|
}
|
|
|
|
func parse_duration(x string) (ans time.Duration, err error) {
|
|
switch x {
|
|
case "never":
|
|
return 0, nil
|
|
case "":
|
|
return -1, nil
|
|
}
|
|
trailer := x[len(x)-1]
|
|
multipler := time.Second
|
|
switch trailer {
|
|
case 's':
|
|
x = x[:len(x)-1]
|
|
case 'm':
|
|
x = x[:len(x)-1]
|
|
multipler = time.Minute
|
|
case 'h':
|
|
x = x[:len(x)-1]
|
|
multipler = time.Hour
|
|
case 'd':
|
|
x = x[:len(x)-1]
|
|
multipler = time.Hour * 24
|
|
}
|
|
val, err := strconv.ParseFloat(x, 64)
|
|
if err != nil {
|
|
return ans, err
|
|
}
|
|
ans = time.Duration(float64(multipler) * val)
|
|
return
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
var expire_time time.Duration
|
|
if expire_time, err = parse_duration(opts.ExpireTime); err != nil {
|
|
return 1, fmt.Errorf("Invalid expire time: %s with error: %w", opts.ExpireTime, err)
|
|
}
|
|
wait_till_closed := opts.WaitTillClosed
|
|
if opts.OnlyPrintEscapeCode {
|
|
generate_chunks(title, body, ident, opts, wait_till_closed, expire_time, func(x string) {
|
|
if err == nil {
|
|
_, err = os.Stdout.WriteString(x)
|
|
}
|
|
})
|
|
} else {
|
|
if opts.PrintIdentifier {
|
|
fmt.Println(ident)
|
|
}
|
|
if wait_till_closed {
|
|
err = run_loop(title, body, ident, opts, wait_till_closed, expire_time)
|
|
} else {
|
|
var term *tty.Term
|
|
if term, err = tty.OpenControllingTerm(); err != nil {
|
|
return 1, fmt.Errorf("Failed to open controlling terminal with error: %w", err)
|
|
}
|
|
generate_chunks(title, body, ident, opts, wait_till_closed, expire_time, func(x string) {
|
|
if err == nil {
|
|
_, err = term.WriteString(x)
|
|
}
|
|
})
|
|
term.RestoreAndClose()
|
|
}
|
|
|
|
}
|
|
if err != nil {
|
|
rc = 1
|
|
}
|
|
return
|
|
}
|
|
|
|
func EntryPoint(parent *cli.Command) {
|
|
create_cmd(parent, main)
|
|
}
|