From c5527dd87b84bbdc9d29ad998f7ad45119b3f962 Mon Sep 17 00:00:00 2001 From: Sylvie Crowe <107814465+oneirocosm@users.noreply.github.com> Date: Tue, 8 Oct 2024 10:22:17 -0700 Subject: [PATCH] Add a Notification Subcommand to Wsh (#985) This allows users to use the `wsh notify` command to make a popup notification, even from a remote connection. --- cmd/wsh/cmd/wshcmd-notify.go | 41 ++++++++++++++++++++++++++++++ emain/emain-wsh.ts | 8 ++++++ frontend/app/store/wshclientapi.ts | 5 ++++ frontend/types/gotypes.d.ts | 7 +++++ pkg/wshrpc/wshclient/wshclient.go | 6 +++++ pkg/wshrpc/wshrpctypes.go | 8 ++++++ 6 files changed, 75 insertions(+) create mode 100644 cmd/wsh/cmd/wshcmd-notify.go diff --git a/cmd/wsh/cmd/wshcmd-notify.go b/cmd/wsh/cmd/wshcmd-notify.go new file mode 100644 index 000000000..aa1bff72e --- /dev/null +++ b/cmd/wsh/cmd/wshcmd-notify.go @@ -0,0 +1,41 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/wavetermdev/waveterm/pkg/wshrpc" + "github.com/wavetermdev/waveterm/pkg/wshutil" +) + +var notifyTitle string +var notifySilent bool + +var setNotifyCmd = &cobra.Command{ + Use: "notify [-t ] [-s]", + Short: "create a notification", + Args: cobra.ExactArgs(1), + Run: notifyRun, + PreRunE: preRunSetupRpcClient, +} + +func init() { + setNotifyCmd.Flags().StringVarP(¬ifyTitle, "title", "t", "Wsh Notify", "the notification title") + setNotifyCmd.Flags().BoolVarP(¬ifySilent, "silent", "s", false, "whether or not the notification sound is silenced") + rootCmd.AddCommand(setNotifyCmd) +} + +func notifyRun(cmd *cobra.Command, args []string) { + message := args[0] + notificationOptions := &wshrpc.WaveNotificationOptions{ + Title: notifyTitle, + Body: message, + Silent: notifySilent, + } + _, err := RpcClient.SendRpcRequest(wshrpc.Command_Notify, notificationOptions, &wshrpc.RpcOpts{Timeout: 2000, Route: wshutil.ElectronRoute}) + if err != nil { + WriteStderr("[error] sending notification: %v\n", err) + return + } +} diff --git a/emain/emain-wsh.ts b/emain/emain-wsh.ts index 6c95ad0d3..60828fe01 100644 --- a/emain/emain-wsh.ts +++ b/emain/emain-wsh.ts @@ -28,6 +28,14 @@ export class ElectronWshClientType extends WshClient { const rtn = await webGetSelector(wc, data.selector, data.opts); return rtn; } + + async handle_notify(rh: RpcResponseHelper, notificationOptions: WaveNotificationOptions) { + new electron.Notification({ + title: notificationOptions.title, + body: notificationOptions.body, + silent: notificationOptions.silent, + }).show(); + } } export let ElectronWshClient: ElectronWshClientType; diff --git a/frontend/app/store/wshclientapi.ts b/frontend/app/store/wshclientapi.ts index 5d7f194cd..44a9923c4 100644 --- a/frontend/app/store/wshclientapi.ts +++ b/frontend/app/store/wshclientapi.ts @@ -132,6 +132,11 @@ class RpcApiType { return client.wshRpcCall("message", data, opts); } + // command "notify" [call] + NotifyCommand(client: WshClient, data: WaveNotificationOptions, opts?: RpcOpts): Promise<void> { + return client.wshRpcCall("notify", data, opts); + } + // command "remotefiledelete" [call] RemoteFileDeleteCommand(client: WshClient, data: string, opts?: RpcOpts): Promise<void> { return client.wshRpcCall("remotefiledelete", data, opts); diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index 98fb35a28..5076253a3 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -632,6 +632,13 @@ declare global { meta: {[key: string]: any}; }; + // wshrpc.WaveNotificationOptions + type WaveNotificationOptions = { + title?: string; + body?: string; + silent?: boolean; + }; + // waveobj.WaveObj type WaveObj = { otype: string; diff --git a/pkg/wshrpc/wshclient/wshclient.go b/pkg/wshrpc/wshclient/wshclient.go index 71b89ff9d..382f1041a 100644 --- a/pkg/wshrpc/wshclient/wshclient.go +++ b/pkg/wshrpc/wshclient/wshclient.go @@ -163,6 +163,12 @@ func MessageCommand(w *wshutil.WshRpc, data wshrpc.CommandMessageData, opts *wsh return err } +// command "notify", wshserver.NotifyCommand +func NotifyCommand(w *wshutil.WshRpc, data wshrpc.WaveNotificationOptions, opts *wshrpc.RpcOpts) error { + _, err := sendRpcRequestCallHelper[any](w, "notify", data, opts) + return err +} + // command "remotefiledelete", wshserver.RemoteFileDeleteCommand func RemoteFileDeleteCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error { _, err := sendRpcRequestCallHelper[any](w, "remotefiledelete", data, opts) diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go index 0e25854ae..3c24e1d2b 100644 --- a/pkg/wshrpc/wshrpctypes.go +++ b/pkg/wshrpc/wshrpctypes.go @@ -68,6 +68,7 @@ const ( Command_ConnList = "connlist" Command_WebSelector = "webselector" + Command_Notify = "notify" ) type RespOrErrorUnion[T any] struct { @@ -126,6 +127,7 @@ type WshRpcInterface interface { RemoteStreamCpuDataCommand(ctx context.Context) chan RespOrErrorUnion[TimeSeriesData] WebSelectorCommand(ctx context.Context, data CommandWebSelectorData) ([]string, error) + NotifyCommand(ctx context.Context, notificationOptions WaveNotificationOptions) error } // for frontend @@ -374,3 +376,9 @@ type BlockInfoData struct { WindowId string `json:"windowid"` Meta waveobj.MetaMapType `json:"meta"` } + +type WaveNotificationOptions struct { + Title string `json:"title,omitempty"` + Body string `json:"body,omitempty"` + Silent bool `json:"silent,omitempty"` +}