mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
global shortcut for wave (#287)
* working on easy global shortcut for wave * globalshortcut setting working * cmd for macos, alt for others * re-remove types.ts (was added back during merge) * rename DDItem to DropdownItem, put into custom.d.ts * make some consts
This commit is contained in:
parent
18fe3f3296
commit
3e4bd458b3
@ -29,12 +29,12 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
|
||||
@boundMethod
|
||||
handleChangeFontSize(fontSize: string): void {
|
||||
let newFontSize = Number(fontSize);
|
||||
const newFontSize = Number(fontSize);
|
||||
this.fontSizeDropdownActive.set(false);
|
||||
if (GlobalModel.termFontSize.get() == newFontSize) {
|
||||
return;
|
||||
}
|
||||
let prtn = GlobalCommandRunner.setTermFontSize(newFontSize, false);
|
||||
const prtn = GlobalCommandRunner.setTermFontSize(newFontSize, false);
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@ -67,29 +67,29 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
getFontSizes(): any {
|
||||
let availableFontSizes: { label: string; value: number }[] = [];
|
||||
getFontSizes(): DropdownItem[] {
|
||||
const availableFontSizes: DropdownItem[] = [];
|
||||
for (let s = appconst.MinFontSize; s <= appconst.MaxFontSize; s++) {
|
||||
availableFontSizes.push({ label: s + "px", value: s });
|
||||
availableFontSizes.push({ label: s + "px", value: String(s) });
|
||||
}
|
||||
return availableFontSizes;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
inlineUpdateOpenAIModel(newModel: string): void {
|
||||
let prtn = GlobalCommandRunner.setClientOpenAISettings({ model: newModel });
|
||||
const prtn = GlobalCommandRunner.setClientOpenAISettings({ model: newModel });
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
inlineUpdateOpenAIToken(newToken: string): void {
|
||||
let prtn = GlobalCommandRunner.setClientOpenAISettings({ apitoken: newToken });
|
||||
const prtn = GlobalCommandRunner.setClientOpenAISettings({ apitoken: newToken });
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
inlineUpdateOpenAIMaxTokens(newMaxTokensStr: string): void {
|
||||
let prtn = GlobalCommandRunner.setClientOpenAISettings({ maxtokens: newMaxTokensStr });
|
||||
const prtn = GlobalCommandRunner.setClientOpenAISettings({ maxtokens: newMaxTokensStr });
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@ -105,19 +105,41 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
GlobalModel.clientSettingsViewModel.closeView();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeShortcut(newShortcut: string): void {
|
||||
const prtn = GlobalCommandRunner.setGlobalShortcut(newShortcut);
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
getFKeys(): DropdownItem[] {
|
||||
const opts: DropdownItem[] = [];
|
||||
opts.push({ label: "Disabled", value: "" });
|
||||
const platform = GlobalModel.getPlatform();
|
||||
for (let i = 1; i <= 12; i++) {
|
||||
const shortcut = (platform == "darwin" ? "Cmd" : "Alt") + "+F" + String(i);
|
||||
opts.push({ label: shortcut, value: shortcut });
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
getCurrentShortcut(): string {
|
||||
const clientData = GlobalModel.clientData.get();
|
||||
return clientData?.clientopts?.globalshortcut ?? "";
|
||||
}
|
||||
|
||||
render() {
|
||||
let isHidden = GlobalModel.activeMainView.get() != "clientsettings";
|
||||
const isHidden = GlobalModel.activeMainView.get() != "clientsettings";
|
||||
if (isHidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let cdata: ClientDataType = GlobalModel.clientData.get();
|
||||
let openAIOpts = cdata.openaiopts ?? {};
|
||||
let apiTokenStr = isBlank(openAIOpts.apitoken) ? "(not set)" : "********";
|
||||
let maxTokensStr = String(
|
||||
const cdata: ClientDataType = GlobalModel.clientData.get();
|
||||
const openAIOpts = cdata.openaiopts ?? {};
|
||||
const apiTokenStr = isBlank(openAIOpts.apitoken) ? "(not set)" : "********";
|
||||
const maxTokensStr = String(
|
||||
openAIOpts.maxtokens == null || openAIOpts.maxtokens == 0 ? 1000 : openAIOpts.maxtokens
|
||||
);
|
||||
let curFontSize = GlobalModel.termFontSize.get();
|
||||
const curFontSize = GlobalModel.termFontSize.get();
|
||||
|
||||
return (
|
||||
<div className={cn("view clientsettings-view")}>
|
||||
@ -207,6 +229,17 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Global Hotkey</div>
|
||||
<div className="settings-input">
|
||||
<Dropdown
|
||||
className="hotkey-dropdown"
|
||||
options={this.getFKeys()}
|
||||
defaultValue={this.getCurrentShortcut()}
|
||||
onChange={this.handleChangeShortcut}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsError errorMessage={this.errorMessage} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -17,7 +17,7 @@ interface DropdownDecorationProps {
|
||||
|
||||
interface DropdownProps {
|
||||
label?: string;
|
||||
options: { value: string; label: string }[];
|
||||
options: DropdownItem[];
|
||||
value?: string;
|
||||
className?: string;
|
||||
onChange: (value: string) => void;
|
||||
|
@ -8,8 +8,9 @@ import fetch from "node-fetch";
|
||||
import * as child_process from "node:child_process";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import * as winston from "winston";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import * as util from "util";
|
||||
import * as waveutil from "../util/util";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { handleJsonFetchResponse } from "@/util/util";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { checkKeyPressed, adaptFromElectronKeyEvent, setKeyUtilPlatform } from "@/util/keyutil";
|
||||
@ -29,6 +30,7 @@ let instanceId = uuidv4();
|
||||
let oldConsoleLog = console.log;
|
||||
let wasActive = true;
|
||||
let wasInFg = true;
|
||||
let currentGlobalShortcut: string | null = null;
|
||||
|
||||
checkPromptMigrate();
|
||||
ensureDir(waveHome);
|
||||
@ -412,7 +414,7 @@ function mainResizeHandler(e, win) {
|
||||
});
|
||||
}
|
||||
|
||||
function calcBounds(clientData) {
|
||||
function calcBounds(clientData: ClientDataType) {
|
||||
let primaryDisplay = electron.screen.getPrimaryDisplay();
|
||||
let pdBounds = primaryDisplay.bounds;
|
||||
let size = { x: 100, y: 100, width: pdBounds.width - 200, height: pdBounds.height - 200 };
|
||||
@ -509,6 +511,12 @@ electron.ipcMain.on("open-external-link", async (_, url) => {
|
||||
}
|
||||
});
|
||||
|
||||
electron.ipcMain.on("reregister-global-shortcut", (event, shortcut: string) => {
|
||||
reregisterGlobalShortcut(shortcut);
|
||||
event.returnValue = true;
|
||||
return;
|
||||
});
|
||||
|
||||
electron.ipcMain.on("get-last-logs", async (event, numberOfLines) => {
|
||||
try {
|
||||
const logPath = path.join(getWaveHomeDir(), "wavesrv.log");
|
||||
@ -698,6 +706,34 @@ function runActiveTimer() {
|
||||
setTimeout(runActiveTimer, 60000);
|
||||
}
|
||||
|
||||
function reregisterGlobalShortcut(shortcut: string) {
|
||||
if (shortcut == "") {
|
||||
shortcut = null;
|
||||
}
|
||||
if (currentGlobalShortcut == shortcut) {
|
||||
return;
|
||||
}
|
||||
if (!waveutil.isBlank(currentGlobalShortcut)) {
|
||||
if (electron.globalShortcut.isRegistered(currentGlobalShortcut)) {
|
||||
electron.globalShortcut.unregister(currentGlobalShortcut);
|
||||
}
|
||||
}
|
||||
if (waveutil.isBlank(shortcut)) {
|
||||
currentGlobalShortcut = null;
|
||||
return;
|
||||
}
|
||||
let ok = electron.globalShortcut.register(shortcut, () => {
|
||||
console.log("global shortcut triggered, showing window");
|
||||
MainWindow?.show();
|
||||
});
|
||||
console.log("registered global shortcut", shortcut, ok ? "ok" : "failed");
|
||||
if (!ok) {
|
||||
currentGlobalShortcut = null;
|
||||
console.log("failed to register global shortcut", shortcut);
|
||||
}
|
||||
currentGlobalShortcut = shortcut;
|
||||
}
|
||||
|
||||
// ====== MAIN ====== //
|
||||
|
||||
(async () => {
|
||||
|
@ -12,6 +12,7 @@ contextBridge.exposeInMainWorld("api", {
|
||||
},
|
||||
restartWaveSrv: () => ipcRenderer.sendSync("restart-server"),
|
||||
reloadWindow: () => ipcRenderer.sendSync("reload-window"),
|
||||
reregisterGlobalShortcut: (shortcut) => ipcRenderer.sendSync("reregister-global-shortcut", shortcut),
|
||||
openExternalLink: (url) => ipcRenderer.send("open-external-link", url),
|
||||
onTCmd: (callback) => ipcRenderer.on("t-cmd", callback),
|
||||
onICmd: (callback) => ipcRenderer.on("i-cmd", callback),
|
||||
|
@ -431,6 +431,10 @@ class CommandRunner {
|
||||
}
|
||||
GlobalModel.submitCommand("sidebar", "open", null, kwargs, false);
|
||||
}
|
||||
|
||||
setGlobalShortcut(shortcut: string): Promise<CommandRtnType> {
|
||||
return GlobalModel.submitCommand("client", "setglobalshortcut", [shortcut], { nohist: "1" }, false);
|
||||
}
|
||||
}
|
||||
|
||||
export { CommandRunner };
|
||||
|
@ -55,6 +55,7 @@ type ElectronApi = {
|
||||
restartWaveSrv: () => boolean;
|
||||
reloadWindow: () => void;
|
||||
openExternalLink: (url: string) => void;
|
||||
reregisterGlobalShortcut: (shortcut: string) => void;
|
||||
onTCmd: (callback: (mods: KeyModsType) => void) => void;
|
||||
onICmd: (callback: (mods: KeyModsType) => void) => void;
|
||||
onLCmd: (callback: (mods: KeyModsType) => void) => void;
|
||||
@ -887,7 +888,7 @@ class Model {
|
||||
this.bookmarksModel.mergeBookmarks(update.bookmarks.bookmarks);
|
||||
}
|
||||
} else if (update.clientdata != null) {
|
||||
this.clientData.set(update.clientdata);
|
||||
this.setClientData(update.clientdata);
|
||||
} else if (update.cmdline != null) {
|
||||
this.inputModel.updateCmdLine(update.cmdline);
|
||||
} else if (update.openaicmdinfochat != null) {
|
||||
@ -1095,16 +1096,25 @@ class Model {
|
||||
fetch(url, { method: "post", body: null, headers: fetchHeaders })
|
||||
.then((resp) => handleJsonFetchResponse(url, resp))
|
||||
.then((data) => {
|
||||
mobx.action(() => {
|
||||
const clientData: ClientDataType = data.data;
|
||||
this.clientData.set(clientData);
|
||||
})();
|
||||
this.setClientData(clientData);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.errorHandler("calling get-client-data", err, true);
|
||||
});
|
||||
}
|
||||
|
||||
setClientData(clientData: ClientDataType) {
|
||||
mobx.action(() => {
|
||||
this.clientData.set(clientData);
|
||||
})();
|
||||
let shortcut = null;
|
||||
if (clientData?.clientopts?.globalshortcutenabled) {
|
||||
shortcut = clientData?.clientopts?.globalshortcut;
|
||||
}
|
||||
getApi().reregisterGlobalShortcut(shortcut);
|
||||
}
|
||||
|
||||
submitCommandPacket(cmdPk: FeCmdPacketType, interactive: boolean): Promise<CommandRtnType> {
|
||||
if (this.debugCmds > 0) {
|
||||
console.log("[cmd]", cmdPacketString(cmdPk));
|
||||
@ -1328,6 +1338,10 @@ class Model {
|
||||
}
|
||||
}
|
||||
|
||||
sendUserInput(userInputResponsePacket: UserInputResponsePacket) {
|
||||
this.ws.pushMessage(userInputResponsePacket);
|
||||
}
|
||||
|
||||
sendInputPacket(inputPacket: any) {
|
||||
this.ws.pushMessage(inputPacket);
|
||||
}
|
||||
|
7
src/types/custom.d.ts
vendored
7
src/types/custom.d.ts
vendored
@ -292,6 +292,11 @@ declare global {
|
||||
userquery?: string;
|
||||
};
|
||||
|
||||
type DropdownItem = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Levels for the screen status indicator
|
||||
*/
|
||||
@ -554,6 +559,8 @@ declare global {
|
||||
collapsed: boolean;
|
||||
width: number;
|
||||
};
|
||||
globalshortcut: string;
|
||||
globalshortcutenabled: boolean;
|
||||
};
|
||||
|
||||
type ReleaseInfoType = {
|
||||
|
@ -1,6 +1,5 @@
|
||||
{
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
// "exclude": ["src/electron/emain.ts"],
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
|
@ -224,6 +224,7 @@ func init() {
|
||||
registerCmdFn("client:accepttos", ClientAcceptTosCommand)
|
||||
registerCmdFn("client:setconfirmflag", ClientConfirmFlagCommand)
|
||||
registerCmdFn("client:setsidebar", ClientSetSidebarCommand)
|
||||
registerCmdFn("client:setglobalshortcut", ClientSetGlobalShortcut)
|
||||
|
||||
registerCmdFn("sidebar:open", SidebarOpenCommand)
|
||||
registerCmdFn("sidebar:close", SidebarCloseCommand)
|
||||
@ -4990,6 +4991,28 @@ func ClientConfirmFlagCommand(ctx context.Context, pk *scpacket.FeCommandPacketT
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func ClientSetGlobalShortcut(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
clientData, err := sstore.EnsureClientData(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot retrieve client data: %v", err)
|
||||
}
|
||||
newShortcut := firstArg(pk)
|
||||
if len(newShortcut) > 50 {
|
||||
return nil, fmt.Errorf("invalid shortcut (maxlen = 50)")
|
||||
}
|
||||
clientOpts := clientData.ClientOpts
|
||||
clientOpts.GlobalShortcut = newShortcut
|
||||
clientOpts.GlobalShortcutEnabled = (newShortcut != "")
|
||||
err = sstore.SetClientOpts(ctx, clientOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error updating client data: %v", err)
|
||||
}
|
||||
clientData.ClientOpts = clientOpts
|
||||
update := &sstore.ModelUpdate{}
|
||||
sstore.AddUpdate(update, *clientData)
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func ClientSetSidebarCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
clientData, err := sstore.EnsureClientData(ctx)
|
||||
if err != nil {
|
||||
|
@ -288,6 +288,8 @@ type ClientOptsType struct {
|
||||
AcceptedTos int64 `json:"acceptedtos,omitempty"`
|
||||
ConfirmFlags map[string]bool `json:"confirmflags,omitempty"`
|
||||
MainSidebar *SidebarValueType `json:"mainsidebar,omitempty"`
|
||||
GlobalShortcut string `json:"globalshortcut,omitempty"`
|
||||
GlobalShortcutEnabled bool `json:"globalshortcutenabled,omitempty"`
|
||||
}
|
||||
|
||||
type FeOptsType struct {
|
||||
|
Loading…
Reference in New Issue
Block a user