mirror of https://github.com/Wox-launcher/Wox
Add hotkey support for actions
This update introduces hotkey support, allowing actions and queries to be triggered via hotkeys. New fields and helper methods were added to handle hotkey parsing and execution. Additionally, modifications were made to handle key events and map them to the corresponding actions.
This commit is contained in:
parent
d85832c346
commit
889bfb2020
|
@ -32,7 +32,7 @@
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wox-launcher/wox-plugin": "^0.0.78",
|
"@wox-launcher/wox-plugin": "^0.0.79",
|
||||||
"dayjs": "^1.11.9",
|
"dayjs": "^1.11.9",
|
||||||
"promise-deferred": "^2.0.4",
|
"promise-deferred": "^2.0.4",
|
||||||
"winston": "^3.10.0",
|
"winston": "^3.10.0",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@wox-launcher/wox-plugin",
|
"name": "@wox-launcher/wox-plugin",
|
||||||
"version": "0.0.78",
|
"version": "0.0.79",
|
||||||
"description": "All nodejs plugin for Wox should use types in this package",
|
"description": "All nodejs plugin for Wox should use types in this package",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -1,246 +1,253 @@
|
||||||
import { MetadataCommand, PluginSettingDefinitionItem } from "./setting.js"
|
import {MetadataCommand, PluginSettingDefinitionItem} from "./setting.js"
|
||||||
import { AI } from "./ai.js"
|
import {AI} from "./ai.js"
|
||||||
|
|
||||||
export type MapString = { [key: string]: string }
|
export type MapString = { [key: string]: string }
|
||||||
|
|
||||||
export type Platform = "windows" | "darwin" | "linux"
|
export type Platform = "windows" | "darwin" | "linux"
|
||||||
|
|
||||||
export interface Plugin {
|
export interface Plugin {
|
||||||
init: (ctx: Context, initParams: PluginInitParams) => Promise<void>
|
init: (ctx: Context, initParams: PluginInitParams) => Promise<void>
|
||||||
query: (ctx: Context, query: Query) => Promise<Result[]>
|
query: (ctx: Context, query: Query) => Promise<Result[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Selection {
|
export interface Selection {
|
||||||
Type: "text" | "file"
|
Type: "text" | "file"
|
||||||
// Only available when Type is text
|
// Only available when Type is text
|
||||||
Text: string
|
Text: string
|
||||||
// Only available when Type is file
|
// Only available when Type is file
|
||||||
FilePaths: string[]
|
FilePaths: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryEnv {
|
export interface QueryEnv {
|
||||||
/**
|
/**
|
||||||
* Active window title when user query
|
* Active window title when user query
|
||||||
*/
|
*/
|
||||||
ActiveWindowTitle: string
|
ActiveWindowTitle: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Query {
|
export interface Query {
|
||||||
/**
|
/**
|
||||||
* By default, Wox will only pass input query to plugin.
|
* By default, Wox will only pass input query to plugin.
|
||||||
* plugin author need to enable MetadataFeatureQuerySelection feature to handle selection query
|
* plugin author need to enable MetadataFeatureQuerySelection feature to handle selection query
|
||||||
*/
|
*/
|
||||||
Type: "input" | "selection"
|
Type: "input" | "selection"
|
||||||
/**
|
/**
|
||||||
* Raw query, this includes trigger keyword if it has
|
* Raw query, this includes trigger keyword if it has
|
||||||
* We didn't recommend use this property directly. You should always use Search property.
|
* We didn't recommend use this property directly. You should always use Search property.
|
||||||
*
|
*
|
||||||
* NOTE: Only available when query type is input
|
* NOTE: Only available when query type is input
|
||||||
*/
|
*/
|
||||||
RawQuery: string
|
RawQuery: string
|
||||||
/**
|
/**
|
||||||
* Trigger keyword of a query. It can be empty if user is using global trigger keyword.
|
* Trigger keyword of a query. It can be empty if user is using global trigger keyword.
|
||||||
*
|
*
|
||||||
* NOTE: Only available when query type is input
|
* NOTE: Only available when query type is input
|
||||||
*/
|
*/
|
||||||
TriggerKeyword?: string
|
TriggerKeyword?: string
|
||||||
/**
|
/**
|
||||||
* Command part of a query.
|
* Command part of a query.
|
||||||
*
|
*
|
||||||
* NOTE: Only available when query type is input
|
* NOTE: Only available when query type is input
|
||||||
*/
|
*/
|
||||||
Command?: string
|
Command?: string
|
||||||
/**
|
/**
|
||||||
* Search part of a query.
|
* Search part of a query.
|
||||||
*
|
*
|
||||||
* NOTE: Only available when query type is input
|
* NOTE: Only available when query type is input
|
||||||
*/
|
*/
|
||||||
Search: string
|
Search: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User selected or drag-drop data, can be text or file or image etc
|
* User selected or drag-drop data, can be text or file or image etc
|
||||||
*
|
*
|
||||||
* NOTE: Only available when query type is selection
|
* NOTE: Only available when query type is selection
|
||||||
*/
|
*/
|
||||||
Selection: Selection
|
Selection: Selection
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Additional query environment data
|
* Additional query environment data
|
||||||
* expose more context env data to plugin, E.g. plugin A only show result when active window title is "Chrome"
|
* expose more context env data to plugin, E.g. plugin A only show result when active window title is "Chrome"
|
||||||
*/
|
*/
|
||||||
Env: QueryEnv
|
Env: QueryEnv
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether current query is global query
|
* Whether current query is global query
|
||||||
*/
|
*/
|
||||||
IsGlobalQuery(): boolean
|
IsGlobalQuery(): boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Result {
|
export interface Result {
|
||||||
Id?: string
|
Id?: string
|
||||||
Title: string
|
Title: string
|
||||||
SubTitle?: string
|
SubTitle?: string
|
||||||
Icon: WoxImage
|
Icon: WoxImage
|
||||||
Preview?: WoxPreview
|
Preview?: WoxPreview
|
||||||
Score?: number
|
Score?: number
|
||||||
Group?: string
|
Group?: string
|
||||||
GroupScore?: number
|
GroupScore?: number
|
||||||
Tails?: ResultTail[]
|
Tails?: ResultTail[]
|
||||||
ContextData?: string
|
ContextData?: string
|
||||||
Actions?: ResultAction[]
|
Actions?: ResultAction[]
|
||||||
// refresh result after specified interval, in milliseconds. If this value is 0, Wox will not refresh this result
|
// refresh result after specified interval, in milliseconds. If this value is 0, Wox will not refresh this result
|
||||||
// interval can only divisible by 100, if not, Wox will use the nearest number which is divisible by 100
|
// interval can only divisible by 100, if not, Wox will use the nearest number which is divisible by 100
|
||||||
// E.g. if you set 123, Wox will use 200, if you set 1234, Wox will use 1300
|
// E.g. if you set 123, Wox will use 200, if you set 1234, Wox will use 1300
|
||||||
RefreshInterval?: number
|
RefreshInterval?: number
|
||||||
// refresh result by calling OnRefresh function
|
// refresh result by calling OnRefresh function
|
||||||
OnRefresh?: (current: RefreshableResult) => Promise<RefreshableResult>
|
OnRefresh?: (current: RefreshableResult) => Promise<RefreshableResult>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResultTail {
|
export interface ResultTail {
|
||||||
Type: "text" | "image"
|
Type: "text" | "image"
|
||||||
Text?: string
|
Text?: string
|
||||||
Image?: WoxImage
|
Image?: WoxImage
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RefreshableResult {
|
export interface RefreshableResult {
|
||||||
Title: string
|
Title: string
|
||||||
SubTitle: string
|
SubTitle: string
|
||||||
Icon: WoxImage
|
Icon: WoxImage
|
||||||
Preview: WoxPreview
|
Preview: WoxPreview
|
||||||
ContextData: string
|
ContextData: string
|
||||||
RefreshInterval: number
|
RefreshInterval: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResultAction {
|
export interface ResultAction {
|
||||||
/**
|
/**
|
||||||
* Result id, should be unique. It's optional, if you don't set it, Wox will assign a random id for you
|
* Result id, should be unique. It's optional, if you don't set it, Wox will assign a random id for you
|
||||||
*/
|
*/
|
||||||
Id?: string
|
Id?: string
|
||||||
Name: string
|
Name: string
|
||||||
Icon?: WoxImage
|
Icon?: WoxImage
|
||||||
/**
|
/**
|
||||||
* If true, Wox will use this action as default action. There can be only one default action in results
|
* If true, Wox will use this action as default action. There can be only one default action in results
|
||||||
* This can be omitted, if you don't set it, Wox will use the first action as default action
|
* This can be omitted, if you don't set it, Wox will use the first action as default action
|
||||||
*/
|
*/
|
||||||
IsDefault?: boolean
|
IsDefault?: boolean
|
||||||
/**
|
/**
|
||||||
* If true, Wox will not hide after user select this result
|
* If true, Wox will not hide after user select this result
|
||||||
*/
|
*/
|
||||||
PreventHideAfterAction?: boolean
|
PreventHideAfterAction?: boolean
|
||||||
Action: (actionContext: ActionContext) => Promise<void>
|
Action: (actionContext: ActionContext) => Promise<void>
|
||||||
|
/**
|
||||||
|
* Hotkey to trigger this action. E.g. "ctrl+Shift+Space", "Ctrl+1", "Command+K"
|
||||||
|
* Case insensitive, space insensitive
|
||||||
|
*
|
||||||
|
* If IsDefault is true, Hotkey will be set to enter key by default
|
||||||
|
*/
|
||||||
|
Hotkey?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ActionContext {
|
export interface ActionContext {
|
||||||
ContextData: string
|
ContextData: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PluginInitParams {
|
export interface PluginInitParams {
|
||||||
API: PublicAPI
|
API: PublicAPI
|
||||||
PluginDirectory: string
|
PluginDirectory: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChangeQueryParam {
|
export interface ChangeQueryParam {
|
||||||
QueryType: "input" | "selection"
|
QueryType: "input" | "selection"
|
||||||
QueryText?: string
|
QueryText?: string
|
||||||
QuerySelection?: Selection
|
QuerySelection?: Selection
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PublicAPI {
|
export interface PublicAPI {
|
||||||
/**
|
/**
|
||||||
* Change Wox query
|
* Change Wox query
|
||||||
*/
|
*/
|
||||||
ChangeQuery: (ctx: Context, query: ChangeQueryParam) => Promise<void>
|
ChangeQuery: (ctx: Context, query: ChangeQueryParam) => Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide Wox
|
* Hide Wox
|
||||||
*/
|
*/
|
||||||
HideApp: (ctx: Context) => Promise<void>
|
HideApp: (ctx: Context) => Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show Wox
|
* Show Wox
|
||||||
*/
|
*/
|
||||||
ShowApp: (ctx: Context) => Promise<void>
|
ShowApp: (ctx: Context) => Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify message
|
* Notify message
|
||||||
*/
|
*/
|
||||||
Notify: (ctx: Context, title: string, description?: string) => Promise<void>
|
Notify: (ctx: Context, title: string, description?: string) => Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write log
|
* Write log
|
||||||
*/
|
*/
|
||||||
Log: (ctx: Context, level: "Info" | "Error" | "Debug" | "Warning", msg: string) => Promise<void>
|
Log: (ctx: Context, level: "Info" | "Error" | "Debug" | "Warning", msg: string) => Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get translation of current language
|
* Get translation of current language
|
||||||
*/
|
*/
|
||||||
GetTranslation: (ctx: Context, key: string) => Promise<string>
|
GetTranslation: (ctx: Context, key: string) => Promise<string>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get customized setting
|
* Get customized setting
|
||||||
*
|
*
|
||||||
* will try to get platform specific setting first, if not found, will try to get global setting
|
* will try to get platform specific setting first, if not found, will try to get global setting
|
||||||
*/
|
*/
|
||||||
GetSetting: (ctx: Context, key: string) => Promise<string>
|
GetSetting: (ctx: Context, key: string) => Promise<string>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save customized setting
|
* Save customized setting
|
||||||
*
|
*
|
||||||
* @isPlatformSpecific If true, setting will be only saved in current platform. If false, setting will be available in all platforms
|
* @isPlatformSpecific If true, setting will be only saved in current platform. If false, setting will be available in all platforms
|
||||||
*/
|
*/
|
||||||
SaveSetting: (ctx: Context, key: string, value: string, isPlatformSpecific: boolean) => Promise<void>
|
SaveSetting: (ctx: Context, key: string, value: string, isPlatformSpecific: boolean) => Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register setting changed callback
|
* Register setting changed callback
|
||||||
*/
|
*/
|
||||||
OnSettingChanged: (ctx: Context, callback: (key: string, value: string) => void) => Promise<void>
|
OnSettingChanged: (ctx: Context, callback: (key: string, value: string) => void) => Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get dynamic setting definition
|
* Get dynamic setting definition
|
||||||
*/
|
*/
|
||||||
OnGetDynamicSetting: (ctx: Context, callback: (key: string) => PluginSettingDefinitionItem) => Promise<void>
|
OnGetDynamicSetting: (ctx: Context, callback: (key: string) => PluginSettingDefinitionItem) => Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register deep link callback
|
* Register deep link callback
|
||||||
*/
|
*/
|
||||||
OnDeepLink: (ctx: Context, callback: (arguments: MapString) => void) => Promise<void>
|
OnDeepLink: (ctx: Context, callback: (arguments: MapString) => void) => Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register on load event
|
* Register on load event
|
||||||
*/
|
*/
|
||||||
OnUnload: (ctx: Context, callback: () => Promise<void>) => Promise<void>
|
OnUnload: (ctx: Context, callback: () => Promise<void>) => Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register query commands
|
* Register query commands
|
||||||
*/
|
*/
|
||||||
RegisterQueryCommands: (ctx: Context, commands: MetadataCommand[]) => Promise<void>
|
RegisterQueryCommands: (ctx: Context, commands: MetadataCommand[]) => Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chat using LLM
|
* Chat using LLM
|
||||||
*/
|
*/
|
||||||
LLMStream: (ctx: Context, conversations: AI.Conversation[], callback: AI.ChatStreamFunc) => Promise<void>
|
LLMStream: (ctx: Context, conversations: AI.Conversation[], callback: AI.ChatStreamFunc) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WoxImageType = "absolute" | "relative" | "base64" | "svg" | "url" | "emoji" | "lottie"
|
export type WoxImageType = "absolute" | "relative" | "base64" | "svg" | "url" | "emoji" | "lottie"
|
||||||
|
|
||||||
export interface WoxImage {
|
export interface WoxImage {
|
||||||
ImageType: WoxImageType
|
ImageType: WoxImageType
|
||||||
ImageData: string
|
ImageData: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WoxPreviewType = "markdown" | "text" | "image" | "url" | "file"
|
export type WoxPreviewType = "markdown" | "text" | "image" | "url" | "file"
|
||||||
|
|
||||||
export interface WoxPreview {
|
export interface WoxPreview {
|
||||||
PreviewType: WoxPreviewType
|
PreviewType: WoxPreviewType
|
||||||
PreviewData: string
|
PreviewData: string
|
||||||
PreviewProperties: Record<string, string>
|
PreviewProperties: Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare interface Context {
|
export declare interface Context {
|
||||||
Values: { [key: string]: string }
|
Values: { [key: string]: string }
|
||||||
Get: (key: string) => string | undefined
|
Get: (key: string) => string | undefined
|
||||||
Set: (key: string, value: string) => void
|
Set: (key: string, value: string) => void
|
||||||
Exists: (key: string) => boolean
|
Exists: (key: string) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NewContext(): Context
|
export function NewContext(): Context
|
||||||
|
|
|
@ -178,7 +178,7 @@ class _WoxSettingPluginTableUpdateState extends State<WoxSettingPluginTableUpdat
|
||||||
);
|
);
|
||||||
case PluginSettingValueType.pluginSettingValueTableColumnTypeHotkey:
|
case PluginSettingValueType.pluginSettingValueTableColumnTypeHotkey:
|
||||||
return WoxHotkeyRecorder(
|
return WoxHotkeyRecorder(
|
||||||
hotkey: WoxHotkey.parseHotkey(getValue(column.key)),
|
hotkey: WoxHotkey.parseHotkeyFromString(getValue(column.key)),
|
||||||
onHotKeyRecorded: (hotkey) {
|
onHotKeyRecorded: (hotkey) {
|
||||||
updateValue(column.key, hotkey);
|
updateValue(column.key, hotkey);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
import 'package:uuid/v4.dart';
|
import 'package:uuid/v4.dart';
|
||||||
import 'package:wox/api/wox_api.dart';
|
import 'package:wox/api/wox_api.dart';
|
||||||
|
import 'package:wox/entity/wox_hotkey.dart';
|
||||||
import 'package:wox/utils/colors.dart';
|
import 'package:wox/utils/colors.dart';
|
||||||
import 'package:wox/utils/log.dart';
|
import 'package:wox/utils/log.dart';
|
||||||
|
|
||||||
|
@ -41,107 +42,9 @@ class _WoxHotkeyRecorderState extends State<WoxHotkeyRecorder> {
|
||||||
HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
|
HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
String getHotkeyString(HotKey hotKey) {
|
|
||||||
var modifiers = [];
|
|
||||||
if (hotKey.modifiers != null) {
|
|
||||||
for (var modifier in hotKey.modifiers!) {
|
|
||||||
if (modifier == HotKeyModifier.shift) {
|
|
||||||
modifiers.add("shift");
|
|
||||||
} else if (modifier == HotKeyModifier.control) {
|
|
||||||
modifiers.add("ctrl");
|
|
||||||
} else if (modifier == HotKeyModifier.alt) {
|
|
||||||
if (Platform.isMacOS) {
|
|
||||||
modifiers.add("option");
|
|
||||||
} else {
|
|
||||||
modifiers.add("alt");
|
|
||||||
}
|
|
||||||
} else if (modifier == HotKeyModifier.meta) {
|
|
||||||
if (Platform.isMacOS) {
|
|
||||||
modifiers.add("cmd");
|
|
||||||
} else {
|
|
||||||
modifiers.add("win");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var keyStr = hotKey.key.keyLabel.toLowerCase();
|
|
||||||
if (hotKey.key == PhysicalKeyboardKey.space) {
|
|
||||||
keyStr = "space";
|
|
||||||
} else if (hotKey.key == PhysicalKeyboardKey.enter) {
|
|
||||||
keyStr = "enter";
|
|
||||||
} else if (hotKey.key == PhysicalKeyboardKey.backspace) {
|
|
||||||
keyStr = "backspace";
|
|
||||||
} else if (hotKey.key == PhysicalKeyboardKey.delete) {
|
|
||||||
keyStr = "delete";
|
|
||||||
} else if (hotKey.key == PhysicalKeyboardKey.arrowLeft) {
|
|
||||||
keyStr = "left";
|
|
||||||
} else if (hotKey.key == PhysicalKeyboardKey.arrowDown) {
|
|
||||||
keyStr = "down";
|
|
||||||
} else if (hotKey.key == PhysicalKeyboardKey.arrowRight) {
|
|
||||||
keyStr = "right";
|
|
||||||
} else if (hotKey.key == PhysicalKeyboardKey.arrowUp) {
|
|
||||||
keyStr = "up";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "${modifiers.join("+")}+$keyStr";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isAllowedKey(PhysicalKeyboardKey key) {
|
|
||||||
var allowedKeys = [
|
|
||||||
PhysicalKeyboardKey.keyA,
|
|
||||||
PhysicalKeyboardKey.keyB,
|
|
||||||
PhysicalKeyboardKey.keyC,
|
|
||||||
PhysicalKeyboardKey.keyD,
|
|
||||||
PhysicalKeyboardKey.keyE,
|
|
||||||
PhysicalKeyboardKey.keyF,
|
|
||||||
PhysicalKeyboardKey.keyG,
|
|
||||||
PhysicalKeyboardKey.keyH,
|
|
||||||
PhysicalKeyboardKey.keyI,
|
|
||||||
PhysicalKeyboardKey.keyJ,
|
|
||||||
PhysicalKeyboardKey.keyK,
|
|
||||||
PhysicalKeyboardKey.keyL,
|
|
||||||
PhysicalKeyboardKey.keyM,
|
|
||||||
PhysicalKeyboardKey.keyN,
|
|
||||||
PhysicalKeyboardKey.keyO,
|
|
||||||
PhysicalKeyboardKey.keyP,
|
|
||||||
PhysicalKeyboardKey.keyQ,
|
|
||||||
PhysicalKeyboardKey.keyR,
|
|
||||||
PhysicalKeyboardKey.keyS,
|
|
||||||
PhysicalKeyboardKey.keyT,
|
|
||||||
PhysicalKeyboardKey.keyU,
|
|
||||||
PhysicalKeyboardKey.keyV,
|
|
||||||
PhysicalKeyboardKey.keyW,
|
|
||||||
PhysicalKeyboardKey.keyX,
|
|
||||||
PhysicalKeyboardKey.keyY,
|
|
||||||
PhysicalKeyboardKey.keyZ,
|
|
||||||
PhysicalKeyboardKey.digit1,
|
|
||||||
PhysicalKeyboardKey.digit2,
|
|
||||||
PhysicalKeyboardKey.digit3,
|
|
||||||
PhysicalKeyboardKey.digit4,
|
|
||||||
PhysicalKeyboardKey.digit5,
|
|
||||||
PhysicalKeyboardKey.digit6,
|
|
||||||
PhysicalKeyboardKey.digit7,
|
|
||||||
PhysicalKeyboardKey.digit8,
|
|
||||||
PhysicalKeyboardKey.digit9,
|
|
||||||
PhysicalKeyboardKey.digit0,
|
|
||||||
PhysicalKeyboardKey.space,
|
|
||||||
PhysicalKeyboardKey.enter,
|
|
||||||
PhysicalKeyboardKey.backspace,
|
|
||||||
PhysicalKeyboardKey.delete,
|
|
||||||
PhysicalKeyboardKey.arrowLeft,
|
|
||||||
PhysicalKeyboardKey.arrowDown,
|
|
||||||
PhysicalKeyboardKey.arrowRight,
|
|
||||||
PhysicalKeyboardKey.arrowUp,
|
|
||||||
];
|
|
||||||
|
|
||||||
return allowedKeys.contains(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _handleKeyEvent(KeyEvent keyEvent) {
|
bool _handleKeyEvent(KeyEvent keyEvent) {
|
||||||
// Logger.instance.debug(const UuidV4().generate(), "Hotkey: ${keyEvent}");
|
// Logger.instance.debug(const UuidV4().generate(), "Hotkey: ${keyEvent}");
|
||||||
if (_isFocused == false) return false;
|
if (_isFocused == false) return false;
|
||||||
if (keyEvent is KeyUpEvent) return false;
|
|
||||||
|
|
||||||
// backspace to clear hotkey
|
// backspace to clear hotkey
|
||||||
if (keyEvent.logicalKey == LogicalKeyboardKey.backspace) {
|
if (keyEvent.logicalKey == LogicalKeyboardKey.backspace) {
|
||||||
|
@ -151,20 +54,12 @@ class _WoxHotkeyRecorderState extends State<WoxHotkeyRecorder> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final physicalKeysPressed = HardwareKeyboard.instance.physicalKeysPressed;
|
var newHotkey = WoxHotkey.parseHotkeyFromEvent(keyEvent);
|
||||||
var modifiers = HotKeyModifier.values.where((e) => e.physicalKeys.any(physicalKeysPressed.contains)).toList();
|
if (newHotkey == null) {
|
||||||
PhysicalKeyboardKey? key;
|
|
||||||
physicalKeysPressed.removeWhere((element) => !isAllowedKey(element));
|
|
||||||
if (physicalKeysPressed.isNotEmpty) {
|
|
||||||
key = physicalKeysPressed.last;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modifiers.isEmpty || key == null) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newHotkey = HotKey(key: key, modifiers: modifiers, scope: HotKeyScope.system);
|
var hotkeyStr = WoxHotkey.toStr(newHotkey);
|
||||||
var hotkeyStr = getHotkeyString(newHotkey);
|
|
||||||
Logger.instance.debug(const UuidV4().generate(), "Hotkey str: $hotkeyStr");
|
Logger.instance.debug(const UuidV4().generate(), "Hotkey str: $hotkeyStr");
|
||||||
WoxApi.instance.isHotkeyAvailable(hotkeyStr).then((isAvailable) {
|
WoxApi.instance.isHotkeyAvailable(hotkeyStr).then((isAvailable) {
|
||||||
Logger.instance.debug(const UuidV4().generate(), "Hotkey available: $isAvailable");
|
Logger.instance.debug(const UuidV4().generate(), "Hotkey available: $isAvailable");
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
|
|
||||||
|
class WoxHotkeyView extends StatelessWidget {
|
||||||
|
final HotKey hotkey;
|
||||||
|
|
||||||
|
const WoxHotkeyView({super.key, required this.hotkey});
|
||||||
|
|
||||||
|
Widget buildSingleView(String key) {
|
||||||
|
return Container(
|
||||||
|
constraints: BoxConstraints.tight(const Size(24, 24)),
|
||||||
|
decoration: BoxDecoration(color: Colors.grey[200], border: Border.all(color: Colors.grey[400]!), borderRadius: BorderRadius.circular(5)),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
key,
|
||||||
|
style: const TextStyle(fontSize: 10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getModifierName(HotKeyModifier modifier) {
|
||||||
|
if (modifier == HotKeyModifier.meta) {
|
||||||
|
return "⌘";
|
||||||
|
} else if (modifier == HotKeyModifier.alt) {
|
||||||
|
return "⌥";
|
||||||
|
} else if (modifier == HotKeyModifier.control) {
|
||||||
|
return "⌃";
|
||||||
|
} else if (modifier == HotKeyModifier.shift) {
|
||||||
|
return "⇧";
|
||||||
|
}
|
||||||
|
|
||||||
|
return modifier.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getKeyName(KeyboardKey key) {
|
||||||
|
if (key == LogicalKeyboardKey.enter) {
|
||||||
|
return "⏎";
|
||||||
|
} else if (key == LogicalKeyboardKey.escape) {
|
||||||
|
return "⎋";
|
||||||
|
} else if (key == LogicalKeyboardKey.backspace) {
|
||||||
|
return "⌫";
|
||||||
|
} else if (key == LogicalKeyboardKey.delete) {
|
||||||
|
return "⌦";
|
||||||
|
} else if (key == LogicalKeyboardKey.arrowUp) {
|
||||||
|
return "↑";
|
||||||
|
} else if (key == LogicalKeyboardKey.arrowDown) {
|
||||||
|
return "↓";
|
||||||
|
} else if (key == LogicalKeyboardKey.arrowLeft) {
|
||||||
|
return "←";
|
||||||
|
} else if (key == LogicalKeyboardKey.arrowRight) {
|
||||||
|
return "→";
|
||||||
|
} else if (key == LogicalKeyboardKey.pageUp) {
|
||||||
|
return "⇞";
|
||||||
|
} else if (key == LogicalKeyboardKey.pageDown) {
|
||||||
|
return "⇟";
|
||||||
|
} else if (key == LogicalKeyboardKey.home) {
|
||||||
|
return "↖";
|
||||||
|
} else if (key == LogicalKeyboardKey.end) {
|
||||||
|
return "↘";
|
||||||
|
} else if (key == LogicalKeyboardKey.tab) {
|
||||||
|
return "⇥";
|
||||||
|
} else if (key == LogicalKeyboardKey.capsLock) {
|
||||||
|
return "⇪";
|
||||||
|
} else if (key == LogicalKeyboardKey.insert) {
|
||||||
|
return "⌅";
|
||||||
|
} else if (key == LogicalKeyboardKey.numLock) {
|
||||||
|
return "⇭";
|
||||||
|
} else if (key == LogicalKeyboardKey.scrollLock) {
|
||||||
|
return "⇳";
|
||||||
|
} else if (key == LogicalKeyboardKey.pause) {
|
||||||
|
return "⎉";
|
||||||
|
} else if (key == LogicalKeyboardKey.printScreen) {
|
||||||
|
return "⎙";
|
||||||
|
} else if (key == LogicalKeyboardKey.f1) {
|
||||||
|
return "F1";
|
||||||
|
} else if (key == LogicalKeyboardKey.f2) {
|
||||||
|
return "F2";
|
||||||
|
} else if (key == LogicalKeyboardKey.f3) {
|
||||||
|
return "F3";
|
||||||
|
} else if (key == LogicalKeyboardKey.f4) {
|
||||||
|
return "F4";
|
||||||
|
} else if (key == LogicalKeyboardKey.f5) {
|
||||||
|
return "F5";
|
||||||
|
}
|
||||||
|
|
||||||
|
return key.keyLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var hotkeyWidgets = <Widget>[];
|
||||||
|
if (hotkey.modifiers != null) {
|
||||||
|
hotkeyWidgets.addAll(hotkey.modifiers!.map((o) => buildSingleView(getModifierName(o))));
|
||||||
|
}
|
||||||
|
hotkeyWidgets.add(buildSingleView(getKeyName(hotkey.key)));
|
||||||
|
|
||||||
|
return Row(children: [
|
||||||
|
for (final widget in hotkeyWidgets)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 10.0),
|
||||||
|
child: widget,
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:from_css_color/from_css_color.dart';
|
import 'package:from_css_color/from_css_color.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
import 'package:uuid/v4.dart';
|
import 'package:uuid/v4.dart';
|
||||||
import 'package:wox/components/wox_image_view.dart';
|
import 'package:wox/components/wox_image_view.dart';
|
||||||
import 'package:wox/entity/wox_image.dart';
|
import 'package:wox/entity/wox_image.dart';
|
||||||
|
@ -12,6 +13,8 @@ import 'package:wox/enums/wox_result_tail_type_enum.dart';
|
||||||
import 'package:wox/utils/log.dart';
|
import 'package:wox/utils/log.dart';
|
||||||
import 'package:wox/utils/wox_setting_util.dart';
|
import 'package:wox/utils/wox_setting_util.dart';
|
||||||
|
|
||||||
|
import 'wox_hotkey_view.dart';
|
||||||
|
|
||||||
class WoxListItemView extends StatelessWidget {
|
class WoxListItemView extends StatelessWidget {
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
final Rx<WoxImage> icon;
|
final Rx<WoxImage> icon;
|
||||||
|
@ -46,6 +49,53 @@ class WoxListItemView extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget buildTails() {
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxWidth: WoxSettingUtil.instance.currentSetting.appWidth / 2),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 10.0, right: 5.0),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
for (final tail in tails)
|
||||||
|
if (tail.type == WoxQueryResultTailTypeEnum.WOX_QUERY_RESULT_TAIL_TYPE_TEXT.code && tail.text != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 10.0),
|
||||||
|
child: Text(
|
||||||
|
tail.text!,
|
||||||
|
style: TextStyle(
|
||||||
|
color: fromCssColor(isActive ? woxTheme.resultItemActiveTailTextColor : woxTheme.resultItemTailTextColor),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
strutStyle: const StrutStyle(
|
||||||
|
forceStrutHeight: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else if (tail.type == WoxQueryResultTailTypeEnum.WOX_QUERY_RESULT_TAIL_TYPE_HOTKEY.code && tail.hotkey != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 10.0),
|
||||||
|
child: WoxHotkeyView(hotkey: tail.hotkey!),
|
||||||
|
)
|
||||||
|
else if (tail.type == WoxQueryResultTailTypeEnum.WOX_QUERY_RESULT_TAIL_TYPE_IMAGE.code && tail.image != null && tail.image!.imageData.isNotEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 10.0),
|
||||||
|
child: WoxImageView(
|
||||||
|
woxImage: tail.image!,
|
||||||
|
width: getImageSize(tail.image!, 24),
|
||||||
|
height: getImageSize(tail.image!, 24),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (LoggerSwitch.enablePaintLog) Logger.instance.info(const UuidV4().generate(), "repaint: list item view $key - container");
|
if (LoggerSwitch.enablePaintLog) Logger.instance.info(const UuidV4().generate(), "repaint: list item view $key - container");
|
||||||
|
@ -131,47 +181,8 @@ class WoxListItemView extends StatelessWidget {
|
||||||
// Tails
|
// Tails
|
||||||
Obx(() {
|
Obx(() {
|
||||||
if (LoggerSwitch.enablePaintLog) Logger.instance.info(const UuidV4().generate(), "repaint: list item view $key - tails");
|
if (LoggerSwitch.enablePaintLog) Logger.instance.info(const UuidV4().generate(), "repaint: list item view $key - tails");
|
||||||
|
|
||||||
if (tails.isNotEmpty) {
|
if (tails.isNotEmpty) {
|
||||||
return ConstrainedBox(
|
return buildTails();
|
||||||
constraints: BoxConstraints(maxWidth: WoxSettingUtil.instance.currentSetting.appWidth / 2),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 10.0, right: 5.0),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
for (final tail in tails)
|
|
||||||
if (tail.type == WoxQueryResultTailTypeEnum.WOX_QUERY_RESULT_TAIL_TYPE_TEXT.code && tail.text.isNotEmpty)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 10.0),
|
|
||||||
child: Text(
|
|
||||||
tail.text,
|
|
||||||
style: TextStyle(
|
|
||||||
color: fromCssColor(isActive ? woxTheme.resultItemActiveTailTextColor : woxTheme.resultItemTailTextColor),
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
strutStyle: const StrutStyle(
|
|
||||||
forceStrutHeight: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else if (tail.type == WoxQueryResultTailTypeEnum.WOX_QUERY_RESULT_TAIL_TYPE_IMAGE.code && tail.image.imageData.isNotEmpty)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 10.0),
|
|
||||||
child: WoxImageView(
|
|
||||||
woxImage: tail.image,
|
|
||||||
width: getImageSize(tail.image, 24),
|
|
||||||
height: getImageSize(tail.image, 24),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
|
|
||||||
|
/// A hotkey in Wox at least consists of a modifier and a key.
|
||||||
class WoxHotkey {
|
class WoxHotkey {
|
||||||
static HotKey? parseHotkey(String value) {
|
static HotKey? parseHotkeyFromString(String value) {
|
||||||
final modifiers = <HotKeyModifier>[];
|
final modifiers = <HotKeyModifier>[];
|
||||||
LogicalKeyboardKey? key;
|
LogicalKeyboardKey? key;
|
||||||
value.split("+").forEach((element) {
|
value.split("+").forEach((element) {
|
||||||
|
@ -166,13 +169,182 @@ class WoxHotkey {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (modifiers.isEmpty || key == null) {
|
if (key == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return HotKey(
|
return HotKey(
|
||||||
key: key!,
|
key: key!,
|
||||||
modifiers: modifiers,
|
modifiers: modifiers.isEmpty ? null : modifiers,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static HotKey? parseHotkeyFromEvent(KeyEvent event) {
|
||||||
|
if (event is KeyUpEvent) return null;
|
||||||
|
|
||||||
|
if (!WoxHotkey.isAllowedKey(event.physicalKey)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<HotKeyModifier> modifiers = [];
|
||||||
|
if (HardwareKeyboard.instance.isAltPressed) {
|
||||||
|
modifiers.add(HotKeyModifier.alt);
|
||||||
|
}
|
||||||
|
if (HardwareKeyboard.instance.isControlPressed) {
|
||||||
|
modifiers.add(HotKeyModifier.control);
|
||||||
|
}
|
||||||
|
if (HardwareKeyboard.instance.isShiftPressed) {
|
||||||
|
modifiers.add(HotKeyModifier.shift);
|
||||||
|
}
|
||||||
|
if (HardwareKeyboard.instance.isMetaPressed) {
|
||||||
|
modifiers.add(HotKeyModifier.meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modifiers.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HotKey(key: event.physicalKey, modifiers: modifiers, scope: HotKeyScope.system);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isAnyModifierPressed() {
|
||||||
|
return HardwareKeyboard.instance.physicalKeysPressed.any((element) => HotKeyModifier.values.any((e) => e.physicalKeys.contains(element)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<HotKeyModifier> getPressedModifiers() {
|
||||||
|
final modifiers = <HotKeyModifier>[];
|
||||||
|
if (HardwareKeyboard.instance.isAltPressed) {
|
||||||
|
modifiers.add(HotKeyModifier.alt);
|
||||||
|
}
|
||||||
|
if (HardwareKeyboard.instance.isControlPressed) {
|
||||||
|
modifiers.add(HotKeyModifier.control);
|
||||||
|
}
|
||||||
|
if (HardwareKeyboard.instance.isShiftPressed) {
|
||||||
|
modifiers.add(HotKeyModifier.shift);
|
||||||
|
}
|
||||||
|
if (HardwareKeyboard.instance.isMetaPressed) {
|
||||||
|
modifiers.add(HotKeyModifier.meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
return modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isAllowedKey(PhysicalKeyboardKey key) {
|
||||||
|
var allowedKeys = [
|
||||||
|
PhysicalKeyboardKey.keyA,
|
||||||
|
PhysicalKeyboardKey.keyB,
|
||||||
|
PhysicalKeyboardKey.keyC,
|
||||||
|
PhysicalKeyboardKey.keyD,
|
||||||
|
PhysicalKeyboardKey.keyE,
|
||||||
|
PhysicalKeyboardKey.keyF,
|
||||||
|
PhysicalKeyboardKey.keyG,
|
||||||
|
PhysicalKeyboardKey.keyH,
|
||||||
|
PhysicalKeyboardKey.keyI,
|
||||||
|
PhysicalKeyboardKey.keyJ,
|
||||||
|
PhysicalKeyboardKey.keyK,
|
||||||
|
PhysicalKeyboardKey.keyL,
|
||||||
|
PhysicalKeyboardKey.keyM,
|
||||||
|
PhysicalKeyboardKey.keyN,
|
||||||
|
PhysicalKeyboardKey.keyO,
|
||||||
|
PhysicalKeyboardKey.keyP,
|
||||||
|
PhysicalKeyboardKey.keyQ,
|
||||||
|
PhysicalKeyboardKey.keyR,
|
||||||
|
PhysicalKeyboardKey.keyS,
|
||||||
|
PhysicalKeyboardKey.keyT,
|
||||||
|
PhysicalKeyboardKey.keyU,
|
||||||
|
PhysicalKeyboardKey.keyV,
|
||||||
|
PhysicalKeyboardKey.keyW,
|
||||||
|
PhysicalKeyboardKey.keyX,
|
||||||
|
PhysicalKeyboardKey.keyY,
|
||||||
|
PhysicalKeyboardKey.keyZ,
|
||||||
|
PhysicalKeyboardKey.digit1,
|
||||||
|
PhysicalKeyboardKey.digit2,
|
||||||
|
PhysicalKeyboardKey.digit3,
|
||||||
|
PhysicalKeyboardKey.digit4,
|
||||||
|
PhysicalKeyboardKey.digit5,
|
||||||
|
PhysicalKeyboardKey.digit6,
|
||||||
|
PhysicalKeyboardKey.digit7,
|
||||||
|
PhysicalKeyboardKey.digit8,
|
||||||
|
PhysicalKeyboardKey.digit9,
|
||||||
|
PhysicalKeyboardKey.digit0,
|
||||||
|
PhysicalKeyboardKey.space,
|
||||||
|
PhysicalKeyboardKey.enter,
|
||||||
|
PhysicalKeyboardKey.backspace,
|
||||||
|
PhysicalKeyboardKey.delete,
|
||||||
|
PhysicalKeyboardKey.arrowLeft,
|
||||||
|
PhysicalKeyboardKey.arrowDown,
|
||||||
|
PhysicalKeyboardKey.arrowRight,
|
||||||
|
PhysicalKeyboardKey.arrowUp,
|
||||||
|
];
|
||||||
|
|
||||||
|
return allowedKeys.contains(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool equals(HotKey? a, HotKey? b) {
|
||||||
|
if (a == null || b == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.key.keyLabel == b.key.keyLabel && isModifiersEquals(a.modifiers, b.modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isModifiersEquals(List<HotKeyModifier>? a, List<HotKeyModifier>? b) {
|
||||||
|
if (a == null || b == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.length != b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if all elements in a are in b
|
||||||
|
// and all elements in b are in a
|
||||||
|
return a.every((element) => b.map((o) => o.name).contains(element.name)) && b.every((element) => a.map((o) => o.name).contains(element.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
static String toStr(HotKey hotKey) {
|
||||||
|
var modifiers = [];
|
||||||
|
if (hotKey.modifiers != null) {
|
||||||
|
for (var modifier in hotKey.modifiers!) {
|
||||||
|
if (modifier == HotKeyModifier.shift) {
|
||||||
|
modifiers.add("shift");
|
||||||
|
} else if (modifier == HotKeyModifier.control) {
|
||||||
|
modifiers.add("ctrl");
|
||||||
|
} else if (modifier == HotKeyModifier.alt) {
|
||||||
|
if (Platform.isMacOS) {
|
||||||
|
modifiers.add("option");
|
||||||
|
} else {
|
||||||
|
modifiers.add("alt");
|
||||||
|
}
|
||||||
|
} else if (modifier == HotKeyModifier.meta) {
|
||||||
|
if (Platform.isMacOS) {
|
||||||
|
modifiers.add("cmd");
|
||||||
|
} else {
|
||||||
|
modifiers.add("win");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyStr = hotKey.key.keyLabel.toLowerCase();
|
||||||
|
if (hotKey.key == PhysicalKeyboardKey.space) {
|
||||||
|
keyStr = "space";
|
||||||
|
} else if (hotKey.key == PhysicalKeyboardKey.enter) {
|
||||||
|
keyStr = "enter";
|
||||||
|
} else if (hotKey.key == PhysicalKeyboardKey.backspace) {
|
||||||
|
keyStr = "backspace";
|
||||||
|
} else if (hotKey.key == PhysicalKeyboardKey.delete) {
|
||||||
|
keyStr = "delete";
|
||||||
|
} else if (hotKey.key == PhysicalKeyboardKey.arrowLeft) {
|
||||||
|
keyStr = "left";
|
||||||
|
} else if (hotKey.key == PhysicalKeyboardKey.arrowDown) {
|
||||||
|
keyStr = "down";
|
||||||
|
} else if (hotKey.key == PhysicalKeyboardKey.arrowRight) {
|
||||||
|
keyStr = "right";
|
||||||
|
} else if (hotKey.key == PhysicalKeyboardKey.arrowUp) {
|
||||||
|
keyStr = "up";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "${modifiers.join("+")}+$keyStr";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
|
import 'package:wox/entity/wox_hotkey.dart';
|
||||||
import 'package:wox/entity/wox_image.dart';
|
import 'package:wox/entity/wox_image.dart';
|
||||||
import 'package:wox/entity/wox_preview.dart';
|
import 'package:wox/entity/wox_preview.dart';
|
||||||
import 'package:wox/enums/wox_last_query_mode_enum.dart';
|
import 'package:wox/enums/wox_last_query_mode_enum.dart';
|
||||||
import 'package:wox/enums/wox_position_type_enum.dart';
|
import 'package:wox/enums/wox_position_type_enum.dart';
|
||||||
import 'package:wox/enums/wox_query_type_enum.dart';
|
import 'package:wox/enums/wox_query_type_enum.dart';
|
||||||
|
import 'package:wox/enums/wox_result_tail_type_enum.dart';
|
||||||
import 'package:wox/enums/wox_selection_type_enum.dart';
|
import 'package:wox/enums/wox_selection_type_enum.dart';
|
||||||
|
|
||||||
class PlainQuery {
|
class PlainQuery {
|
||||||
|
@ -188,24 +191,68 @@ class WoxQueryResult {
|
||||||
|
|
||||||
class WoxQueryResultTail {
|
class WoxQueryResultTail {
|
||||||
late String type;
|
late String type;
|
||||||
late String text;
|
late String? text;
|
||||||
late WoxImage image;
|
late WoxImage? image;
|
||||||
|
late HotKey? hotkey;
|
||||||
|
|
||||||
WoxQueryResultTail({required this.type, required this.text, required this.image});
|
WoxQueryResultTail({required this.type, this.text, this.image, this.hotkey});
|
||||||
|
|
||||||
WoxQueryResultTail.fromJson(Map<String, dynamic> json) {
|
WoxQueryResultTail.fromJson(Map<String, dynamic> json) {
|
||||||
type = json['Type'];
|
type = json['Type'];
|
||||||
text = json['Text'];
|
if (json['Text'] != null) {
|
||||||
image = WoxImage.fromJson(json['Image']);
|
text = json['Text'];
|
||||||
|
} else {
|
||||||
|
text = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json['Image'] != null) {
|
||||||
|
image = WoxImage.fromJson(json['Image']);
|
||||||
|
} else {
|
||||||
|
image = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json['Hotkey'] != null) {
|
||||||
|
hotkey = WoxHotkey.parseHotkeyFromString(json['Hotkey']);
|
||||||
|
} else {
|
||||||
|
hotkey = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final Map<String, dynamic> data = <String, dynamic>{};
|
final Map<String, dynamic> data = <String, dynamic>{};
|
||||||
data['Type'] = type;
|
data['Type'] = type;
|
||||||
data['Text'] = text;
|
|
||||||
data['Image'] = image.toJson();
|
if (text != null) {
|
||||||
|
data['Text'] = text;
|
||||||
|
} else {
|
||||||
|
data['Text'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image != null) {
|
||||||
|
data['Image'] = image!.toJson();
|
||||||
|
} else {
|
||||||
|
data['Image'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hotkey != null) {
|
||||||
|
data['Hotkey'] = hotkey!.toString();
|
||||||
|
} else {
|
||||||
|
data['Hotkey'] = null;
|
||||||
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
factory WoxQueryResultTail.text(String text) {
|
||||||
|
return WoxQueryResultTail(type: WoxQueryResultTailTypeEnum.WOX_QUERY_RESULT_TAIL_TYPE_TEXT.code, text: text);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory WoxQueryResultTail.hotkey(HotKey hotkey) {
|
||||||
|
return WoxQueryResultTail(type: WoxQueryResultTailTypeEnum.WOX_QUERY_RESULT_TAIL_TYPE_HOTKEY.code, hotkey: hotkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory WoxQueryResultTail.image(WoxImage image) {
|
||||||
|
return WoxQueryResultTail(type: WoxQueryResultTailTypeEnum.WOX_QUERY_RESULT_TAIL_TYPE_IMAGE.code, image: image);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class WoxResultAction {
|
class WoxResultAction {
|
||||||
|
@ -214,8 +261,9 @@ class WoxResultAction {
|
||||||
late Rx<WoxImage> icon;
|
late Rx<WoxImage> icon;
|
||||||
late bool isDefault;
|
late bool isDefault;
|
||||||
late bool preventHideAfterAction;
|
late bool preventHideAfterAction;
|
||||||
|
late String hotkey;
|
||||||
|
|
||||||
WoxResultAction({required this.id, required this.name, required this.icon, required this.isDefault, required this.preventHideAfterAction});
|
WoxResultAction({required this.id, required this.name, required this.icon, required this.isDefault, required this.preventHideAfterAction, required this.hotkey});
|
||||||
|
|
||||||
WoxResultAction.fromJson(Map<String, dynamic> json) {
|
WoxResultAction.fromJson(Map<String, dynamic> json) {
|
||||||
id = json['Id'];
|
id = json['Id'];
|
||||||
|
@ -223,6 +271,9 @@ class WoxResultAction {
|
||||||
icon = (json['Icon'] != null ? WoxImage.fromJson(json['Icon']).obs : null)!;
|
icon = (json['Icon'] != null ? WoxImage.fromJson(json['Icon']).obs : null)!;
|
||||||
isDefault = json['IsDefault'];
|
isDefault = json['IsDefault'];
|
||||||
preventHideAfterAction = json['PreventHideAfterAction'];
|
preventHideAfterAction = json['PreventHideAfterAction'];
|
||||||
|
if (json['Hotkey'] != null) {
|
||||||
|
hotkey = json['Hotkey'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
|
@ -232,11 +283,12 @@ class WoxResultAction {
|
||||||
data['Icon'] = icon.toJson();
|
data['Icon'] = icon.toJson();
|
||||||
data['IsDefault'] = isDefault;
|
data['IsDefault'] = isDefault;
|
||||||
data['PreventHideAfterAction'] = preventHideAfterAction;
|
data['PreventHideAfterAction'] = preventHideAfterAction;
|
||||||
|
data['Hotkey'] = hotkey;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static WoxResultAction empty() {
|
static WoxResultAction empty() {
|
||||||
return WoxResultAction(id: "", name: "".obs, icon: WoxImage.empty().obs, isDefault: false, preventHideAfterAction: false);
|
return WoxResultAction(id: "", name: "".obs, icon: WoxImage.empty().obs, isDefault: false, preventHideAfterAction: false, hotkey: "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,8 @@ typedef WoxQueryResultTailType = String;
|
||||||
|
|
||||||
enum WoxQueryResultTailTypeEnum {
|
enum WoxQueryResultTailTypeEnum {
|
||||||
WOX_QUERY_RESULT_TAIL_TYPE_TEXT("text", "text"),
|
WOX_QUERY_RESULT_TAIL_TYPE_TEXT("text", "text"),
|
||||||
WOX_QUERY_RESULT_TAIL_TYPE_IMAGE("image", "image");
|
WOX_QUERY_RESULT_TAIL_TYPE_IMAGE("image", "image"),
|
||||||
|
WOX_QUERY_RESULT_TAIL_TYPE_HOTKEY("hotkey", "hotkey");
|
||||||
|
|
||||||
final String code;
|
final String code;
|
||||||
final String value;
|
final String value;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:get/get.dart';
|
||||||
import 'package:uuid/v4.dart';
|
import 'package:uuid/v4.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
import 'package:wox/components/wox_image_view.dart';
|
import 'package:wox/components/wox_image_view.dart';
|
||||||
|
import 'package:wox/entity/wox_hotkey.dart';
|
||||||
import 'package:wox/modules/launcher/wox_launcher_controller.dart';
|
import 'package:wox/modules/launcher/wox_launcher_controller.dart';
|
||||||
import 'package:wox/utils/log.dart';
|
import 'package:wox/utils/log.dart';
|
||||||
|
|
||||||
|
@ -21,46 +22,63 @@ class WoxQueryBoxView extends GetView<WoxLauncherController> {
|
||||||
child: Focus(
|
child: Focus(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
onKeyEvent: (FocusNode node, KeyEvent event) {
|
onKeyEvent: (FocusNode node, KeyEvent event) {
|
||||||
if (event is KeyDownEvent) {
|
var isAnyModifierPressed = WoxHotkey.isAnyModifierPressed();
|
||||||
switch (event.logicalKey) {
|
if (!isAnyModifierPressed) {
|
||||||
case LogicalKeyboardKey.escape:
|
if (event is KeyDownEvent) {
|
||||||
controller.hideApp(const UuidV4().generate());
|
switch (event.logicalKey) {
|
||||||
return KeyEventResult.handled;
|
case LogicalKeyboardKey.escape:
|
||||||
case LogicalKeyboardKey.arrowDown:
|
controller.hideApp(const UuidV4().generate());
|
||||||
controller.handleQueryBoxArrowDown();
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
case LogicalKeyboardKey.arrowUp:
|
|
||||||
controller.handleQueryBoxArrowUp();
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
case LogicalKeyboardKey.enter:
|
|
||||||
controller.executeAction(const UuidV4().generate());
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
case LogicalKeyboardKey.tab:
|
|
||||||
controller.autoCompleteQuery(const UuidV4().generate());
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
case LogicalKeyboardKey.home:
|
|
||||||
controller.moveQueryBoxCursorToStart();
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
case LogicalKeyboardKey.end:
|
|
||||||
controller.moveQueryBoxCursorToEnd();
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
case LogicalKeyboardKey.keyJ:
|
|
||||||
if (HardwareKeyboard.instance.isMetaPressed || HardwareKeyboard.instance.isAltPressed) {
|
|
||||||
controller.toggleActionPanel(const UuidV4().generate());
|
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
case LogicalKeyboardKey.arrowDown:
|
||||||
|
controller.handleQueryBoxArrowDown();
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
case LogicalKeyboardKey.arrowUp:
|
||||||
|
controller.handleQueryBoxArrowUp();
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
case LogicalKeyboardKey.enter:
|
||||||
|
controller.executeActiveAction(const UuidV4().generate());
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
case LogicalKeyboardKey.tab:
|
||||||
|
controller.autoCompleteQuery(const UuidV4().generate());
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
case LogicalKeyboardKey.home:
|
||||||
|
controller.moveQueryBoxCursorToStart();
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
case LogicalKeyboardKey.end:
|
||||||
|
controller.moveQueryBoxCursorToEnd();
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event is KeyRepeatEvent) {
|
||||||
|
switch (event.logicalKey) {
|
||||||
|
case LogicalKeyboardKey.arrowDown:
|
||||||
|
controller.handleQueryBoxArrowDown();
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
case LogicalKeyboardKey.arrowUp:
|
||||||
|
controller.handleQueryBoxArrowUp();
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event is KeyRepeatEvent) {
|
var pressedHotkey = WoxHotkey.parseHotkeyFromEvent(event);
|
||||||
switch (event.logicalKey) {
|
if (pressedHotkey == null) {
|
||||||
case LogicalKeyboardKey.arrowDown:
|
return KeyEventResult.ignored;
|
||||||
controller.handleQueryBoxArrowDown();
|
}
|
||||||
return KeyEventResult.handled;
|
|
||||||
case LogicalKeyboardKey.arrowUp:
|
// list all actions
|
||||||
controller.handleQueryBoxArrowUp();
|
if (WoxHotkey.equals(pressedHotkey, WoxHotkey.parseHotkeyFromString("cmd+J"))) {
|
||||||
return KeyEventResult.handled;
|
controller.toggleActionPanel(const UuidV4().generate());
|
||||||
}
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the pressed hotkey is the action hotkey
|
||||||
|
var result = controller.getActiveResult();
|
||||||
|
var action = controller.getActionByHotkey(result, pressedHotkey);
|
||||||
|
if (action != null) {
|
||||||
|
controller.executeAction(const UuidV4().generate(), result, action);
|
||||||
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:get/get.dart';
|
||||||
import 'package:uuid/v4.dart';
|
import 'package:uuid/v4.dart';
|
||||||
import 'package:wox/components/wox_list_item_view.dart';
|
import 'package:wox/components/wox_list_item_view.dart';
|
||||||
import 'package:wox/components/wox_preview_view.dart';
|
import 'package:wox/components/wox_preview_view.dart';
|
||||||
|
import 'package:wox/entity/wox_hotkey.dart';
|
||||||
import 'package:wox/entity/wox_query.dart';
|
import 'package:wox/entity/wox_query.dart';
|
||||||
import 'package:wox/enums/wox_direction_enum.dart';
|
import 'package:wox/enums/wox_direction_enum.dart';
|
||||||
import 'package:wox/enums/wox_event_device_type_enum.dart';
|
import 'package:wox/enums/wox_event_device_type_enum.dart';
|
||||||
|
@ -18,6 +19,17 @@ import '../wox_launcher_controller.dart';
|
||||||
class WoxQueryResultView extends GetView<WoxLauncherController> {
|
class WoxQueryResultView extends GetView<WoxLauncherController> {
|
||||||
const WoxQueryResultView({super.key});
|
const WoxQueryResultView({super.key});
|
||||||
|
|
||||||
|
RxList<WoxQueryResultTail> getHotkeyTails(WoxResultAction action) {
|
||||||
|
var tails = <WoxQueryResultTail>[];
|
||||||
|
if (action.hotkey != "") {
|
||||||
|
var hotkey = WoxHotkey.parseHotkeyFromString(action.hotkey);
|
||||||
|
if (hotkey != null) {
|
||||||
|
tails.add(WoxQueryResultTail.hotkey(hotkey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tails.obs;
|
||||||
|
}
|
||||||
|
|
||||||
Widget getActionListView() {
|
Widget getActionListView() {
|
||||||
return Obx(() {
|
return Obx(() {
|
||||||
if (LoggerSwitch.enablePaintLog) Logger.instance.info(const UuidV4().generate(), "repaint: action list view container");
|
if (LoggerSwitch.enablePaintLog) Logger.instance.info(const UuidV4().generate(), "repaint: action list view container");
|
||||||
|
@ -40,7 +52,7 @@ class WoxQueryResultView extends GetView<WoxLauncherController> {
|
||||||
woxTheme: controller.woxTheme.value,
|
woxTheme: controller.woxTheme.value,
|
||||||
icon: woxResultAction.icon,
|
icon: woxResultAction.icon,
|
||||||
title: woxResultAction.name,
|
title: woxResultAction.name,
|
||||||
tails: RxList<WoxQueryResultTail>(),
|
tails: getHotkeyTails(woxResultAction),
|
||||||
subTitle: "".obs,
|
subTitle: "".obs,
|
||||||
isActive: controller.isActionActiveByIndex(index),
|
isActive: controller.isActionActiveByIndex(index),
|
||||||
listViewType: WoxListViewTypeEnum.WOX_LIST_VIEW_TYPE_ACTION.code,
|
listViewType: WoxListViewTypeEnum.WOX_LIST_VIEW_TYPE_ACTION.code,
|
||||||
|
@ -170,42 +182,58 @@ class WoxQueryResultView extends GetView<WoxLauncherController> {
|
||||||
Widget getActionQueryBox() {
|
Widget getActionQueryBox() {
|
||||||
return Focus(
|
return Focus(
|
||||||
onKeyEvent: (FocusNode node, KeyEvent event) {
|
onKeyEvent: (FocusNode node, KeyEvent event) {
|
||||||
if (event is KeyDownEvent) {
|
var isAnyModifierPressed = WoxHotkey.isAnyModifierPressed();
|
||||||
if (event.logicalKey == LogicalKeyboardKey.escape) {
|
if (!isAnyModifierPressed) {
|
||||||
controller.toggleActionPanel(const UuidV4().generate());
|
if (event is KeyDownEvent) {
|
||||||
return KeyEventResult.handled;
|
switch (event.logicalKey) {
|
||||||
|
case LogicalKeyboardKey.escape:
|
||||||
|
controller.toggleActionPanel(const UuidV4().generate());
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
case LogicalKeyboardKey.arrowDown:
|
||||||
|
controller.changeActionScrollPosition(
|
||||||
|
const UuidV4().generate(), WoxEventDeviceTypeEnum.WOX_EVENT_DEVEICE_TYPE_KEYBOARD.code, WoxDirectionEnum.WOX_DIRECTION_DOWN.code);
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
case LogicalKeyboardKey.arrowUp:
|
||||||
|
controller.changeActionScrollPosition(
|
||||||
|
const UuidV4().generate(), WoxEventDeviceTypeEnum.WOX_EVENT_DEVEICE_TYPE_KEYBOARD.code, WoxDirectionEnum.WOX_DIRECTION_UP.code);
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
case LogicalKeyboardKey.enter:
|
||||||
|
controller.executeActiveAction(const UuidV4().generate());
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
|
||||||
controller.changeActionScrollPosition(
|
if (event is KeyRepeatEvent) {
|
||||||
const UuidV4().generate(), WoxEventDeviceTypeEnum.WOX_EVENT_DEVEICE_TYPE_KEYBOARD.code, WoxDirectionEnum.WOX_DIRECTION_DOWN.code);
|
switch (event.logicalKey) {
|
||||||
return KeyEventResult.handled;
|
case LogicalKeyboardKey.arrowDown:
|
||||||
}
|
controller.changeActionScrollPosition(
|
||||||
if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
const UuidV4().generate(), WoxEventDeviceTypeEnum.WOX_EVENT_DEVEICE_TYPE_KEYBOARD.code, WoxDirectionEnum.WOX_DIRECTION_DOWN.code);
|
||||||
controller.changeActionScrollPosition(
|
return KeyEventResult.handled;
|
||||||
const UuidV4().generate(), WoxEventDeviceTypeEnum.WOX_EVENT_DEVEICE_TYPE_KEYBOARD.code, WoxDirectionEnum.WOX_DIRECTION_UP.code);
|
case LogicalKeyboardKey.arrowUp:
|
||||||
return KeyEventResult.handled;
|
controller.changeActionScrollPosition(
|
||||||
}
|
const UuidV4().generate(), WoxEventDeviceTypeEnum.WOX_EVENT_DEVEICE_TYPE_KEYBOARD.code, WoxDirectionEnum.WOX_DIRECTION_UP.code);
|
||||||
if (event.logicalKey == LogicalKeyboardKey.enter) {
|
return KeyEventResult.handled;
|
||||||
controller.executeAction(const UuidV4().generate());
|
}
|
||||||
return KeyEventResult.handled;
|
|
||||||
}
|
|
||||||
if ((HardwareKeyboard.instance.isMetaPressed || HardwareKeyboard.instance.isAltPressed) && event.logicalKey == LogicalKeyboardKey.keyJ) {
|
|
||||||
controller.toggleActionPanel(const UuidV4().generate());
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event is KeyRepeatEvent) {
|
var pressedHotkey = WoxHotkey.parseHotkeyFromEvent(event);
|
||||||
if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
if (pressedHotkey == null) {
|
||||||
controller.changeActionScrollPosition(
|
return KeyEventResult.ignored;
|
||||||
const UuidV4().generate(), WoxEventDeviceTypeEnum.WOX_EVENT_DEVEICE_TYPE_KEYBOARD.code, WoxDirectionEnum.WOX_DIRECTION_DOWN.code);
|
}
|
||||||
return KeyEventResult.handled;
|
|
||||||
}
|
// list all actions
|
||||||
if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
if (WoxHotkey.equals(pressedHotkey, WoxHotkey.parseHotkeyFromString("cmd+J"))) {
|
||||||
controller.changeActionScrollPosition(
|
controller.toggleActionPanel(const UuidV4().generate());
|
||||||
const UuidV4().generate(), WoxEventDeviceTypeEnum.WOX_EVENT_DEVEICE_TYPE_KEYBOARD.code, WoxDirectionEnum.WOX_DIRECTION_UP.code);
|
return KeyEventResult.handled;
|
||||||
return KeyEventResult.handled;
|
}
|
||||||
}
|
|
||||||
|
// check if the pressed hotkey is the action hotkey
|
||||||
|
var result = controller.getActiveResult();
|
||||||
|
var action = controller.getActionByHotkey(result, pressedHotkey);
|
||||||
|
if (action != null) {
|
||||||
|
controller.executeAction(const UuidV4().generate(), result, action);
|
||||||
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:from_css_color/from_css_color.dart';
|
import 'package:from_css_color/from_css_color.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
import 'package:wox/components/wox_hotkey_view.dart';
|
||||||
import 'package:wox/components/wox_image_view.dart';
|
import 'package:wox/entity/wox_hotkey.dart';
|
||||||
import 'package:wox/entity/wox_image.dart';
|
|
||||||
import 'package:wox/enums/wox_image_type_enum.dart';
|
|
||||||
import 'package:wox/modules/launcher/wox_launcher_controller.dart';
|
import 'package:wox/modules/launcher/wox_launcher_controller.dart';
|
||||||
import 'package:wox/utils/wox_theme_util.dart';
|
import 'package:wox/utils/wox_theme_util.dart';
|
||||||
|
|
||||||
|
@ -22,10 +19,11 @@ class WoxQueryToolbarView extends GetView<WoxLauncherController> {
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hotkey = WoxHotkey.parseHotkeyFromString(action.hotkey) ?? WoxHotkey.parseHotkeyFromString("enter");
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
WoxImageView(woxImage: WoxImage(imageType: WoxImageTypeEnum.WOX_IMAGE_TYPE_EMOJI.code, imageData: "↩️")),
|
|
||||||
Text(action.name.value, style: TextStyle(color: fromCssColor(controller.woxTheme.value.toolbarFontColor))),
|
Text(action.name.value, style: TextStyle(color: fromCssColor(controller.woxTheme.value.toolbarFontColor))),
|
||||||
|
WoxHotkeyView(hotkey: hotkey!),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,12 @@ import 'dart:ui';
|
||||||
import 'package:desktop_drop/desktop_drop.dart';
|
import 'package:desktop_drop/desktop_drop.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
import 'package:lpinyin/lpinyin.dart';
|
import 'package:lpinyin/lpinyin.dart';
|
||||||
import 'package:uuid/v4.dart';
|
import 'package:uuid/v4.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
import 'package:wox/api/wox_api.dart';
|
import 'package:wox/api/wox_api.dart';
|
||||||
|
import 'package:wox/entity/wox_hotkey.dart';
|
||||||
import 'package:wox/entity/wox_image.dart';
|
import 'package:wox/entity/wox_image.dart';
|
||||||
import 'package:wox/entity/wox_preview.dart';
|
import 'package:wox/entity/wox_preview.dart';
|
||||||
import 'package:wox/entity/wox_query.dart';
|
import 'package:wox/entity/wox_query.dart';
|
||||||
|
@ -236,17 +238,40 @@ class WoxLauncherController extends GetxController {
|
||||||
return actions[activeActionIndex.value];
|
return actions[activeActionIndex.value];
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> executeAction(String traceId) async {
|
/// given a hotkey, find the action in the result
|
||||||
Logger.instance.debug(traceId, "user execute result action");
|
WoxResultAction? getActionByHotkey(WoxQueryResult? result, HotKey hotkey) {
|
||||||
|
if (result == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
WoxQueryResult? woxQueryResult = getActiveResult();
|
var filteredActions = result.actions.where((action) {
|
||||||
if (woxQueryResult == null) {
|
var actionHotkey = WoxHotkey.parseHotkeyFromString(action.hotkey);
|
||||||
|
if (WoxHotkey.equals(actionHotkey, hotkey)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filteredActions.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredActions.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> executeActiveAction(String traceId) async {
|
||||||
|
executeAction(traceId, getActiveResult(), getActiveAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> executeAction(String traceId, WoxQueryResult? result, WoxResultAction? action) async {
|
||||||
|
Logger.instance.debug(traceId, "user execute result action: ${action?.name}");
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
Logger.instance.error(traceId, "active query result is null");
|
Logger.instance.error(traceId, "active query result is null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (action == null) {
|
||||||
WoxResultAction? activeAction = getActiveAction();
|
|
||||||
if (activeAction == null) {
|
|
||||||
Logger.instance.error(traceId, "active action is null");
|
Logger.instance.error(traceId, "active action is null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -257,12 +282,12 @@ class WoxLauncherController extends GetxController {
|
||||||
type: WoxMsgTypeEnum.WOX_MSG_TYPE_REQUEST.code,
|
type: WoxMsgTypeEnum.WOX_MSG_TYPE_REQUEST.code,
|
||||||
method: WoxMsgMethodEnum.WOX_MSG_METHOD_ACTION.code,
|
method: WoxMsgMethodEnum.WOX_MSG_METHOD_ACTION.code,
|
||||||
data: {
|
data: {
|
||||||
"resultId": woxQueryResult.id,
|
"resultId": result.id,
|
||||||
"actionId": activeAction.id,
|
"actionId": action.id,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
if (!activeAction.preventHideAfterAction) {
|
if (!action.preventHideAfterAction) {
|
||||||
hideApp(traceId);
|
hideApp(traceId);
|
||||||
}
|
}
|
||||||
hideActionPanel();
|
hideActionPanel();
|
||||||
|
|
|
@ -72,7 +72,7 @@ class WoxSettingGeneralView extends GetView<WoxSettingController> {
|
||||||
label: controller.tr("hotkey"),
|
label: controller.tr("hotkey"),
|
||||||
tips: controller.tr("hotkey_tips"),
|
tips: controller.tr("hotkey_tips"),
|
||||||
child: WoxHotkeyRecorder(
|
child: WoxHotkeyRecorder(
|
||||||
hotkey: WoxHotkey.parseHotkey(controller.woxSetting.value.mainHotkey),
|
hotkey: WoxHotkey.parseHotkeyFromString(controller.woxSetting.value.mainHotkey),
|
||||||
onHotKeyRecorded: (hotkey) {
|
onHotKeyRecorded: (hotkey) {
|
||||||
controller.updateConfig("MainHotkey", hotkey);
|
controller.updateConfig("MainHotkey", hotkey);
|
||||||
},
|
},
|
||||||
|
@ -82,7 +82,7 @@ class WoxSettingGeneralView extends GetView<WoxSettingController> {
|
||||||
label: controller.tr("selection_hotkey"),
|
label: controller.tr("selection_hotkey"),
|
||||||
tips: controller.tr("selection_hotkey_tips"),
|
tips: controller.tr("selection_hotkey_tips"),
|
||||||
child: WoxHotkeyRecorder(
|
child: WoxHotkeyRecorder(
|
||||||
hotkey: WoxHotkey.parseHotkey(controller.woxSetting.value.selectionHotkey),
|
hotkey: WoxHotkey.parseHotkeyFromString(controller.woxSetting.value.selectionHotkey),
|
||||||
onHotKeyRecorded: (hotkey) {
|
onHotKeyRecorded: (hotkey) {
|
||||||
controller.updateConfig("SelectionHotkey", hotkey);
|
controller.updateConfig("SelectionHotkey", hotkey);
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.1.3 <4.0.0'
|
sdk: '>=3.4.0 <4.0.0'
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
# Dependencies specify other packages that your package needs in order to work.
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
# To automatically upgrade your package dependencies to the latest versions
|
||||||
|
|
|
@ -503,6 +503,7 @@ func (m *Manager) PolishResult(ctx context.Context, pluginInstance *Instance, qu
|
||||||
})
|
})
|
||||||
if defaultActionCount == 0 && len(result.Actions) > 0 {
|
if defaultActionCount == 0 && len(result.Actions) > 0 {
|
||||||
result.Actions[0].IsDefault = true
|
result.Actions[0].IsDefault = true
|
||||||
|
result.Actions[0].Hotkey = "Enter"
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultCache = &QueryResultCache{
|
var resultCache = &QueryResultCache{
|
||||||
|
@ -516,8 +517,26 @@ func (m *Manager) PolishResult(ctx context.Context, pluginInstance *Instance, qu
|
||||||
}
|
}
|
||||||
|
|
||||||
// store actions for ui invoke later
|
// store actions for ui invoke later
|
||||||
for actionId := range result.Actions {
|
for actionIndex := range result.Actions {
|
||||||
var action = result.Actions[actionId]
|
var action = result.Actions[actionIndex]
|
||||||
|
|
||||||
|
// if default action's hotkey is empty, set it as Enter
|
||||||
|
if action.IsDefault && action.Hotkey == "" {
|
||||||
|
result.Actions[actionIndex].Hotkey = "Enter"
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace hotkey modifiers for platform specific, E.g. replace win to cmd on macos, replace cmd to win on windows
|
||||||
|
if util.IsMacOS() {
|
||||||
|
result.Actions[actionIndex].Hotkey = strings.ReplaceAll(result.Actions[actionIndex].Hotkey, "win", "cmd")
|
||||||
|
result.Actions[actionIndex].Hotkey = strings.ReplaceAll(result.Actions[actionIndex].Hotkey, "windows", "cmd")
|
||||||
|
result.Actions[actionIndex].Hotkey = strings.ReplaceAll(result.Actions[actionIndex].Hotkey, "alt", "option")
|
||||||
|
}
|
||||||
|
if util.IsWindows() || util.IsLinux() {
|
||||||
|
result.Actions[actionIndex].Hotkey = strings.ReplaceAll(result.Actions[actionIndex].Hotkey, "cmd", "win")
|
||||||
|
result.Actions[actionIndex].Hotkey = strings.ReplaceAll(result.Actions[actionIndex].Hotkey, "command", "win")
|
||||||
|
result.Actions[actionIndex].Hotkey = strings.ReplaceAll(result.Actions[actionIndex].Hotkey, "option", "alt")
|
||||||
|
}
|
||||||
|
|
||||||
if action.Action != nil {
|
if action.Action != nil {
|
||||||
resultCache.Actions.Store(action.Id, action.Action)
|
resultCache.Actions.Store(action.Id, action.Action)
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,6 +132,10 @@ type QueryResultAction struct {
|
||||||
// If true, Wox will not hide after user select this result
|
// If true, Wox will not hide after user select this result
|
||||||
PreventHideAfterAction bool
|
PreventHideAfterAction bool
|
||||||
Action func(ctx context.Context, actionContext ActionContext)
|
Action func(ctx context.Context, actionContext ActionContext)
|
||||||
|
// Hotkey to trigger this action. E.g. "ctrl+Shift+Space", "Ctrl+1", "Command+K"
|
||||||
|
// Case insensitive, space insensitive
|
||||||
|
// If IsDefault is true, Hotkey will be set to enter key by default
|
||||||
|
Hotkey string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActionContext struct {
|
type ActionContext struct {
|
||||||
|
@ -158,6 +162,7 @@ func (q *QueryResult) ToUI() QueryResultUI {
|
||||||
Icon: action.Icon,
|
Icon: action.Icon,
|
||||||
IsDefault: action.IsDefault,
|
IsDefault: action.IsDefault,
|
||||||
PreventHideAfterAction: action.PreventHideAfterAction,
|
PreventHideAfterAction: action.PreventHideAfterAction,
|
||||||
|
Hotkey: action.Hotkey,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
RefreshInterval: q.RefreshInterval,
|
RefreshInterval: q.RefreshInterval,
|
||||||
|
@ -186,6 +191,7 @@ type QueryResultActionUI struct {
|
||||||
Icon WoxImage
|
Icon WoxImage
|
||||||
IsDefault bool
|
IsDefault bool
|
||||||
PreventHideAfterAction bool
|
PreventHideAfterAction bool
|
||||||
|
Hotkey string
|
||||||
}
|
}
|
||||||
|
|
||||||
// store latest result value after query/refresh, so we can retrieve data later in action/refresh
|
// store latest result value after query/refresh, so we can retrieve data later in action/refresh
|
||||||
|
|
Loading…
Reference in New Issue