补全屏幕截图、屏幕取色、获取显示器信息、剪贴板操作

This commit is contained in:
fofolee 2025-01-09 22:43:33 +08:00
parent 682f6d0bcd
commit 378ae7f92f
10 changed files with 420 additions and 83 deletions

View File

@ -1,10 +1,13 @@
const { findImage } = require("./imageFinder"); const { findImage } = require("./imageFinder");
const { captureScreen } = require("./screenCapture");
const { keyboardTap, keySequence } = require("./keyboardTap"); const { keyboardTap, keySequence } = require("./keyboardTap");
const { screenColorPick } = require("./screenColorPick");
const screenCapture = require("./screenCapture");
module.exports = { module.exports = {
findImage, findImage,
captureScreen, screenCapture,
keyboardTap, keyboardTap,
keySequence, keySequence,
screenColorPick,
...screenCapture,
}; };

View File

@ -187,7 +187,7 @@ async function captureLinuxScreen() {
} }
// 统一的截图接口 // 统一的截图接口
async function captureScreen() { async function captureFullScreen() {
try { try {
if (process.platform === "darwin") { if (process.platform === "darwin") {
return await captureMacScreen(); return await captureMacScreen();
@ -202,4 +202,41 @@ async function captureScreen() {
return null; return null;
} }
module.exports = { captureScreen }; function captureAreaScreen() {
return new Promise((resolve) => {
window.utools.screenCapture((data) => {
resolve(data);
});
});
}
async function captureScreen(range = "fullscreen") {
return range === "fullscreen"
? await captureFullScreen()
: await captureAreaScreen();
}
async function captureScreenToFile(range = "fullscreen", path = null) {
if (!path) return null;
const result = await captureScreen(range);
if (!result) return null;
fs.writeFileSync(
path,
result.replace("data:image/png;base64,", ""),
"base64"
);
return result;
}
async function captureScreenToClipboard(range = "fullscreen") {
const result = await captureScreen(range);
if (!result) return null;
window.utools.copyImage(result);
return result;
}
module.exports = {
captureScreen,
captureScreenToFile,
captureScreenToClipboard,
};

View File

@ -0,0 +1,11 @@
const screenColorPick = () => {
return new Promise((resolve) => {
utools.screenColorPick((color) => {
resolve(color);
});
});
};
module.exports = {
screenColorPick,
};

View File

@ -41,16 +41,16 @@ export default defineComponent({
// , excludeConfig[] // , excludeConfig[]
this.modelValue.config?.filter( this.modelValue.config?.filter(
(_, index) => (_, index) =>
!this.getSelectFunction()?.excludeConfig?.includes(index) !this.getSelectSubCommand()?.excludeConfig?.includes(index)
) || [] ) || []
); );
}, },
// configconfig // configconfig
functionConfig() { subCommandConfig() {
return this.getSelectFunction()?.config || []; return this.getSelectSubCommand()?.config || [];
}, },
localConfig() { localConfig() {
return [...this.commonConfig, ...this.functionConfig].map((item) => { return [...this.commonConfig, ...this.subCommandConfig].map((item) => {
const value = const value =
item.type === "varInput" item.type === "varInput"
? item.defaultValue || newVarInputVal("str") ? item.defaultValue || newVarInputVal("str")
@ -79,7 +79,7 @@ export default defineComponent({
}); });
// 使 // 使
this.getSelectFunction(value)?.config?.forEach((config, index) => { this.getSelectSubCommand(value)?.config?.forEach((config, index) => {
newArgvs[this.commonConfig.length + index] = config.defaultValue; newArgvs[this.commonConfig.length + index] = config.defaultValue;
}); });
@ -96,7 +96,7 @@ export default defineComponent({
}, },
}, },
methods: { methods: {
getSelectFunction(funcName = this.funcName) { getSelectSubCommand(funcName = this.funcName) {
return this.modelValue.subCommands?.find( return this.modelValue.subCommands?.find(
(item) => item.value === funcName (item) => item.value === funcName
); );
@ -165,7 +165,7 @@ export default defineComponent({
}, },
getSummary(argvs) { getSummary(argvs) {
// header // header
const funcNameLabel = this.getSelectFunction()?.label; const funcNameLabel = this.getSelectSubCommand()?.label;
const subFeature = funcNameLabel ? `${funcNameLabel} ` : ""; const subFeature = funcNameLabel ? `${funcNameLabel} ` : "";
const allArgvs = argvs const allArgvs = argvs
.filter((item) => item != null && item != "") .filter((item) => item != null && item != "")

View File

@ -82,7 +82,10 @@
" "
/> />
</div> </div>
<div v-if="item.type !== 'fixed'" class="col-auto"> <div
v-if="item.type !== 'fixed' && !options?.disableAdd"
class="col-auto"
>
<div class="btn-container"> <div class="btn-container">
<template v-if="editableItems.length === 1"> <template v-if="editableItems.length === 1">
<q-btn <q-btn
@ -145,6 +148,7 @@ import BorderLabel from "components/composer/common/BorderLabel.vue";
* @property {Object} options - 配置选项 * @property {Object} options - 配置选项
* @property {String[]|Object[]} [options.optionKeys] - 可选键名 * @property {String[]|Object[]} [options.optionKeys] - 可选键名
* @property {String[]|Object[]} [options.fixedKeys] - 固定键名 * @property {String[]|Object[]} [options.fixedKeys] - 固定键名
* @property {Boolean} [options.disableAdd] - 禁止添加新的键值对
* *
* @example * @example
* // * //
@ -195,30 +199,7 @@ export default defineComponent({
}, },
emits: ["update:modelValue"], emits: ["update:modelValue"],
data() { data() {
const modelEntries = Object.entries(this.modelValue || {});
const fixedKeys = this.normalizeKeys(this.options?.fixedKeys || []);
const fixedKeyValues = fixedKeys.map((key) => ({
type: "fixed",
key: key.value,
value:
modelEntries.find(([k]) => k === key.value)?.[1] ||
newVarInputVal("str"),
}));
const editableEntries = modelEntries.filter(
([key]) => !fixedKeys.some((k) => k.value === key)
);
return { return {
fixedItems: fixedKeyValues,
localItems: editableEntries.length
? editableEntries.map(([key, value]) => ({
type: "editable",
key,
value,
}))
: [{ type: "editable", key: "", value: newVarInputVal("str") }],
filterOptions: this.normalizeKeys(this.options?.optionKeys || []),
inputValue: "", inputValue: "",
}; };
}, },
@ -232,11 +213,49 @@ export default defineComponent({
this.updateModelValue(); this.updateModelValue();
}, },
}, },
normalizedOptionKeys() {
return this.filterOptions;
},
//
normalizedFixedKeys() {
return this.normalizeKeys(this.options?.fixedKeys || []);
},
// modelValue
modelEntries() {
return Object.entries(this.modelValue || {});
},
//
fixedItems() {
return this.normalizedFixedKeys.map((key) => ({
type: "fixed",
key: key.value,
value:
this.modelEntries.find(([k]) => k === key.value)?.[1] ||
newVarInputVal("str"),
}));
},
//
localItems() {
//
const editableEntries = this.modelEntries.filter(
([key]) => !this.normalizedFixedKeys.some((k) => k.value === key)
);
return editableEntries.length || this.options?.disableAdd
? editableEntries.map(([key, value]) => ({
type: "editable",
key,
value,
}))
: [{ type: "editable", key: "", value: newVarInputVal("str") }];
},
//
allItems() { allItems() {
return [...this.fixedItems, ...this.localItems]; return [...this.fixedItems, ...this.localItems];
}, },
normalizedOptionKeys() { //
return this.filterOptions; filterOptions() {
return this.normalizeKeys(this.options?.optionKeys || []);
}, },
}, },
methods: { methods: {
@ -251,10 +270,7 @@ export default defineComponent({
getKeyLabel(key) { getKeyLabel(key) {
if (typeof key === "object") return key.label; if (typeof key === "object") return key.label;
const allKeys = [ const allKeys = [...this.normalizedFixedKeys, ...this.filterOptions];
...this.normalizeKeys(this.options?.fixedKeys || []),
...this.normalizeKeys(this.options?.optionKeys || []),
];
return allKeys.find((k) => k.value === key)?.label || key; return allKeys.find((k) => k.value === key)?.label || key;
}, },
@ -280,62 +296,57 @@ export default defineComponent({
}, },
updateItemValue(val, index, isFixed = false) { updateItemValue(val, index, isFixed = false) {
if (isFixed) { const dict = { ...this.modelValue };
const newItems = [...this.fixedItems]; const item = this.allItems[index];
newItems[index].value = val; if (item.key) {
this.fixedItems = newItems; dict[item.key] = val;
} else { this.$emit("update:modelValue", dict);
const newItems = [...this.localItems];
newItems[index].value = val;
this.localItems = newItems;
} }
this.updateModelValue();
}, },
addItem() { addItem() {
this.localItems = [ if (this.options?.disableAdd) return;
...this.localItems, const dict = { ...this.modelValue };
{ dict[""] = newVarInputVal("str");
key: "", this.$emit("update:modelValue", dict);
value: newVarInputVal("str"),
},
];
}, },
removeItem(index) { removeItem(index) {
const newItems = [...this.localItems]; if (this.options?.disableAdd) return;
newItems.splice(index, 1); const dict = { ...this.modelValue };
if (newItems.length === 0) { const item = this.localItems[index];
newItems.push({ if (item.key) {
key: "", delete dict[item.key];
value: newVarInputVal("str"), this.$emit("update:modelValue", dict);
});
} }
this.localItems = newItems;
}, },
updateItemKey(val, index) { updateItemKey(val, index) {
const newItems = [...this.localItems]; const dict = { ...this.modelValue };
newItems[index].key = val; const oldItem = this.localItems[index];
this.localItems = newItems; if (oldItem.key) {
delete dict[oldItem.key];
}
dict[val] = oldItem.value;
this.$emit("update:modelValue", dict);
}, },
handleInput(val, index) { handleInput(val, index) {
this.inputValue = val; this.inputValue = val;
if (val) { if (val) {
const newItems = [...this.localItems]; this.updateItemKey(val, index);
newItems[index].key = val;
this.localItems = newItems;
this.updateModelValue();
} }
}, },
handleSelect(val, index) { handleSelect(val, index) {
this.inputValue = ""; this.inputValue = "";
const newItems = [...this.localItems]; this.updateItemKey(val.value || val, index);
newItems[index].key = val.value || val;
this.localItems = newItems;
this.updateModelValue();
}, },
handleBlur() { handleBlur() {
this.inputValue = ""; this.inputValue = "";
}, },
filterFn(val, update) { filterFn(val, update) {
if (!this.options?.optionKeys) return; if (!this.options?.optionKeys) return;

View File

@ -11,6 +11,7 @@ import { codingCommands } from "./codingCommand";
import { mathCommands } from "./mathCommands"; import { mathCommands } from "./mathCommands";
import { userdataCommands } from "./userdataCommands"; import { userdataCommands } from "./userdataCommands";
import { utoolsCommands } from "./utoolsCommand"; import { utoolsCommands } from "./utoolsCommand";
import { screenCommands } from "./screenCommands";
export const commandCategories = [ export const commandCategories = [
fileCommands, fileCommands,
@ -25,5 +26,6 @@ export const commandCategories = [
simulateCommands, simulateCommands,
mathCommands, mathCommands,
userdataCommands, userdataCommands,
screenCommands,
otherCommands, otherCommands,
]; ];

View File

@ -0,0 +1,126 @@
import { newVarInputVal } from "js/composer/varInputValManager";
const XY_DICT_EDITOR = {
label: "坐标",
type: "dictEditor",
icon: "transform",
isCollapse: false,
width: 12,
defaultValue: {
x: newVarInputVal("var", "0"),
y: newVarInputVal("var", "0"),
},
options: {
fixedKeys: [
{ value: "x", label: "X坐标" },
{ value: "y", label: "Y坐标" },
],
disableAdd: true,
},
};
const RECT_DICT_EDITOR = {
label: "区域",
type: "dictEditor",
icon: "transform",
isCollapse: false,
width: 12,
defaultValue: {
x: newVarInputVal("var", "0"),
y: newVarInputVal("var", "0"),
width: newVarInputVal("var", "100"),
height: newVarInputVal("var", "100"),
},
options: {
fixedKeys: [
{ value: "x", label: "X坐标" },
{ value: "y", label: "Y坐标" },
{ value: "width", label: "宽度" },
{ value: "height", label: "高度" },
],
disableAdd: true,
},
};
export const screenCommands = {
label: "显示器",
icon: "screenshot_monitor",
commands: [
{
value: "utools.getPrimaryDisplay",
label: "获取显示器信息",
desc: "获取显示器信息",
icon: "monitor",
outputVariable: "display",
saveOutput: true,
subCommands: [
{
value: "utools.getPrimaryDisplay",
label: "获取主显示器",
icon: "monitor",
},
{
value: "utools.getAllDisplays",
label: "获取所有显示器",
icon: "desktop_windows",
},
{
value: "utools.getDisplayNearestPoint",
label: "获取位置所在显示器",
icon: "gps_fixed",
config: [XY_DICT_EDITOR],
},
{
value: "utools.getDisplayMatching",
label: "获取矩形所在显示器",
icon: "crop_square",
config: [RECT_DICT_EDITOR],
},
],
},
{
value: "utools.screenToDipPoint",
label: "物理/DIP坐标转换",
desc: "屏幕物理坐标和 DIP 坐标转换",
icon: "transform",
outputVariable: "{x,y}",
saveOutput: true,
config: [XY_DICT_EDITOR],
subCommands: [
{
value: "utools.screenToDipPoint",
label: "物理坐标转DIP坐标",
icon: "transform",
},
{
value: "utools.dipToScreenPoint",
label: "DIP坐标转物理坐标",
icon: "transform",
},
],
},
{
value: "utools.screenToDipRect",
label: "物理/DIP区域转换",
desc: "屏幕物理区域和 DIP 区域转换",
icon: "transform",
outputVariable: "{x,y,width,height}",
saveOutput: true,
config: [RECT_DICT_EDITOR],
subCommands: [
{
value: "utools.screenToDipRect",
label: "物理区域转DIP区域",
desc: "屏幕物理区域转 DIP 区域",
icon: "transform",
},
{
value: "utools.dipToScreenRect",
label: "DIP区域转物理区域",
desc: "DIP 区域转屏幕物理区域",
icon: "transform",
},
],
},
],
};

View File

@ -1,3 +1,5 @@
import { newVarInputVal } from "js/composer/varInputValManager";
export const simulateCommands = { export const simulateCommands = {
label: "模拟操作", label: "模拟操作",
icon: "ads_click", icon: "ads_click",
@ -185,5 +187,89 @@ export const simulateCommands = {
config: [], config: [],
isAsync: true, isAsync: true,
}, },
{
value: "quickcomposer.simulate.screenColorPick",
label: "屏幕取色",
desc: "获取用户选择的颜色,会弹出一个系统取色器",
icon: "colorize",
isAsync: true,
outputVariable: "{hex,rgb}",
saveOutput: true,
},
{
value: "quickcomposer.simulate.captureScreen",
label: "屏幕截图",
desc: "屏幕截图,进行区域截图或全屏截图",
icon: "screenshot_monitor",
isAsync: true,
outputVariable: "base64Data",
saveOutput: true,
config: [
{
key: "range",
label: "截图范围",
type: "buttonGroup",
options: [
{
label: "全屏截图",
value: "fullscreen",
},
{
label: "区域截图",
value: "area",
},
],
defaultValue: "fullscreen",
width: 12,
},
],
subCommands: [
{
label: "保存到dataUrl",
value: "quickcomposer.simulate.captureScreen",
icon: "link",
},
{
label: "保存到文件",
value: "quickcomposer.simulate.captureScreenToFile",
icon: "file_copy",
config: [
{
key: "path",
label: "截图保存路径",
type: "varInput",
defaultValue: newVarInputVal(
"str",
`${window.utools.getPath("desktop")}${
utools.isWindows() ? "\\" : "/"
}quickcommand_screenshot.png`
),
options: {
dialog: {
type: "save",
options: {
title: "选择保存路径",
properties: ["openFile", "showHiddenFiles"],
filters: [
{
name: "PNG",
extensions: ["png"],
},
],
},
},
},
icon: "description",
width: 12,
},
],
},
{
label: "复制到剪贴板",
value: "quickcomposer.simulate.captureScreenToClipboard",
icon: "content_copy",
},
],
},
], ],
}; };

View File

@ -6,20 +6,75 @@ export const systemCommands = {
defaultOpened: false, defaultOpened: false,
commands: [ commands: [
{ {
value: "electron.clipboard.writeText", value: "utools.copyText",
label: "将内容写入剪贴板", label: "写入剪贴板",
config: [ subCommands: [
{ {
key: "content", value: "utools.copyText",
label: "要写入剪切板的内容", label: "写入文本",
type: "varInput",
icon: "content_copy", icon: "content_copy",
config: [
{
key: "content",
label: "要写入剪切板的内容",
type: "varInput",
icon: "content_copy",
},
],
},
{
value: "utools.copyImage",
label: "写入图片",
icon: "image",
config: [
{
key: "image",
label: "图片路径/base64",
type: "varInput",
icon: "image",
options: {
dialog: {
type: "open",
options: {
title: "选择图片",
properties: ["openFile", "showHiddenFiles"],
},
},
},
},
],
},
{
value: "utools.copyFile",
label: "写入文件",
icon: "file_copy",
config: [
{
key: "file",
label: "文件路径",
type: "varInput",
icon: "file_copy",
options: {
dialog: {
type: "open",
options: {
title: "选择文件",
properties: [
"openFile",
"showHiddenFiles",
"multiSelections",
],
},
},
},
},
],
}, },
], ],
}, },
{ {
value: "electron.clipboard.readText", value: "electron.clipboard.readText",
label: "获取剪贴板内容", label: "读取剪贴板",
outputVariable: "clipboardContent", outputVariable: "clipboardContent",
saveOutput: true, saveOutput: true,
subCommands: [ subCommands: [
@ -33,6 +88,11 @@ export const systemCommands = {
label: "剪贴板图片", label: "剪贴板图片",
icon: "image", icon: "image",
}, },
{
value: "utools.getCopyedFiles",
label: "剪贴板文件",
icon: "file_copy",
},
{ {
value: "electron.clipboard.readRTF", value: "electron.clipboard.readRTF",
label: "剪贴板RTF", label: "剪贴板RTF",

View File

@ -110,6 +110,7 @@ export const utoolsCommands = {
type: "dictEditor", type: "dictEditor",
icon: "settings", icon: "settings",
options: { options: {
disableAdd: true,
fixedKeys: [ fixedKeys: [
{ {
value: "forward", value: "forward",