From 9bd155ae50233d97798496ed3fb2c30fccedf322 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 29 Jul 2024 08:21:45 +0530 Subject: [PATCH] More work on notify kitten --- kittens/notify/main.go | 82 ++++++++++++++++++++++++++++++++++++------ kittens/notify/main.py | 12 +++---- 2 files changed, 77 insertions(+), 17 deletions(-) diff --git a/kittens/notify/main.go b/kittens/notify/main.go index 9c25355b8..e94a1a42f 100644 --- a/kittens/notify/main.go +++ b/kittens/notify/main.go @@ -6,8 +6,10 @@ import ( "os" "strconv" "strings" + "time" "kitty/tools/cli" + "kitty/tools/tty" "kitty/tools/tui/loop" "kitty/tools/utils" ) @@ -22,7 +24,7 @@ func b64encode(x string) string { return base64.RawStdEncoding.EncodeToString(utils.UnsafeStringToBytes(x)) } -func create_metadata(opts *Options) string { +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)) @@ -33,13 +35,13 @@ func create_metadata(opts *Options) string { case "critical": ans = append(ans, "u=2") } - if opts.ExpireTime >= 0 { - ans = append(ans, "w="+strconv.Itoa(opts.ExpireTime)) + 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 opts.WaitTillClosed { + if wait_till_closed { ans = append(ans, "c=1") } m := strings.Join(ans, ":") @@ -49,7 +51,9 @@ func create_metadata(opts *Options) string { return m } -func generate_chunks(title, body, identifier string, opts *Options, wait_till_closed bool, callback func(string)) { +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) @@ -61,10 +65,10 @@ func generate_chunks(title, body, identifier string, opts *Options, wait_till_cl 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) + write_chunk(":d=0:e=1" + p + ";" + enc) } } - metadata := create_metadata(opts) + metadata := create_metadata(opts, wait_till_closed, expire_time) write_chunk(":d=0" + metadata + ";") add_payload("title", title) if body != "" { @@ -73,22 +77,60 @@ func generate_chunks(title, body, identifier string, opts *Options, wait_till_cl write_chunk(";") } -func run_loop(title, body, identifier string, opts *Options, wait_till_closed bool) (err error) { +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, func(x string) { lp.QueueWriteString(x) }) + 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") @@ -107,9 +149,13 @@ func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) { 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, func(x string) { + generate_chunks(title, body, ident, opts, wait_till_closed, expire_time, func(x string) { if err == nil { _, err = os.Stdout.WriteString(x) } @@ -118,7 +164,21 @@ func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) { if opts.PrintIdentifier { fmt.Println(ident) } - err = run_loop(title, body, ident, opts, wait_till_closed) + 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 diff --git a/kittens/notify/main.py b/kittens/notify/main.py index 0b5a598a4..31351e5f4 100644 --- a/kittens/notify/main.py +++ b/kittens/notify/main.py @@ -16,13 +16,13 @@ The urgency of the notification. --expire-time -t -default=-1 -type=int -The duration, in milliseconds, for the notification to appear on screen. The default is to -use the policy of the OS notification service. A value of 0 means the notification should +The duration, for the notification to appear on screen. The default is to +use the policy of the OS notification service. A value of :code:`never` means the notification should never expire, however, this may or may not work depending on the policies of the OS notification -service. Positive values guarantee the notification will be closed automatically -after that many milliseconds have elapsed. The notification could be closed before by user +service. Time is specified in the form NUMBER[SUFFIX] where SUFFIX can be :code:`s` for seconds, :code:`m` for minutes, +:code:`h` for hours or :code:`d` for days. Non-integer numbers are allowed. +If not specified, seconds is assumed. The notification is guaranteed to be closed automatically +after the specified time has elapsed. The notification could be closed before by user action or OS policy.