Merge c1a115bd1e
into 5167e0c084
4
.gitattributes
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/.yarn/** linguist-vendored
|
||||
/.yarn/releases/* binary
|
||||
/.yarn/plugins/**/* binary
|
||||
/.pnp.* binary linguist-generated
|
11
.gitignore
vendored
@ -8,11 +8,12 @@ pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
dist-electron
|
||||
native
|
||||
release
|
||||
.yarn
|
||||
**/dist
|
||||
**/dist-ssr
|
||||
**/dist-electron
|
||||
**/native
|
||||
**/release
|
||||
*.local
|
||||
.env
|
||||
|
||||
|
893
.yarn/releases/yarn-4.0.2.cjs
vendored
Normal file
9
.yarnrc.yml
Normal file
@ -0,0 +1,9 @@
|
||||
compressionLevel: mixed
|
||||
|
||||
enableGlobalCache: true
|
||||
|
||||
nmMode: hardlinks-local
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.0.2.cjs
|
139
Cargo.lock
generated
@ -2,6 +2,22 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "addon"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"clipboard-win",
|
||||
"image",
|
||||
"napi",
|
||||
"napi-build",
|
||||
"napi-derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"static_vcruntime",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
@ -102,9 +118,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.3"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
|
||||
checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
@ -113,22 +129,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.15"
|
||||
version = "0.9.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
|
||||
checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.16"
|
||||
version = "0.8.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
|
||||
checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
@ -141,12 +156,12 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.2.5"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583"
|
||||
checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -249,9 +264,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
@ -270,18 +285,18 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.149"
|
||||
version = "0.2.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
||||
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.4"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
|
||||
checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"winapi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -321,9 +336,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "napi"
|
||||
version = "2.13.3"
|
||||
version = "2.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd063c93b900149304e3ba96ce5bf210cd4f81ef5eb80ded0d100df3e85a3ac0"
|
||||
checksum = "1133249c46e92da921bafc8aba4912bf84d6c475f7625183772ed2d0844dc3a7"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"ctor",
|
||||
@ -334,29 +349,29 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "napi-build"
|
||||
version = "2.0.1"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "882a73d9ef23e8dc2ebbffb6a6ae2ef467c0f18ac10711e4cc59c5485d41df0e"
|
||||
checksum = "d4b4532cf86bfef556348ac65e561e3123879f0e7566cca6d43a6ff5326f13df"
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive"
|
||||
version = "2.13.0"
|
||||
version = "2.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da1c6a8fa84d549aa8708fcd062372bf8ec6e849de39016ab921067d21bde367"
|
||||
checksum = "9b5af262f1d8e660742eb722abc7113a5b3c3de4144d0ef23ede2518672ceff1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"convert_case",
|
||||
"napi-derive-backend",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive-backend"
|
||||
version = "1.0.52"
|
||||
version = "1.0.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20bbc7c69168d06a848f925ec5f0e0997f98e8c8d4f2cc30157f0da51c009e17"
|
||||
checksum = "4ea236321b521d6926213a2021e407b0562e28a257c037a45919e414d2cdb4f8"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"once_cell",
|
||||
@ -364,14 +379,14 @@ dependencies = [
|
||||
"quote",
|
||||
"regex",
|
||||
"semver",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-sys"
|
||||
version = "2.2.3"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "166b5ef52a3ab5575047a9fe8d4a030cdd0f63c96f071cd6907674453b07bae3"
|
||||
checksum = "2503fa6af34dc83fb74888df8b22afe933b58d37daf7d80424b1c60c68196b8b"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
@ -408,9 +423,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.18.0"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
@ -427,9 +442,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.69"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@ -501,27 +516,11 @@ version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "rust"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"clipboard-win",
|
||||
"image",
|
||||
"napi",
|
||||
"napi-build",
|
||||
"napi-derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"static_vcruntime",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
@ -537,22 +536,22 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.190"
|
||||
version = "1.0.193"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
|
||||
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.190"
|
||||
version = "1.0.193"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
|
||||
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -574,9 +573,9 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.1"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
|
||||
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
@ -601,20 +600,9 @@ checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
version = "2.0.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
|
||||
checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -681,6 +669,15 @@ dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
|
9
Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["./packages/addon"]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
opt-level = 3
|
||||
strip = "symbols"
|
6
build.rs
@ -1,6 +0,0 @@
|
||||
extern crate napi_build;
|
||||
|
||||
fn main() {
|
||||
static_vcruntime::metabuild();
|
||||
napi_build::setup();
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import { join, dirname } from "node:path";
|
||||
import Database from "better-sqlite3-multiple-ciphers";
|
||||
import { getUserDataPath } from "../main/commons";
|
||||
|
||||
let database: Database.Database;
|
||||
let cacheDatabase: Database.Database;
|
||||
|
||||
function getDataSqlite3() {
|
||||
let filename = join(getUserDataPath(), "Data.db");
|
||||
database ??= new Database(filename, {
|
||||
nativeBinding: join(
|
||||
process.env.NODE_ENV !== "development" ? dirname(process.execPath) : "",
|
||||
import.meta.env.VITE_BETTER_SQLITE3_BINDING
|
||||
),
|
||||
});
|
||||
return database;
|
||||
}
|
||||
|
||||
function getCacheDataSqlite3() {
|
||||
let filename = join(getUserDataPath(), "CacheData.db");
|
||||
cacheDatabase ??= new Database(filename, {
|
||||
nativeBinding: join(
|
||||
process.env.NODE_ENV !== "development" ? dirname(process.execPath) : "",
|
||||
import.meta.env.VITE_BETTER_SQLITE3_BINDING
|
||||
),
|
||||
});
|
||||
return cacheDatabase;
|
||||
}
|
||||
|
||||
function getCustomDataSqlite3(filePath: string) {
|
||||
let db = new Database(filePath, {
|
||||
nativeBinding: join(
|
||||
process.env.NODE_ENV !== "development" ? dirname(process.execPath) : "",
|
||||
import.meta.env.VITE_BETTER_SQLITE3_BINDING
|
||||
),
|
||||
});
|
||||
return db;
|
||||
}
|
||||
|
||||
export { getDataSqlite3, getCacheDataSqlite3, getCustomDataSqlite3 };
|
@ -1,336 +0,0 @@
|
||||
import { Menu, MenuItem, ipcMain, dialog } from "electron";
|
||||
import { Classification } from "../../../types/classification";
|
||||
import {
|
||||
createAddEditWindow,
|
||||
createAssociateFolderWindow,
|
||||
createSetIconWindow,
|
||||
getItemLayoutMenu,
|
||||
getItemSortMenu,
|
||||
getItemColumnNumber,
|
||||
setAssociateFolder,
|
||||
getItemIconSize,
|
||||
addClassificationByDirectory,
|
||||
getItemShowOnly,
|
||||
createAggregateWindow,
|
||||
updateAggregate,
|
||||
updateExcludeSearch,
|
||||
} from "./index";
|
||||
import {
|
||||
add,
|
||||
del,
|
||||
list,
|
||||
selectById,
|
||||
update,
|
||||
updateOrder,
|
||||
updateIcon,
|
||||
hasChildClassification,
|
||||
batchUpdateFixed,
|
||||
} from "./data";
|
||||
import { setShortcutKey } from "../setting";
|
||||
import { closeWindow, getDot, sendToWebContent } from "../commons/index";
|
||||
|
||||
export default function () {
|
||||
// 获取分类列表
|
||||
ipcMain.on("getClassificationList", (event) => {
|
||||
event.returnValue = list(null);
|
||||
});
|
||||
// 根据ID查询分类
|
||||
ipcMain.on("getClassificationById", (event, args) => {
|
||||
event.returnValue = selectById(args.id);
|
||||
});
|
||||
// 添加分类
|
||||
ipcMain.on("addClassification", (event, args) => {
|
||||
let classification = add(
|
||||
args.parentId,
|
||||
args.name,
|
||||
args.shortcutKey,
|
||||
args.globalShortcutKey
|
||||
);
|
||||
setShortcutKey();
|
||||
event.returnValue = classification;
|
||||
});
|
||||
// 更新分类
|
||||
ipcMain.on("updateClassification", (event, args) => {
|
||||
let res = update(args);
|
||||
setShortcutKey();
|
||||
event.returnValue = res;
|
||||
});
|
||||
// 更新序号
|
||||
ipcMain.on("updateClassificationOrder", (event, args) => {
|
||||
event.returnValue = updateOrder(args.fromId, args.toId, args.parentId);
|
||||
});
|
||||
// 更新图标
|
||||
ipcMain.on("updateClassificationIcon", (event, args) => {
|
||||
event.returnValue = updateIcon(args.id, args.icon);
|
||||
});
|
||||
// 显示新增/修改窗口
|
||||
ipcMain.on("showClassificationAddEditWindow", () => {
|
||||
if (global.classificationAddEditWindow) {
|
||||
global.classificationAddEditWindow.show();
|
||||
}
|
||||
});
|
||||
// 关闭新增/修改窗口
|
||||
ipcMain.on("closeClassificationAddEditWindow", () => {
|
||||
closeWindow(global.classificationAddEditWindow);
|
||||
});
|
||||
// 显示设置图标窗口
|
||||
ipcMain.on("showClassificationSetIconWindow", () => {
|
||||
if (global.classificationSetIconWindow) {
|
||||
global.classificationSetIconWindow.show();
|
||||
}
|
||||
});
|
||||
// 关闭设置图标窗口
|
||||
ipcMain.on("closeClassificationSetIconWindow", () => {
|
||||
closeWindow(global.classificationSetIconWindow);
|
||||
});
|
||||
// 右键菜单
|
||||
ipcMain.on("showClassificationRightMenu", (event, args) => {
|
||||
// 锁定/解锁分类
|
||||
let lockClassification: boolean = args.lockClassification;
|
||||
// 分类
|
||||
let classification: Classification | null = args.classification;
|
||||
// 菜单
|
||||
let menuList: Array<MenuItem> = [];
|
||||
// 组装菜单
|
||||
if (!classification) {
|
||||
menuList.push(
|
||||
new MenuItem({
|
||||
label: global.language.newClassification,
|
||||
click: () => {
|
||||
// 创建窗口
|
||||
createAddEditWindow(null, null);
|
||||
},
|
||||
}),
|
||||
new MenuItem({ type: "separator" }),
|
||||
new MenuItem({
|
||||
label: !lockClassification
|
||||
? global.language.lockClassification
|
||||
: global.language.unlockClassification,
|
||||
click: () => {
|
||||
sendToWebContent("mainWindow", "onLockClassification", []);
|
||||
},
|
||||
})
|
||||
);
|
||||
} else {
|
||||
if (!classification.parentId && classification.type === 0) {
|
||||
menuList.push(
|
||||
new MenuItem({
|
||||
label: global.language.newSubclassification,
|
||||
click: () => {
|
||||
// 创建窗口
|
||||
createAddEditWindow(null, classification.id);
|
||||
},
|
||||
}),
|
||||
new MenuItem({ type: "separator" })
|
||||
);
|
||||
}
|
||||
menuList.push(
|
||||
new MenuItem({
|
||||
label: global.language.fixedClassification,
|
||||
icon: classification.data.fixed ? getDot() : null,
|
||||
click: () => {
|
||||
batchUpdateFixed(
|
||||
classification.data.fixed ? null : classification.id
|
||||
);
|
||||
sendToWebContent(
|
||||
"mainWindow",
|
||||
"onUpdateClassificationFixed",
|
||||
classification.data.fixed ? null : classification.id
|
||||
);
|
||||
},
|
||||
})
|
||||
);
|
||||
if (classification.type === 0 || classification.type === 1) {
|
||||
menuList.push(
|
||||
new MenuItem({
|
||||
label: global.language.excludeSearch,
|
||||
icon: classification.data.excludeSearch ? getDot() : null,
|
||||
click: () => {
|
||||
updateExcludeSearch(
|
||||
classification.id,
|
||||
!classification.data.excludeSearch
|
||||
);
|
||||
sendToWebContent(
|
||||
"mainWindow",
|
||||
"onUpdateClassificationExcludeSearch",
|
||||
{
|
||||
id: classification.id,
|
||||
value: !classification.data.excludeSearch,
|
||||
}
|
||||
);
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
menuList.push(new MenuItem({ type: "separator" }));
|
||||
menuList.push(
|
||||
new MenuItem({
|
||||
label: global.language.setIcon,
|
||||
click: () => {
|
||||
// 创建窗口
|
||||
createSetIconWindow(classification.id);
|
||||
},
|
||||
}),
|
||||
new MenuItem({
|
||||
label: global.language.deleteIcon,
|
||||
click: () => {
|
||||
let res = updateIcon(classification.id, null);
|
||||
if (res) {
|
||||
sendToWebContent("mainWindow", "onUpdateClassificationIcon", {
|
||||
id: classification.id,
|
||||
icon: null,
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
// 子分类、没有子分类的父级分类可以显示
|
||||
if (
|
||||
classification.parentId ||
|
||||
(!classification.parentId && !hasChildClassification(classification.id))
|
||||
) {
|
||||
menuList.push(new MenuItem({ type: "separator" }));
|
||||
if (classification.type === 0 || classification.type === 1) {
|
||||
menuList.push(
|
||||
new MenuItem({
|
||||
label: global.language.associateFolder,
|
||||
click: () => {
|
||||
// 创建窗口
|
||||
createAssociateFolderWindow(classification.id);
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
if (classification.type === 0 || classification.type === 2) {
|
||||
menuList.push(
|
||||
new MenuItem({
|
||||
label: global.language.aggregateClassification,
|
||||
click: () => {
|
||||
// 创建窗口
|
||||
createAggregateWindow(classification.id);
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
// 分割线
|
||||
menuList.push(new MenuItem({ type: "separator" }));
|
||||
if (classification.type !== 2) {
|
||||
// 排序
|
||||
menuList.push(getItemSortMenu(classification));
|
||||
}
|
||||
// 布局
|
||||
menuList.push(getItemLayoutMenu(classification));
|
||||
// 列数
|
||||
if (
|
||||
!hasChildClassification(classification.id) &&
|
||||
(classification.data.itemLayout === "list" ||
|
||||
(global.setting.item.layout === "list" &&
|
||||
classification.data.itemLayout === "default"))
|
||||
) {
|
||||
// 只有子级分类或没有子级分类的父级分类并且布局是列表的才显示列数
|
||||
menuList.push(getItemColumnNumber(classification));
|
||||
}
|
||||
// 图标
|
||||
menuList.push(getItemIconSize(classification));
|
||||
// 显示
|
||||
menuList.push(getItemShowOnly(classification));
|
||||
// 编辑/删除
|
||||
menuList.push(
|
||||
new MenuItem({ type: "separator" }),
|
||||
new MenuItem({
|
||||
label: global.language.edit,
|
||||
click: () => {
|
||||
// 创建窗口
|
||||
createAddEditWindow(classification.id, null);
|
||||
},
|
||||
}),
|
||||
new MenuItem({
|
||||
label: global.language.delete,
|
||||
click: () => {
|
||||
let res = dialog.showMessageBoxSync(global.mainWindow, {
|
||||
message: global.language.deleteClassificationPrompt,
|
||||
buttons: [global.language.ok, global.language.cancel],
|
||||
type: "question",
|
||||
noLink: true,
|
||||
cancelId: 1,
|
||||
});
|
||||
if (res === 0) {
|
||||
// 删除数据
|
||||
if (del(classification.id)) {
|
||||
// 快捷键
|
||||
setShortcutKey();
|
||||
// 通知前端删除数据
|
||||
sendToWebContent(
|
||||
"mainWindow",
|
||||
"onDeleteClassification",
|
||||
classification.id
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
// 载入菜单
|
||||
let menu = Menu.buildFromTemplate(menuList);
|
||||
// 菜单显示
|
||||
menu.on("menu-will-show", () => {
|
||||
global.classificationRightMenu = true;
|
||||
});
|
||||
// 菜单关闭
|
||||
menu.on("menu-will-close", () => {
|
||||
global.classificationRightMenu = false;
|
||||
});
|
||||
// 显示
|
||||
menu.popup();
|
||||
});
|
||||
// 显示关联文件夹窗口
|
||||
ipcMain.on("showClassificationAssociateFolderWindow", () => {
|
||||
if (global.classificationAssociateFolderWindow) {
|
||||
global.classificationAssociateFolderWindow.show();
|
||||
}
|
||||
});
|
||||
// 关闭关联文件夹窗口
|
||||
ipcMain.on("closeClassificationAssociateFolderWindow", () => {
|
||||
closeWindow(global.classificationAssociateFolderWindow);
|
||||
});
|
||||
// 设置关联文件夹
|
||||
ipcMain.on("setClassificationAssociateFolder", (event, args) => {
|
||||
// 分类ID
|
||||
let id: number = args.id;
|
||||
// 文件夹路径
|
||||
let dir: string | null = args.dir;
|
||||
if (!dir || dir.trim() === "") {
|
||||
dir = null;
|
||||
}
|
||||
// 隐藏项
|
||||
let hiddenItems: string | null = args.hiddenItems;
|
||||
// 设置
|
||||
event.returnValue = setAssociateFolder(id, dir, hiddenItems);
|
||||
});
|
||||
// 是否拥有子分类
|
||||
ipcMain.on("hasChildClassification", (event, args) => {
|
||||
event.returnValue = hasChildClassification(args);
|
||||
});
|
||||
// 根据文件夹创建分类
|
||||
ipcMain.on("addClassificationByDirectory", (event, args) => {
|
||||
let res = addClassificationByDirectory(args);
|
||||
// 通知页面
|
||||
sendToWebContent("mainWindow", "onAddClassificationByDirectory", res);
|
||||
});
|
||||
// 显示聚合分类窗口
|
||||
ipcMain.on("showClassificationAggregateWindow", () => {
|
||||
if (global.classificationAggregateWindow) {
|
||||
global.classificationAggregateWindow.show();
|
||||
}
|
||||
});
|
||||
// 关闭聚合分类窗口
|
||||
ipcMain.on("closeClassificationAggregateWindow", () => {
|
||||
closeWindow(global.classificationAggregateWindow);
|
||||
});
|
||||
// 更新聚合分类
|
||||
ipcMain.on("updateClassificationAggregate", (event, args) => {
|
||||
event.returnValue = updateAggregate(args.id, args.sort, args.itemCount);
|
||||
});
|
||||
}
|
75
electron/types/global.d.ts
vendored
@ -1,75 +0,0 @@
|
||||
import { BrowserWindow, Tray } from "electron";
|
||||
import { FSWatcher } from "node:fs";
|
||||
import { Setting } from "../../types/setting";
|
||||
|
||||
declare global {
|
||||
// addon
|
||||
var addon: any;
|
||||
// 语言
|
||||
var language: any;
|
||||
// 主窗口
|
||||
var mainWindow: BrowserWindow | null;
|
||||
// 快速搜索窗口
|
||||
var quickSearchWindow: BrowserWindow | null;
|
||||
// 快速搜索窗口是否初始化完成
|
||||
var quickSearchWindowInit: Boolean | null;
|
||||
// 设置窗口
|
||||
var settingWindow: BrowserWindow | null;
|
||||
// 分类添加/编辑窗口
|
||||
var classificationAddEditWindow: BrowserWindow | null;
|
||||
// 分类图标窗口
|
||||
var classificationSetIconWindow: BrowserWindow | null;
|
||||
// 关联分类窗口
|
||||
var classificationAssociateFolderWindow: BrowserWindow | null;
|
||||
// 聚合分类窗口
|
||||
var classificationAggregateWindow: BrowserWindow | null;
|
||||
// 项目添加/编辑窗口
|
||||
var itemAddEditWindow: BrowserWindow | null;
|
||||
// 项目网络图标窗口
|
||||
var itemNetworkIconWindow: BrowserWindow | null;
|
||||
// 项目SVG图标窗口
|
||||
var itemSVGIconWindow: BrowserWindow | null;
|
||||
// 关于窗口
|
||||
var aboutWindow: BrowserWindow | null;
|
||||
// 备份/恢复数据窗口
|
||||
var backupRestoreDataWindow: BrowserWindow | null;
|
||||
// 存储关联分类监听
|
||||
var associateFolderWatcher: Map<number, AssociateFolderData>;
|
||||
// 设置
|
||||
var setting: Setting | null;
|
||||
// 托盘
|
||||
var tray: Tray;
|
||||
// 主窗口方向
|
||||
var mainWindowDirection: String | null;
|
||||
// 停靠在桌面边缘时自动隐藏timer
|
||||
var autoHideTimer: NodeJS.Timeout;
|
||||
// 需要失去焦点时隐藏
|
||||
var blurHide: boolean | null;
|
||||
// 双击任务栏显示/隐藏timer
|
||||
var doubleClickTaskbarTimer: NodeJS.Timeout;
|
||||
// 双击任务栏显示/隐藏counter
|
||||
var doubleClickTaskbarCounter: number;
|
||||
// 监测无效项目interval
|
||||
var checkInvalidItemInterval: NodeJS.Timeout;
|
||||
// 存储子进程信息
|
||||
var childProcessMap: Map<number, ChildProcessInfo>;
|
||||
// 分类右键菜单显示
|
||||
var classificationRightMenu: boolean | null;
|
||||
// 项目右键菜单显示
|
||||
var itemRightMenu: boolean | null;
|
||||
}
|
||||
|
||||
export interface ChildProcessInfo {
|
||||
utilityProcess: Electron.UtilityProcess;
|
||||
port1: Electron.MessagePortMain;
|
||||
port2: Electron.MessagePortMain;
|
||||
}
|
||||
|
||||
export interface AssociateFolderData {
|
||||
classificationId: number;
|
||||
dir: string;
|
||||
hiddenItems: string | null;
|
||||
watch: FSWatcher;
|
||||
}
|
||||
|
||||
export {};
|
91
package.json
@ -1,74 +1,19 @@
|
||||
{
|
||||
"name": "dawn-launcher",
|
||||
"productName": "Dawn Launcher",
|
||||
"version": "1.3.5",
|
||||
"main": "dist-electron/main/index.js",
|
||||
"description": "Windows 快捷启动工具,帮助您整理杂乱无章的桌面,分门别类管理您的桌面快捷方式,让您的桌面保持干净整洁。",
|
||||
"author": "FanChenIO",
|
||||
"private": true,
|
||||
"keywords": [
|
||||
"electron",
|
||||
"vite",
|
||||
"vue"
|
||||
],
|
||||
"debug": {
|
||||
"env": {
|
||||
"VITE_DEV_SERVER_URL": "http://127.0.0.1:3344/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit && vite build && electron-builder",
|
||||
"preview": "vite preview",
|
||||
"rsbuild": "napi build --release --strip ./native",
|
||||
"rebuild": "electron rebuild.js",
|
||||
"postinstall": "yarn run rebuild && yarn run rsbuild"
|
||||
},
|
||||
"napi": {
|
||||
"name": "addon"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.16.3",
|
||||
"@vicons/material": "^0.12.0",
|
||||
"@vicons/ionicons5": "^0.12.0",
|
||||
"@vicons/utils": "^0.1.4",
|
||||
"@vitejs/plugin-vue": "^4.4.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"better-sqlite3-multiple-ciphers": "^9.0.0",
|
||||
"electron": "^26.4.2",
|
||||
"electron-builder": "^24.6.4",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^11.1.3",
|
||||
"naive-ui": "^2.35.0",
|
||||
"postcss": "^8.4.31",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.4.11",
|
||||
"vite-plugin-electron": "^0.15.4",
|
||||
"vue": "^3.3.7",
|
||||
"vue-tsc": "^1.8.22"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/dompurify": "^3.0.3",
|
||||
"@types/mime": "^3.0.2",
|
||||
"@types/request": "^2.48.10",
|
||||
"@types/retry": "^0.12.3",
|
||||
"@types/sortablejs": "^1.15.3",
|
||||
"@types/urijs": "^1.19.22",
|
||||
"@types/xml2js": "^0.4.12",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"dompurify": "^3.0.6",
|
||||
"electron-log": "^5.0.0",
|
||||
"electron-store": "^8.1.0",
|
||||
"mime": "^3.0.0",
|
||||
"pinia": "^2.1.7",
|
||||
"pinyin-pro": "^3.17.0",
|
||||
"request": "^2.88.2",
|
||||
"retry": "^0.13.1",
|
||||
"simplebar": "^6.2.5",
|
||||
"sortablejs": "^1.15.0",
|
||||
"urijs": "^1.19.11",
|
||||
"vue-router": "^4.2.5",
|
||||
"xml2js": "^0.6.2"
|
||||
}
|
||||
}
|
||||
"name": "@dawn-launcher/monorepos",
|
||||
"productName": "Dawn Launcher",
|
||||
"version": "1.3.5",
|
||||
"private": true,
|
||||
"author": "FanChenIO",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "yarn workspace @dawn-launcher/electron dev",
|
||||
"build": "yarn workspace @dawn-launcher/electron build",
|
||||
"build:addon": "yarn workspace @dawn-launcher/addon build",
|
||||
"build:sqlite": "yarn workspace @dawn-launcher/electron build:sqlite",
|
||||
"postinstall": "yarn build:addon && yarn build:sqlite"
|
||||
},
|
||||
"packageManager": "yarn@4.0.2"
|
||||
}
|
||||
|
197
packages/addon/.gitignore
vendored
Normal file
@ -0,0 +1,197 @@
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/node
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=node
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/node
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/macos
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=macos
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### macOS Patch ###
|
||||
# iCloud generated files
|
||||
*.icloud
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/macos
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/windows
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=windows
|
||||
|
||||
### Windows ###
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/windows
|
||||
|
||||
#Added by cargo
|
||||
|
||||
/target
|
||||
Cargo.lock
|
||||
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
*.node
|
13
packages/addon/.npmignore
Normal file
@ -0,0 +1,13 @@
|
||||
target
|
||||
Cargo.lock
|
||||
.cargo
|
||||
.github
|
||||
npm
|
||||
.eslintrc
|
||||
.prettierignore
|
||||
rustfmt.toml
|
||||
yarn.lock
|
||||
*.node
|
||||
.yarn
|
||||
__test__
|
||||
renovate.json
|
1
packages/addon/.yarnrc.yml
Normal file
@ -0,0 +1 @@
|
||||
nodeLinker: node-modules
|
@ -1,11 +1,10 @@
|
||||
[package]
|
||||
name = "rust"
|
||||
version = "0.0.0"
|
||||
build = "build.rs"
|
||||
edition = "2021"
|
||||
name = "addon"
|
||||
version = "0.0.1"
|
||||
authors = ["fanchenio <fanchenio@163.com>", "DarkSky <darksky2048@gmail.com>"]
|
||||
|
||||
[lib]
|
||||
path = "rust/lib.rs"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
@ -13,12 +12,12 @@ napi = { version = "2", features = ["napi4"] }
|
||||
napi-derive = "2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
image = "0.24.6"
|
||||
image = "0.24.7"
|
||||
base64 = "0.21.2"
|
||||
clipboard-win = "4.5.0"
|
||||
|
||||
[dependencies.windows]
|
||||
version = "0.48"
|
||||
version = "0.48.0"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Gdi",
|
||||
@ -40,3 +39,6 @@ features = [
|
||||
[build-dependencies]
|
||||
napi-build = "2"
|
||||
static_vcruntime = "2.0"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
6
packages/addon/build.rs
Normal file
@ -0,0 +1,6 @@
|
||||
extern crate napi_build;
|
||||
|
||||
fn main() {
|
||||
static_vcruntime::metabuild();
|
||||
napi_build::setup();
|
||||
}
|
85
packages/addon/index.d.ts
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
/**
|
||||
* 获取图标
|
||||
*/
|
||||
export function getFileIcon(path: string): string | null
|
||||
/**
|
||||
* 搜索路径
|
||||
*/
|
||||
export function searchPath(path: string): string | null
|
||||
/**
|
||||
* 解析快捷方式
|
||||
*/
|
||||
export function getShortcutFileInfo(path: string): Record<string, string> | null
|
||||
/**
|
||||
* 运行
|
||||
*/
|
||||
export function shellExecute(operation: string, file: string, params: string, startLocation?: string | undefined | null): void
|
||||
/**
|
||||
* 运行系统项目
|
||||
*/
|
||||
export function systemItemExecute(target: string, params?: string | undefined | null): void
|
||||
/**
|
||||
* 打开文件所在位置
|
||||
*/
|
||||
export function openFileLocation(path: string): void
|
||||
/**
|
||||
* 资源管理器菜单
|
||||
*/
|
||||
export function explorerContextMenu(window: number, path: string, x: number, y: number): void
|
||||
/**
|
||||
* 获取环境变量
|
||||
*/
|
||||
export function getEnvByName(name: string): string | null
|
||||
/**
|
||||
* 是否是全屏模式
|
||||
*/
|
||||
export function isFullscreen(): boolean
|
||||
/**
|
||||
* 切换英文输入法
|
||||
*/
|
||||
export function switchEnglish(window: number): void
|
||||
/**
|
||||
* 创建鼠标HOOK
|
||||
*/
|
||||
export function createMouseHook(callback: (...args: any[]) => any): void
|
||||
/**
|
||||
* 启用鼠标HOOK
|
||||
*/
|
||||
export function enableMouseHook(): void
|
||||
/**
|
||||
* 禁用鼠标HOOK
|
||||
*/
|
||||
export function disableMouseHook(): void
|
||||
/**
|
||||
* 获取鼠标点击的窗口ClassName
|
||||
*/
|
||||
export function getCursorPosWindowClassName(): string
|
||||
/**
|
||||
* 获取剪切板文件列表
|
||||
*/
|
||||
export function getClipboardFileList(): Array<string>
|
||||
/**
|
||||
* 剪切板是否存在BITMAP
|
||||
*/
|
||||
export function clipboardHasBitmap(): boolean
|
||||
/**
|
||||
* 获取剪切板BITMAP的BASE64
|
||||
*/
|
||||
export function getClipboardBitmapBase64(): string | null
|
||||
/**
|
||||
* 清空回收站
|
||||
*/
|
||||
export function emptyRecycleBin(window: number): void
|
||||
/**
|
||||
* 去掉窗口动画
|
||||
*/
|
||||
export function removeWindowAnimation(window: number): void
|
||||
/**
|
||||
* 获取APPX列表
|
||||
*/
|
||||
export function getAppxList(): Array<Record<string, string>>
|
49
packages/addon/index.js
Normal file
@ -0,0 +1,49 @@
|
||||
import * as Addon from './addon.node'
|
||||
|
||||
const {
|
||||
getFileIcon,
|
||||
searchPath,
|
||||
getShortcutFileInfo,
|
||||
shellExecute,
|
||||
systemItemExecute,
|
||||
openFileLocation,
|
||||
explorerContextMenu,
|
||||
getEnvByName,
|
||||
isFullscreen,
|
||||
switchEnglish,
|
||||
createMouseHook,
|
||||
enableMouseHook,
|
||||
disableMouseHook,
|
||||
getCursorPosWindowClassName,
|
||||
getClipboardFileList,
|
||||
clipboardHasBitmap,
|
||||
getClipboardBitmapBase64,
|
||||
emptyRecycleBin,
|
||||
removeWindowAnimation,
|
||||
getAppxList,
|
||||
} = Addon
|
||||
|
||||
export default Addon
|
||||
|
||||
export {
|
||||
getFileIcon,
|
||||
searchPath,
|
||||
getShortcutFileInfo,
|
||||
shellExecute,
|
||||
systemItemExecute,
|
||||
openFileLocation,
|
||||
explorerContextMenu,
|
||||
getEnvByName,
|
||||
isFullscreen,
|
||||
switchEnglish,
|
||||
createMouseHook,
|
||||
enableMouseHook,
|
||||
disableMouseHook,
|
||||
getCursorPosWindowClassName,
|
||||
getClipboardFileList,
|
||||
clipboardHasBitmap,
|
||||
getClipboardBitmapBase64,
|
||||
emptyRecycleBin,
|
||||
removeWindowAnimation,
|
||||
getAppxList,
|
||||
}
|
37
packages/addon/package.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "@dawn-launcher/addon",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"napi": {
|
||||
"name": "addon",
|
||||
"triples": {
|
||||
"defaults": false,
|
||||
"additional": [
|
||||
"aarch64-pc-windows-msvc",
|
||||
"x86_64-pc-windows-msvc"
|
||||
]
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.17.0",
|
||||
"ava": "^5.1.1"
|
||||
},
|
||||
"ava": {
|
||||
"timeout": "3m"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"scripts": {
|
||||
"artifacts": "napi artifacts",
|
||||
"build": "napi build --release",
|
||||
"build:debug": "napi build",
|
||||
"prepublishOnly": "napi prepublish -t npm",
|
||||
"test": "ava",
|
||||
"universal": "napi universal",
|
||||
"version": "napi version"
|
||||
}
|
||||
}
|
2
packages/addon/rustfmt.toml
Normal file
@ -0,0 +1,2 @@
|
||||
tab_spaces = 2
|
||||
edition = "2021"
|
@ -10,7 +10,7 @@ mod windows;
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn get_file_icon(path: String) -> Option<String> {
|
||||
windows::get_file_icon(&path)
|
||||
windows::get_file_icon(&path)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -19,7 +19,7 @@ fn get_file_icon(path: String) -> Option<String> {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn search_path(path: String) -> Option<String> {
|
||||
windows::search_path(&path)
|
||||
windows::search_path(&path)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -28,7 +28,7 @@ fn search_path(path: String) -> Option<String> {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn get_shortcut_file_info(path: String) -> Option<HashMap<String, String>> {
|
||||
windows::get_shortcut_file_info(&path)
|
||||
windows::get_shortcut_file_info(&path)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,7 +37,7 @@ fn get_shortcut_file_info(path: String) -> Option<HashMap<String, String>> {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn shell_execute(operation: String, file: String, params: String, start_location: Option<String>) {
|
||||
windows::shell_execute(operation, file, params, start_location)
|
||||
windows::shell_execute(operation, file, params, start_location)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,7 +46,7 @@ fn shell_execute(operation: String, file: String, params: String, start_location
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn system_item_execute(target: String, params: Option<String>) {
|
||||
windows::system_item_execute(&target, params.as_deref())
|
||||
windows::system_item_execute(&target, params.as_deref())
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,7 +55,7 @@ fn system_item_execute(target: String, params: Option<String>) {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn open_file_location(path: String) {
|
||||
windows::open_file_location(&path)
|
||||
windows::open_file_location(&path)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,7 +64,7 @@ fn open_file_location(path: String) {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn explorer_context_menu(window: i32, path: String, x: i32, y: i32) {
|
||||
windows::explorer_context_menu(window, &path, x, y)
|
||||
windows::explorer_context_menu(window, &path, x, y)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,7 +73,7 @@ fn explorer_context_menu(window: i32, path: String, x: i32, y: i32) {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn get_env_by_name(name: String) -> Option<String> {
|
||||
windows::get_env_by_name(&name)
|
||||
windows::get_env_by_name(&name)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,7 +82,7 @@ fn get_env_by_name(name: String) -> Option<String> {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn is_fullscreen() -> bool {
|
||||
windows::is_fullscreen()
|
||||
windows::is_fullscreen()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,7 +91,7 @@ fn is_fullscreen() -> bool {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn switch_english(window: i32) {
|
||||
windows::switch_english(window)
|
||||
windows::switch_english(window)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,7 +100,7 @@ fn switch_english(window: i32) {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn create_mouse_hook(callback: JsFunction) {
|
||||
windows::create_mouse_hook(callback)
|
||||
windows::create_mouse_hook(callback)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,7 +109,7 @@ fn create_mouse_hook(callback: JsFunction) {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn enable_mouse_hook() {
|
||||
windows::enable_mouse_hook()
|
||||
windows::enable_mouse_hook()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -118,7 +118,7 @@ fn enable_mouse_hook() {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn disable_mouse_hook() {
|
||||
windows::disable_mouse_hook()
|
||||
windows::disable_mouse_hook()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -127,7 +127,7 @@ fn disable_mouse_hook() {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn get_cursor_pos_window_class_name() -> String {
|
||||
windows::get_cursor_pos_window_class_name()
|
||||
windows::get_cursor_pos_window_class_name()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -136,7 +136,7 @@ fn get_cursor_pos_window_class_name() -> String {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn get_clipboard_file_list() -> Vec<String> {
|
||||
windows::get_clipboard_file_list()
|
||||
windows::get_clipboard_file_list()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -145,7 +145,7 @@ fn get_clipboard_file_list() -> Vec<String> {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn clipboard_has_bitmap() -> bool {
|
||||
windows::clipboard_has_bitmap()
|
||||
windows::clipboard_has_bitmap()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -154,7 +154,7 @@ fn clipboard_has_bitmap() -> bool {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn get_clipboard_bitmap_base64() -> Option<String> {
|
||||
windows::get_clipboard_bitmap_base64()
|
||||
windows::get_clipboard_bitmap_base64()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -163,7 +163,7 @@ fn get_clipboard_bitmap_base64() -> Option<String> {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn empty_recycle_bin(window: i32) {
|
||||
windows::empty_recycle_bin(window)
|
||||
windows::empty_recycle_bin(window)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -172,7 +172,7 @@ fn empty_recycle_bin(window: i32) {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn remove_window_animation(window: i32) {
|
||||
windows::remove_window_animation(window);
|
||||
windows::remove_window_animation(window);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -181,5 +181,5 @@ fn remove_window_animation(window: i32) {
|
||||
#[allow(dead_code)]
|
||||
#[napi]
|
||||
fn get_appx_list() -> Vec<HashMap<String, String>> {
|
||||
windows::get_appx_list()
|
||||
windows::get_appx_list()
|
||||
}
|
749
packages/addon/src/windows.rs
Normal file
@ -0,0 +1,749 @@
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use clipboard_win::{formats, get_clipboard};
|
||||
use image::{imageops::flip_vertical, ImageBuffer, ImageFormat, Rgba};
|
||||
use napi::{
|
||||
threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
|
||||
JsFunction,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::Cursor,
|
||||
path::Path,
|
||||
process::Command,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
use windows::Management::Deployment::PackageManager;
|
||||
use windows::{
|
||||
core::{ComInterface, HSTRING, PCSTR, PCWSTR},
|
||||
w,
|
||||
Win32::{
|
||||
Foundation::{HWND, LPARAM, LRESULT, MAX_PATH, POINT, RECT, SIZE, WPARAM},
|
||||
Graphics::{
|
||||
Dwm::{DwmSetWindowAttribute, DWMWA_TRANSITIONS_FORCEDISABLED},
|
||||
Gdi::{
|
||||
GetMonitorInfoW, GetObjectW, MonitorFromWindow, BITMAP, MONITORINFO,
|
||||
MONITOR_DEFAULTTONEAREST,
|
||||
},
|
||||
},
|
||||
Storage::FileSystem::SearchPathW,
|
||||
System::{
|
||||
Com::{
|
||||
CoCreateInstance, CoInitializeEx, CoUninitialize, IPersistFile, CLSCTX_INPROC_SERVER,
|
||||
COINIT_APARTMENTTHREADED, STGM_READ,
|
||||
},
|
||||
Environment::GetEnvironmentVariableW,
|
||||
SystemInformation::GetSystemDirectoryW,
|
||||
},
|
||||
UI::{
|
||||
Input::Ime::{
|
||||
ImmGetContext, ImmReleaseContext, ImmSetConversionStatus, IME_CMODE_ALPHANUMERIC,
|
||||
IME_SMODE_AUTOMATIC,
|
||||
},
|
||||
Shell::{
|
||||
BHID_SFUIObject, IContextMenu, IShellItem, IShellItemImageFactory, IShellLinkW,
|
||||
SHCreateItemFromParsingName, SHEmptyRecycleBinW, SHQueryUserNotificationState,
|
||||
ShellExecuteW, ShellLink, CMF_NORMAL, CMINVOKECOMMANDINFO, QUNS_ACCEPTS_NOTIFICATIONS,
|
||||
QUNS_APP, QUNS_BUSY, QUNS_NOT_PRESENT, QUNS_PRESENTATION_MODE, QUNS_QUIET_TIME,
|
||||
QUNS_RUNNING_D3D_FULL_SCREEN, SHERB_NOSOUND, SIIGBF_ICONONLY, SLGP_UNCPRIORITY,
|
||||
},
|
||||
WindowsAndMessaging::{
|
||||
CallNextHookEx, CreatePopupMenu, DestroyMenu, FindWindowW, GetClassNameW, GetCursorPos,
|
||||
GetForegroundWindow, GetSystemMetrics, GetWindowRect, SendMessageW, SetForegroundWindow,
|
||||
SetWindowsHookExW, TrackPopupMenu, WindowFromPoint, HHOOK, MSLLHOOKSTRUCT, SC_MONITORPOWER,
|
||||
SM_CXSCREEN, SM_CYSCREEN, SW_NORMAL, SW_SHOWDEFAULT, TPM_NONOTIFY, TPM_RETURNCMD,
|
||||
WH_MOUSE_LL, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL,
|
||||
WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SYSCOMMAND,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// 获取图标并转为BASE64
|
||||
pub fn get_file_icon(path: &str) -> Option<String> {
|
||||
// 返回信息
|
||||
let mut base64 = None;
|
||||
// HSTRING
|
||||
let path = HSTRING::from(path);
|
||||
// PCWSTR
|
||||
let path: PCWSTR = PCWSTR(path.as_ptr());
|
||||
// unsafe
|
||||
unsafe {
|
||||
// Init
|
||||
let _ = CoInitializeEx(None, COINIT_APARTMENTTHREADED);
|
||||
}
|
||||
// IShellItemImageFactory
|
||||
let result =
|
||||
unsafe { SHCreateItemFromParsingName::<PCWSTR, Option<_>, IShellItemImageFactory>(path, None) };
|
||||
if let Ok(shell_item_image_factory) = result {
|
||||
if let Some(mut image_buffer) = get_file_icon_image_buffer(&shell_item_image_factory, 256) {
|
||||
// 判断像素点,是否是小图标
|
||||
let mut transparency: f64 = 0_f64;
|
||||
let mut non_transparency: f64 = 0_f64;
|
||||
for y in 0..image_buffer.height() {
|
||||
for x in 0..image_buffer.width() {
|
||||
let pixel = image_buffer.get_pixel(x, y);
|
||||
let alpha = pixel[3]; // 获取像素的 Alpha 通道值
|
||||
if alpha == 0 {
|
||||
// 透明
|
||||
transparency += 1_f64;
|
||||
} else {
|
||||
// 不透明
|
||||
non_transparency += 1_f64;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 计算如果透明区域大于百分之70就代表是小图标
|
||||
let proportion = (transparency / (transparency + non_transparency) * 100_f64).round() as u32;
|
||||
if proportion >= 70 {
|
||||
// 获取小图标
|
||||
if let Some(image_buffer_small) = get_file_icon_image_buffer(&shell_item_image_factory, 48)
|
||||
{
|
||||
image_buffer = image_buffer_small;
|
||||
}
|
||||
}
|
||||
// 翻转图片
|
||||
image_buffer = flip_vertical(&image_buffer);
|
||||
// 转码
|
||||
base64 = Some(image_buffer_to_base64(image_buffer));
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
// UnInit
|
||||
CoUninitialize();
|
||||
}
|
||||
base64
|
||||
}
|
||||
|
||||
// 获取图标并转为ImageBuffer
|
||||
fn get_file_icon_image_buffer(
|
||||
shell_item_image_factory: &IShellItemImageFactory,
|
||||
size: i32,
|
||||
) -> Option<ImageBuffer<Rgba<u8>, Vec<u8>>> {
|
||||
// 获取图片
|
||||
let result =
|
||||
unsafe { shell_item_image_factory.GetImage(SIZE { cx: size, cy: size }, SIIGBF_ICONONLY) };
|
||||
if let Ok(h_bitmap) = result {
|
||||
// 转为BITMAP
|
||||
let mut bitmap: BITMAP = BITMAP::default();
|
||||
unsafe {
|
||||
GetObjectW(
|
||||
h_bitmap,
|
||||
std::mem::size_of::<BITMAP>() as i32,
|
||||
Some(&mut bitmap as *mut _ as _),
|
||||
);
|
||||
}
|
||||
// 转换ImageBuffer
|
||||
let width: u32 = bitmap.bmWidth as u32;
|
||||
let height = bitmap.bmHeight as u32;
|
||||
let pixel_data: &[u8] = unsafe {
|
||||
std::slice::from_raw_parts(bitmap.bmBits as *const u8, (width * height * 4) as usize)
|
||||
};
|
||||
let result = ImageBuffer::<Rgba<u8>, _>::from_raw(width, height, pixel_data.to_vec());
|
||||
if let Some(mut image_buffer) = result {
|
||||
// 将ImageBuffer的颜色通道顺序从BGRA转为RGB
|
||||
for pixel in image_buffer.pixels_mut() {
|
||||
let b = pixel[0];
|
||||
let r = pixel[2];
|
||||
pixel[0] = r;
|
||||
pixel[2] = b;
|
||||
}
|
||||
return Some(image_buffer);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/**
|
||||
* imageBuffer转BASE64
|
||||
*/
|
||||
fn image_buffer_to_base64(image_buffer: ImageBuffer<Rgba<u8>, Vec<u8>>) -> String {
|
||||
// imageBufferData
|
||||
let mut image_buffer_data = Cursor::new(Vec::new());
|
||||
// write
|
||||
image_buffer
|
||||
.write_to(&mut image_buffer_data, ImageFormat::Png)
|
||||
.unwrap();
|
||||
// 转码
|
||||
format!(
|
||||
"data:image/png;base64,{}",
|
||||
general_purpose::STANDARD.encode(image_buffer_data.into_inner())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行
|
||||
*/
|
||||
pub fn shell_execute(
|
||||
operation: String,
|
||||
file: String,
|
||||
params: String,
|
||||
start_location: Option<String>,
|
||||
) {
|
||||
// dir
|
||||
let dir = start_location.unwrap_or_else(|| {
|
||||
// 判断是否是文件夹
|
||||
let path = Path::new(&file);
|
||||
if path.is_dir() {
|
||||
// 文件夹
|
||||
file.clone()
|
||||
} else {
|
||||
// 文件 获取上一级目录
|
||||
path.parent().unwrap().display().to_string()
|
||||
}
|
||||
});
|
||||
// HSTRING
|
||||
let operation = HSTRING::from(operation.as_str());
|
||||
let file = HSTRING::from(file.as_str());
|
||||
let params = HSTRING::from(params.as_str());
|
||||
let dir = HSTRING::from(dir.as_str());
|
||||
// PCWSTR
|
||||
let operation = PCWSTR(operation.as_ptr());
|
||||
let file = PCWSTR(file.as_ptr());
|
||||
let params = PCWSTR(params.as_ptr());
|
||||
let dir = PCWSTR(dir.as_ptr());
|
||||
unsafe {
|
||||
// execute
|
||||
ShellExecuteW(None, operation, file, params, dir, SW_SHOWDEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取快捷方式信息
|
||||
*/
|
||||
pub fn get_shortcut_file_info(path: &str) -> Option<HashMap<String, String>> {
|
||||
// HSTRING
|
||||
let path = HSTRING::from(path);
|
||||
unsafe {
|
||||
// Init
|
||||
let _ = CoInitializeEx(None, COINIT_APARTMENTTHREADED);
|
||||
}
|
||||
// IShellLinkW
|
||||
let shell_link_result: Result<IShellLinkW, windows::core::Error> =
|
||||
unsafe { CoCreateInstance(&ShellLink, None, CLSCTX_INPROC_SERVER) };
|
||||
if let Ok(shell_link) = shell_link_result {
|
||||
// IPersistFile
|
||||
let persist_file_result: Result<IPersistFile, windows::core::Error> = shell_link.cast();
|
||||
if let Ok(persist_file) = persist_file_result {
|
||||
let load_result = unsafe {
|
||||
// 加载路径
|
||||
persist_file.Load(PCWSTR(path.as_ptr()), STGM_READ)
|
||||
};
|
||||
if let Ok(()) = load_result {
|
||||
// 获取目标
|
||||
let mut target_buffer = [0u16; MAX_PATH as usize];
|
||||
let _ = unsafe {
|
||||
shell_link.GetPath(
|
||||
&mut target_buffer,
|
||||
std::ptr::null_mut(),
|
||||
SLGP_UNCPRIORITY.0 as u32,
|
||||
)
|
||||
};
|
||||
// 获取参数
|
||||
let mut arguments_buffer = [0u16; MAX_PATH as usize];
|
||||
let _ = unsafe { shell_link.GetArguments(&mut arguments_buffer) };
|
||||
// map
|
||||
let mut map = HashMap::with_capacity(2);
|
||||
map.insert(String::from("target"), u16_to_string(&target_buffer));
|
||||
map.insert(String::from("arguments"), u16_to_string(&arguments_buffer));
|
||||
return Some(map);
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
// UnInit
|
||||
CoUninitialize();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行命令
|
||||
*/
|
||||
pub fn system_item_execute(target: &str, params: Option<&str>) {
|
||||
if target == "static:TurnOffMonitor" {
|
||||
// 关闭显示器
|
||||
turn_off_monitor()
|
||||
} else {
|
||||
let mut file = target.to_string();
|
||||
if !target.starts_with("shell:") {
|
||||
// 如果不是shell开头,就查询路径
|
||||
file = search_path(target).unwrap_or(target.to_string());
|
||||
}
|
||||
let file = HSTRING::from(file);
|
||||
let params = match params {
|
||||
Some(p) => HSTRING::from(p),
|
||||
_ => HSTRING::new(),
|
||||
};
|
||||
// 获取系统盘路径当作工作目录
|
||||
let mut buffer = [0u16; MAX_PATH as usize];
|
||||
unsafe {
|
||||
GetSystemDirectoryW(Some(&mut buffer));
|
||||
}
|
||||
let dir = u16_to_string(&buffer);
|
||||
let dir = HSTRING::from(dir);
|
||||
// execute
|
||||
unsafe {
|
||||
ShellExecuteW(
|
||||
None,
|
||||
w!("open"),
|
||||
PCWSTR(file.as_ptr()),
|
||||
PCWSTR(params.as_ptr()),
|
||||
PCWSTR(dir.as_ptr()),
|
||||
SW_SHOWDEFAULT,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭显示器
|
||||
*/
|
||||
pub fn turn_off_monitor() {
|
||||
unsafe {
|
||||
let _ = SendMessageW(
|
||||
FindWindowW(PCWSTR::null(), PCWSTR::null()),
|
||||
WM_SYSCOMMAND,
|
||||
WPARAM(SC_MONITORPOWER as usize),
|
||||
LPARAM(2),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开文件所在位置
|
||||
*/
|
||||
pub fn open_file_location(path: &str) {
|
||||
let _ = Command::new("explorer").arg("/select,").arg(path).spawn();
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源管理器菜单
|
||||
*/
|
||||
pub fn explorer_context_menu(window: i32, path: &str, x: i32, y: i32) {
|
||||
// IShellItem
|
||||
let path = HSTRING::from(path);
|
||||
if let Ok(shell_item) =
|
||||
unsafe { SHCreateItemFromParsingName::<_, _, IShellItem>(PCWSTR(path.as_ptr()), None) }
|
||||
{
|
||||
// IContextMenu
|
||||
if let Ok(context_menu) =
|
||||
unsafe { shell_item.BindToHandler::<_, IContextMenu>(None, &BHID_SFUIObject) }
|
||||
{
|
||||
// Menu
|
||||
if let Ok(menu) = unsafe { CreatePopupMenu() } {
|
||||
// 写入菜单
|
||||
if let Ok(()) = unsafe { context_menu.QueryContextMenu(menu, 0, 1, 0x7FFF, CMF_NORMAL) } {
|
||||
// HWND
|
||||
let hwnd = HWND(window as isize);
|
||||
// 弹出菜单
|
||||
let res = unsafe {
|
||||
SetForegroundWindow(hwnd);
|
||||
TrackPopupMenu(menu, TPM_RETURNCMD | TPM_NONOTIFY, x, y, 0, hwnd, None)
|
||||
};
|
||||
unsafe {
|
||||
DestroyMenu(menu);
|
||||
}
|
||||
if res.as_bool() {
|
||||
let mut info = CMINVOKECOMMANDINFO::default();
|
||||
info.cbSize = std::mem::size_of::<CMINVOKECOMMANDINFO>() as u32;
|
||||
info.hwnd = hwnd;
|
||||
info.lpVerb = PCSTR((res.0 - 1) as *mut u8);
|
||||
info.nShow = SW_NORMAL.0 as i32;
|
||||
let _ = unsafe { context_menu.InvokeCommand(&info) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索路径
|
||||
*/
|
||||
pub fn search_path(name: &str) -> Option<String> {
|
||||
let name = HSTRING::from(name);
|
||||
let mut buffer = [0u16; MAX_PATH as usize];
|
||||
let result = unsafe {
|
||||
SearchPathW(
|
||||
PCWSTR::null(),
|
||||
PCWSTR(name.as_ptr()),
|
||||
PCWSTR::null(),
|
||||
Some(&mut buffer),
|
||||
None,
|
||||
)
|
||||
};
|
||||
if result > 0 {
|
||||
Some(u16_to_string(&buffer))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组转String
|
||||
*/
|
||||
fn u16_to_string(slice: &[u16]) -> String {
|
||||
let mut vec = vec![];
|
||||
for s in slice {
|
||||
if *s > 0 {
|
||||
vec.push(*s);
|
||||
}
|
||||
}
|
||||
String::from_utf16_lossy(&vec)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取环境变量
|
||||
*/
|
||||
pub fn get_env_by_name(name: &str) -> Option<String> {
|
||||
let name = HSTRING::from(name);
|
||||
let mut buffer = [0u16; MAX_PATH as usize];
|
||||
let result = unsafe { GetEnvironmentVariableW(PCWSTR(name.as_ptr()), Some(&mut buffer)) };
|
||||
if result > 0 {
|
||||
Some(u16_to_string(&buffer))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有全屏窗口
|
||||
*/
|
||||
fn is_fullscreen_window() -> bool {
|
||||
// 获取当前活动窗口的句柄
|
||||
let foreground_window = unsafe { GetForegroundWindow() };
|
||||
// 获取活动窗口的位置信息
|
||||
let mut window_rect = RECT::default();
|
||||
unsafe { GetWindowRect(foreground_window, &mut window_rect) };
|
||||
// 获取包含活动窗口的显示器句柄
|
||||
let monitor = unsafe { MonitorFromWindow(foreground_window, MONITOR_DEFAULTTONEAREST) };
|
||||
// 获取显示器信息
|
||||
let mut monitor_info = MONITORINFO::default();
|
||||
monitor_info.cbSize = std::mem::size_of::<MONITORINFO>() as u32;
|
||||
unsafe { GetMonitorInfoW(monitor, &mut monitor_info) };
|
||||
// 获取屏幕的尺寸
|
||||
let screen_width = unsafe { GetSystemMetrics(SM_CXSCREEN) };
|
||||
let screen_height = unsafe { GetSystemMetrics(SM_CYSCREEN) };
|
||||
// 比较窗口位置和显示器尺寸来判断是否处于全屏模式
|
||||
if window_rect.left <= 0
|
||||
&& window_rect.top <= 0
|
||||
&& window_rect.right >= screen_width
|
||||
&& window_rect.bottom >= screen_height
|
||||
&& monitor_info.rcMonitor.left == 0
|
||||
&& monitor_info.rcMonitor.top == 0
|
||||
&& monitor_info.rcMonitor.right == screen_width
|
||||
&& monitor_info.rcMonitor.bottom == screen_height
|
||||
{
|
||||
// 获取窗口类名
|
||||
let mut buffer = [0u16; MAX_PATH as usize];
|
||||
unsafe { GetClassNameW(foreground_window, &mut buffer) };
|
||||
// 转为String
|
||||
let name = u16_to_string(&buffer);
|
||||
if name != "WorkerW" {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是全屏模式
|
||||
*/
|
||||
pub fn is_fullscreen() -> bool {
|
||||
if let Ok(state) = unsafe { SHQueryUserNotificationState() } {
|
||||
match state {
|
||||
// 全屏(F11 全屏,我试过的所有视频游戏都使用它)
|
||||
QUNS_BUSY => is_fullscreen_window(),
|
||||
// 全屏(Direct3D 应用程序以独占模式运行,即全屏)
|
||||
QUNS_RUNNING_D3D_FULL_SCREEN |
|
||||
// 全屏(一种用于显示全屏演示文稿的特殊模式)
|
||||
QUNS_PRESENTATION_MODE => true,
|
||||
QUNS_NOT_PRESENT | // 非全屏(机器锁定/屏幕保护程序/用户切换)
|
||||
QUNS_ACCEPTS_NOTIFICATIONS |QUNS_QUIET_TIME |QUNS_APP => false,
|
||||
_ =>false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换英文输入法
|
||||
*/
|
||||
pub fn switch_english(window: i32) {
|
||||
// 窗口句柄
|
||||
let hwnd = HWND(window as isize);
|
||||
// 获取输入法上下文
|
||||
let imc = unsafe { ImmGetContext(hwnd) };
|
||||
// 设置输入法的首选转换模式为英文
|
||||
unsafe { ImmSetConversionStatus(imc, IME_CMODE_ALPHANUMERIC, IME_SMODE_AUTOMATIC) };
|
||||
// 释放输入法上下文
|
||||
unsafe { ImmReleaseContext(hwnd, imc) };
|
||||
}
|
||||
|
||||
// 是否回调
|
||||
static mut MOUSE_HOOK_CALL: AtomicBool = AtomicBool::new(false);
|
||||
// ThreadsafeFunction
|
||||
static mut TSFN: Option<ThreadsafeFunction<String>> = None;
|
||||
// 全局鼠标HOOK
|
||||
static mut MOUSE_HOOK: Option<HHOOK> = None;
|
||||
|
||||
// 鼠标事件
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct MouseEvent {
|
||||
event: String,
|
||||
x: i32,
|
||||
y: i32,
|
||||
button: i32,
|
||||
mouse_data: u32,
|
||||
class_name: String,
|
||||
}
|
||||
|
||||
/**
|
||||
* HOOK回调方法
|
||||
*/
|
||||
unsafe extern "system" fn mouse_proc(code: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||
if code >= 0 && MOUSE_HOOK_CALL.load(Ordering::Relaxed) {
|
||||
// 鼠标坐标
|
||||
let msll_struct = lparam.0 as *const MSLLHOOKSTRUCT;
|
||||
let x = (*msll_struct).pt.x;
|
||||
let y = (*msll_struct).pt.y;
|
||||
let mouse_data = (*msll_struct).mouseData;
|
||||
// 类名
|
||||
let mut class_name = String::new();
|
||||
// 参数
|
||||
let param = wparam.0 as u32;
|
||||
// 事件
|
||||
let mut event = String::from("");
|
||||
// 按键类型
|
||||
let mut button = -1;
|
||||
// 判断事件
|
||||
if param == WM_MOUSEMOVE {
|
||||
// 鼠标移动
|
||||
event.push_str("mousemove");
|
||||
} else {
|
||||
// 鼠标操作
|
||||
if param == WM_LBUTTONUP || param == WM_RBUTTONUP || param == WM_MBUTTONUP {
|
||||
event.push_str("mouseup");
|
||||
class_name.push_str(&get_foreground_window_class_name());
|
||||
} else if param == WM_LBUTTONDOWN || param == WM_RBUTTONDOWN || param == WM_MBUTTONDOWN {
|
||||
event.push_str("mousedown");
|
||||
class_name.push_str(&get_foreground_window_class_name());
|
||||
} else if param == WM_MOUSEWHEEL || param == WM_MOUSEHWHEEL {
|
||||
event.push_str("mousewheel");
|
||||
class_name.push_str(&get_foreground_window_class_name());
|
||||
}
|
||||
// 按键类型
|
||||
if param == WM_LBUTTONUP || param == WM_LBUTTONDOWN {
|
||||
button = 1;
|
||||
} else if param == WM_RBUTTONUP || param == WM_RBUTTONDOWN {
|
||||
button = 2;
|
||||
} else if param == WM_MBUTTONUP || param == WM_MBUTTONDOWN {
|
||||
button = 3;
|
||||
} else if param == WM_MOUSEWHEEL {
|
||||
button = 0;
|
||||
} else if param == WM_MOUSEHWHEEL {
|
||||
button = 1;
|
||||
}
|
||||
}
|
||||
if event.is_empty() {
|
||||
if let Some(func) = TSFN.as_ref() {
|
||||
let mouse_event = MouseEvent {
|
||||
event,
|
||||
x,
|
||||
y,
|
||||
mouse_data,
|
||||
button,
|
||||
class_name,
|
||||
};
|
||||
func.call(
|
||||
Ok(serde_json::to_string(&mouse_event).unwrap()),
|
||||
ThreadsafeFunctionCallMode::NonBlocking,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
CallNextHookEx(MOUSE_HOOK.unwrap(), code, wparam, lparam)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建鼠标hook
|
||||
*/
|
||||
pub fn create_mouse_hook(callback: JsFunction) {
|
||||
// 创建回调
|
||||
if let Ok(threadsafe_function) = callback.create_threadsafe_function(0, |ctx| Ok(vec![ctx.value]))
|
||||
{
|
||||
unsafe { TSFN = Some(threadsafe_function) };
|
||||
// 创建鼠标HOOK
|
||||
if let Ok(hook) = unsafe { SetWindowsHookExW(WH_MOUSE_LL, Some(mouse_proc), None, 0) } {
|
||||
unsafe {
|
||||
MOUSE_HOOK = Some(hook);
|
||||
MOUSE_HOOK_CALL.store(true, Ordering::Relaxed);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用鼠标HOOK
|
||||
*/
|
||||
pub fn enable_mouse_hook() {
|
||||
unsafe { MOUSE_HOOK_CALL.store(true, Ordering::Relaxed) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用鼠标HOOK
|
||||
*/
|
||||
pub fn disable_mouse_hook() {
|
||||
unsafe { MOUSE_HOOK_CALL.store(false, Ordering::Relaxed) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取鼠标点击的窗口ClassName
|
||||
*/
|
||||
pub fn get_cursor_pos_window_class_name() -> String {
|
||||
// 获取鼠标位置
|
||||
let mut point: POINT = POINT::default();
|
||||
unsafe {
|
||||
GetCursorPos(&mut point);
|
||||
}
|
||||
// 获取鼠标所在的窗口句柄
|
||||
let hwnd = unsafe { WindowFromPoint(point) };
|
||||
// 获取窗口的ClassName
|
||||
let mut buffer = [0u16; MAX_PATH as usize];
|
||||
unsafe {
|
||||
GetClassNameW(hwnd, &mut buffer);
|
||||
};
|
||||
// 返回
|
||||
u16_to_string(&buffer)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取剪切板文件列表
|
||||
*/
|
||||
pub fn get_clipboard_file_list() -> Vec<String> {
|
||||
match get_clipboard(formats::FileList) {
|
||||
Ok(vec) => vec,
|
||||
Err(_) => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 剪切板是否存在BITMAP
|
||||
*/
|
||||
pub fn clipboard_has_bitmap() -> bool {
|
||||
get_clipboard(formats::Bitmap).is_ok()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取剪切板BITMAP的BASE64
|
||||
*/
|
||||
pub fn get_clipboard_bitmap_base64() -> Option<String> {
|
||||
match get_clipboard(formats::Bitmap) {
|
||||
Ok(data) => Some(format!(
|
||||
"data:image/bmp;base64,{}",
|
||||
general_purpose::STANDARD.encode(data)
|
||||
)),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空回收站
|
||||
*/
|
||||
pub fn empty_recycle_bin(window: i32) {
|
||||
// HWND
|
||||
let hwnd = HWND(window as isize);
|
||||
// 清空回收站
|
||||
unsafe {
|
||||
let _ = SHEmptyRecycleBinW(hwnd, None, SHERB_NOSOUND);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 去掉窗口动画
|
||||
*/
|
||||
pub fn remove_window_animation(window: i32) {
|
||||
// HWND
|
||||
let hwnd = HWND(window as isize);
|
||||
let pvattribute = &mut true as *mut _ as *const _;
|
||||
unsafe {
|
||||
let _ = DwmSetWindowAttribute(
|
||||
hwnd,
|
||||
DWMWA_TRANSITIONS_FORCEDISABLED,
|
||||
pvattribute,
|
||||
std::mem::size_of_val(&pvattribute) as u32,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取APPX列表
|
||||
*/
|
||||
pub fn get_appx_list() -> Vec<HashMap<String, String>> {
|
||||
let mut result_list = vec![];
|
||||
let package_manager: Result<PackageManager, windows::core::Error> = PackageManager::new();
|
||||
if package_manager.is_err() {
|
||||
return result_list;
|
||||
}
|
||||
let packages = package_manager
|
||||
.unwrap()
|
||||
.FindPackagesByUserSecurityId(&HSTRING::default());
|
||||
if packages.is_err() {
|
||||
return result_list;
|
||||
}
|
||||
for package in packages.unwrap() {
|
||||
let mut map = HashMap::new();
|
||||
if let Ok(diaplay_name) = package.DisplayName() {
|
||||
map.insert(String::from("displayName"), diaplay_name.to_string());
|
||||
}
|
||||
if let Ok(path) = package.InstalledPath() {
|
||||
map.insert(String::from("path"), path.to_string());
|
||||
}
|
||||
if let Ok(id) = package.Id() {
|
||||
if let Ok(family_name) = id.FamilyName() {
|
||||
map.insert(String::from("familyName"), family_name.to_string());
|
||||
}
|
||||
}
|
||||
if let Ok(logo) = package.Logo() {
|
||||
if let Ok(path) = logo.Path() {
|
||||
map.insert(String::from("logo"), path.to_string());
|
||||
}
|
||||
}
|
||||
if let Ok(app_list) = package.GetAppListEntriesAsync() {
|
||||
if let Ok(app_list) = app_list.get() {
|
||||
for (index, app) in app_list.into_iter().enumerate() {
|
||||
if app.DisplayInfo().is_err() || app.DisplayInfo().unwrap().DisplayName().is_err() {
|
||||
continue;
|
||||
}
|
||||
map.insert(
|
||||
format!("appName{}", index),
|
||||
app
|
||||
.DisplayInfo()
|
||||
.unwrap()
|
||||
.DisplayName()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
result_list.push(map);
|
||||
}
|
||||
result_list
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前活跃窗口的类名
|
||||
*/
|
||||
fn get_foreground_window_class_name() -> String {
|
||||
let hwnd = unsafe { GetForegroundWindow() };
|
||||
// 获取窗口的ClassName
|
||||
let mut buffer = [0u16; MAX_PATH as usize];
|
||||
unsafe {
|
||||
GetClassNameW(hwnd, &mut buffer);
|
||||
};
|
||||
// 返回
|
||||
u16_to_string(&buffer)
|
||||
}
|
9
packages/addon/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": false,
|
||||
"outDir": "lib",
|
||||
"composite": true
|
||||
},
|
||||
"include": ["index.d.ts"]
|
||||
}
|
@ -13,6 +13,13 @@
|
||||
asarUnpack: ["**/*.node"],
|
||||
npmRebuild: false,
|
||||
files: ["dist", "dist-electron", "native", "!node_modules/**/*"],
|
||||
"extraFiles": [
|
||||
{
|
||||
"from": "../addon/",
|
||||
"to": "./resources/addon/",
|
||||
"filter": ["*.node"]
|
||||
}
|
||||
],
|
||||
win: {
|
||||
appId: "com.dawnlauncher.application",
|
||||
target: [
|
31
packages/electron/electron/commons/betterSqlite3.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { join, dirname } from 'node:path'
|
||||
import Database from 'better-sqlite3-multiple-ciphers'
|
||||
import { getUserDataPath } from '../main/commons'
|
||||
|
||||
let database: Database.Database
|
||||
let cacheDatabase: Database.Database
|
||||
|
||||
function getDataSqlite3() {
|
||||
let filename = join(getUserDataPath(), 'Data.db')
|
||||
database ??= new Database(filename, {
|
||||
nativeBinding: join(process.env.NODE_ENV !== 'development' ? dirname(process.execPath) : '', import.meta.env.VITE_BETTER_SQLITE3_BINDING),
|
||||
})
|
||||
return database
|
||||
}
|
||||
|
||||
function getCacheDataSqlite3() {
|
||||
let filename = join(getUserDataPath(), 'CacheData.db')
|
||||
cacheDatabase ??= new Database(filename, {
|
||||
nativeBinding: join(process.env.NODE_ENV !== 'development' ? dirname(process.execPath) : '', import.meta.env.VITE_BETTER_SQLITE3_BINDING),
|
||||
})
|
||||
return cacheDatabase
|
||||
}
|
||||
|
||||
function getCustomDataSqlite3(filePath: string) {
|
||||
let db = new Database(filePath, {
|
||||
nativeBinding: join(process.env.NODE_ENV !== 'development' ? dirname(process.execPath) : '', import.meta.env.VITE_BETTER_SQLITE3_BINDING),
|
||||
})
|
||||
return db
|
||||
}
|
||||
|
||||
export { getDataSqlite3, getCacheDataSqlite3, getCustomDataSqlite3 }
|
297
packages/electron/electron/main/classification/ipcEvent.ts
Normal file
@ -0,0 +1,297 @@
|
||||
import { Menu, MenuItem, ipcMain, dialog } from 'electron'
|
||||
import { Classification } from '../../../types/classification'
|
||||
import {
|
||||
createAddEditWindow,
|
||||
createAssociateFolderWindow,
|
||||
createSetIconWindow,
|
||||
getItemLayoutMenu,
|
||||
getItemSortMenu,
|
||||
getItemColumnNumber,
|
||||
setAssociateFolder,
|
||||
getItemIconSize,
|
||||
addClassificationByDirectory,
|
||||
getItemShowOnly,
|
||||
createAggregateWindow,
|
||||
updateAggregate,
|
||||
updateExcludeSearch,
|
||||
} from './index'
|
||||
import { add, del, list, selectById, update, updateOrder, updateIcon, hasChildClassification, batchUpdateFixed } from './data'
|
||||
import { setShortcutKey } from '../setting'
|
||||
import { closeWindow, getDot, sendToWebContent } from '../commons/index'
|
||||
|
||||
export default function () {
|
||||
// 获取分类列表
|
||||
ipcMain.on('getClassificationList', (event) => {
|
||||
event.returnValue = list(null)
|
||||
})
|
||||
// 根据ID查询分类
|
||||
ipcMain.on('getClassificationById', (event, args) => {
|
||||
event.returnValue = selectById(args.id)
|
||||
})
|
||||
// 添加分类
|
||||
ipcMain.on('addClassification', (event, args) => {
|
||||
let classification = add(args.parentId, args.name, args.shortcutKey, args.globalShortcutKey)
|
||||
setShortcutKey()
|
||||
event.returnValue = classification
|
||||
})
|
||||
// 更新分类
|
||||
ipcMain.on('updateClassification', (event, args) => {
|
||||
let res = update(args)
|
||||
setShortcutKey()
|
||||
event.returnValue = res
|
||||
})
|
||||
// 更新序号
|
||||
ipcMain.on('updateClassificationOrder', (event, args) => {
|
||||
event.returnValue = updateOrder(args.fromId, args.toId, args.parentId)
|
||||
})
|
||||
// 更新图标
|
||||
ipcMain.on('updateClassificationIcon', (event, args) => {
|
||||
event.returnValue = updateIcon(args.id, args.icon)
|
||||
})
|
||||
// 显示新增/修改窗口
|
||||
ipcMain.on('showClassificationAddEditWindow', () => {
|
||||
if (global.classificationAddEditWindow) {
|
||||
global.classificationAddEditWindow.show()
|
||||
}
|
||||
})
|
||||
// 关闭新增/修改窗口
|
||||
ipcMain.on('closeClassificationAddEditWindow', () => {
|
||||
closeWindow(global.classificationAddEditWindow)
|
||||
})
|
||||
// 显示设置图标窗口
|
||||
ipcMain.on('showClassificationSetIconWindow', () => {
|
||||
if (global.classificationSetIconWindow) {
|
||||
global.classificationSetIconWindow.show()
|
||||
}
|
||||
})
|
||||
// 关闭设置图标窗口
|
||||
ipcMain.on('closeClassificationSetIconWindow', () => {
|
||||
closeWindow(global.classificationSetIconWindow)
|
||||
})
|
||||
// 右键菜单
|
||||
ipcMain.on('showClassificationRightMenu', (event, args) => {
|
||||
// 锁定/解锁分类
|
||||
let lockClassification: boolean = args.lockClassification
|
||||
// 分类
|
||||
let classification: Classification | null = args.classification
|
||||
// 菜单
|
||||
let menuList: Array<MenuItem> = []
|
||||
// 组装菜单
|
||||
if (!classification) {
|
||||
menuList.push(
|
||||
new MenuItem({
|
||||
label: global.language.newClassification,
|
||||
click: () => {
|
||||
// 创建窗口
|
||||
createAddEditWindow(null, null)
|
||||
},
|
||||
}),
|
||||
new MenuItem({ type: 'separator' }),
|
||||
new MenuItem({
|
||||
label: !lockClassification ? global.language.lockClassification : global.language.unlockClassification,
|
||||
click: () => {
|
||||
sendToWebContent('mainWindow', 'onLockClassification', [])
|
||||
},
|
||||
})
|
||||
)
|
||||
} else {
|
||||
if (!classification.parentId && classification.type === 0) {
|
||||
menuList.push(
|
||||
new MenuItem({
|
||||
label: global.language.newSubclassification,
|
||||
click: () => {
|
||||
// 创建窗口
|
||||
createAddEditWindow(null, classification.id)
|
||||
},
|
||||
}),
|
||||
new MenuItem({ type: 'separator' })
|
||||
)
|
||||
}
|
||||
menuList.push(
|
||||
new MenuItem({
|
||||
label: global.language.fixedClassification,
|
||||
icon: classification.data.fixed ? getDot() : null,
|
||||
click: () => {
|
||||
batchUpdateFixed(classification.data.fixed ? null : classification.id)
|
||||
sendToWebContent('mainWindow', 'onUpdateClassificationFixed', classification.data.fixed ? null : classification.id)
|
||||
},
|
||||
})
|
||||
)
|
||||
if (classification.type === 0 || classification.type === 1) {
|
||||
menuList.push(
|
||||
new MenuItem({
|
||||
label: global.language.excludeSearch,
|
||||
icon: classification.data.excludeSearch ? getDot() : null,
|
||||
click: () => {
|
||||
updateExcludeSearch(classification.id, !classification.data.excludeSearch)
|
||||
sendToWebContent('mainWindow', 'onUpdateClassificationExcludeSearch', {
|
||||
id: classification.id,
|
||||
value: !classification.data.excludeSearch,
|
||||
})
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
menuList.push(new MenuItem({ type: 'separator' }))
|
||||
menuList.push(
|
||||
new MenuItem({
|
||||
label: global.language.setIcon,
|
||||
click: () => {
|
||||
// 创建窗口
|
||||
createSetIconWindow(classification.id)
|
||||
},
|
||||
}),
|
||||
new MenuItem({
|
||||
label: global.language.deleteIcon,
|
||||
click: () => {
|
||||
let res = updateIcon(classification.id, null)
|
||||
if (res) {
|
||||
sendToWebContent('mainWindow', 'onUpdateClassificationIcon', {
|
||||
id: classification.id,
|
||||
icon: null,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
)
|
||||
// 子分类、没有子分类的父级分类可以显示
|
||||
if (classification.parentId || (!classification.parentId && !hasChildClassification(classification.id))) {
|
||||
menuList.push(new MenuItem({ type: 'separator' }))
|
||||
if (classification.type === 0 || classification.type === 1) {
|
||||
menuList.push(
|
||||
new MenuItem({
|
||||
label: global.language.associateFolder,
|
||||
click: () => {
|
||||
// 创建窗口
|
||||
createAssociateFolderWindow(classification.id)
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
if (classification.type === 0 || classification.type === 2) {
|
||||
menuList.push(
|
||||
new MenuItem({
|
||||
label: global.language.aggregateClassification,
|
||||
click: () => {
|
||||
// 创建窗口
|
||||
createAggregateWindow(classification.id)
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
// 分割线
|
||||
menuList.push(new MenuItem({ type: 'separator' }))
|
||||
if (classification.type !== 2) {
|
||||
// 排序
|
||||
menuList.push(getItemSortMenu(classification))
|
||||
}
|
||||
// 布局
|
||||
menuList.push(getItemLayoutMenu(classification))
|
||||
// 列数
|
||||
if (
|
||||
!hasChildClassification(classification.id) &&
|
||||
(classification.data.itemLayout === 'list' || (global.setting.item.layout === 'list' && classification.data.itemLayout === 'default'))
|
||||
) {
|
||||
// 只有子级分类或没有子级分类的父级分类并且布局是列表的才显示列数
|
||||
menuList.push(getItemColumnNumber(classification))
|
||||
}
|
||||
// 图标
|
||||
menuList.push(getItemIconSize(classification))
|
||||
// 显示
|
||||
menuList.push(getItemShowOnly(classification))
|
||||
// 编辑/删除
|
||||
menuList.push(
|
||||
new MenuItem({ type: 'separator' }),
|
||||
new MenuItem({
|
||||
label: global.language.edit,
|
||||
click: () => {
|
||||
// 创建窗口
|
||||
createAddEditWindow(classification.id, null)
|
||||
},
|
||||
}),
|
||||
new MenuItem({
|
||||
label: global.language.delete,
|
||||
click: () => {
|
||||
let res = dialog.showMessageBoxSync(global.mainWindow, {
|
||||
message: global.language.deleteClassificationPrompt,
|
||||
buttons: [global.language.ok, global.language.cancel],
|
||||
type: 'question',
|
||||
noLink: true,
|
||||
cancelId: 1,
|
||||
})
|
||||
if (res === 0) {
|
||||
// 删除数据
|
||||
if (del(classification.id)) {
|
||||
// 快捷键
|
||||
setShortcutKey()
|
||||
// 通知前端删除数据
|
||||
sendToWebContent('mainWindow', 'onDeleteClassification', classification.id)
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
// 载入菜单
|
||||
let menu = Menu.buildFromTemplate(menuList)
|
||||
// 菜单显示
|
||||
menu.on('menu-will-show', () => {
|
||||
global.classificationRightMenu = true
|
||||
})
|
||||
// 菜单关闭
|
||||
menu.on('menu-will-close', () => {
|
||||
global.classificationRightMenu = false
|
||||
})
|
||||
// 显示
|
||||
menu.popup()
|
||||
})
|
||||
// 显示关联文件夹窗口
|
||||
ipcMain.on('showClassificationAssociateFolderWindow', () => {
|
||||
if (global.classificationAssociateFolderWindow) {
|
||||
global.classificationAssociateFolderWindow.show()
|
||||
}
|
||||
})
|
||||
// 关闭关联文件夹窗口
|
||||
ipcMain.on('closeClassificationAssociateFolderWindow', () => {
|
||||
closeWindow(global.classificationAssociateFolderWindow)
|
||||
})
|
||||
// 设置关联文件夹
|
||||
ipcMain.on('setClassificationAssociateFolder', (event, args) => {
|
||||
// 分类ID
|
||||
let id: number = args.id
|
||||
// 文件夹路径
|
||||
let dir: string | null = args.dir
|
||||
if (!dir || dir.trim() === '') {
|
||||
dir = null
|
||||
}
|
||||
// 隐藏项
|
||||
let hiddenItems: string | null = args.hiddenItems
|
||||
// 设置
|
||||
event.returnValue = setAssociateFolder(id, dir, hiddenItems)
|
||||
})
|
||||
// 是否拥有子分类
|
||||
ipcMain.on('hasChildClassification', (event, args) => {
|
||||
event.returnValue = hasChildClassification(args)
|
||||
})
|
||||
// 根据文件夹创建分类
|
||||
ipcMain.on('addClassificationByDirectory', (event, args) => {
|
||||
let res = addClassificationByDirectory(args)
|
||||
// 通知页面
|
||||
sendToWebContent('mainWindow', 'onAddClassificationByDirectory', res)
|
||||
})
|
||||
// 显示聚合分类窗口
|
||||
ipcMain.on('showClassificationAggregateWindow', () => {
|
||||
if (global.classificationAggregateWindow) {
|
||||
global.classificationAggregateWindow.show()
|
||||
}
|
||||
})
|
||||
// 关闭聚合分类窗口
|
||||
ipcMain.on('closeClassificationAggregateWindow', () => {
|
||||
closeWindow(global.classificationAggregateWindow)
|
||||
})
|
||||
// 更新聚合分类
|
||||
ipcMain.on('updateClassificationAggregate', (event, args) => {
|
||||
event.returnValue = updateAggregate(args.id, args.sort, args.itemCount)
|
||||
})
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import { app, BrowserWindow, dialog } from "electron";
|
||||
import { release } from "node:os";
|
||||
import { join, dirname } from "node:path";
|
||||
import Addon from "@dawn-launcher/addon";
|
||||
import indexIpcEvent from "./main/ipcEvent";
|
||||
import classificationIpcEvent from "./classification/ipcEvent";
|
||||
import { init as classificationDataInit } from "./classification/data";
|
||||
@ -55,7 +56,7 @@ if (!app.requestSingleInstanceLock()) {
|
||||
app.whenReady().then(() => {
|
||||
try {
|
||||
// addon
|
||||
global.addon = require("../../native/addon.node");
|
||||
global.addon = Addon;
|
||||
// 初始化数据
|
||||
settingDataInit();
|
||||
// 获取语言
|
@ -7,7 +7,8 @@ import {
|
||||
import { CommonItem, Item } from "../../types/item";
|
||||
import { parse, join, extname } from "node:path";
|
||||
import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
||||
import { execSync } from "node:child_process";
|
||||
|
||||
import Addon from "@dawn-launcher/addon";
|
||||
import xml2js from "xml2js";
|
||||
import { newCommonItem, newCommonItemData } from "../../commons/utils/common";
|
||||
import { ShortcutInfo } from "../../types/common";
|
||||
@ -21,7 +22,7 @@ export interface AppxInfo {
|
||||
}
|
||||
|
||||
// addon
|
||||
global.addon = require("../../native/addon.node");
|
||||
global.addon = Addon;
|
||||
|
||||
// 接收消息
|
||||
process.parentPort.once("message", async (event) => {
|
76
packages/electron/electron/types/global.d.ts
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
import { BrowserWindow, Tray } from 'electron'
|
||||
import { FSWatcher } from 'node:fs'
|
||||
import { Setting } from '../../types/setting'
|
||||
import * as Addon from '@dawn-launcher/addon'
|
||||
|
||||
declare global {
|
||||
// addon
|
||||
var addon: typeof Addon
|
||||
// 语言
|
||||
var language: any
|
||||
// 主窗口
|
||||
var mainWindow: BrowserWindow | null
|
||||
// 快速搜索窗口
|
||||
var quickSearchWindow: BrowserWindow | null
|
||||
// 快速搜索窗口是否初始化完成
|
||||
var quickSearchWindowInit: Boolean | null
|
||||
// 设置窗口
|
||||
var settingWindow: BrowserWindow | null
|
||||
// 分类添加/编辑窗口
|
||||
var classificationAddEditWindow: BrowserWindow | null
|
||||
// 分类图标窗口
|
||||
var classificationSetIconWindow: BrowserWindow | null
|
||||
// 关联分类窗口
|
||||
var classificationAssociateFolderWindow: BrowserWindow | null
|
||||
// 聚合分类窗口
|
||||
var classificationAggregateWindow: BrowserWindow | null
|
||||
// 项目添加/编辑窗口
|
||||
var itemAddEditWindow: BrowserWindow | null
|
||||
// 项目网络图标窗口
|
||||
var itemNetworkIconWindow: BrowserWindow | null
|
||||
// 项目SVG图标窗口
|
||||
var itemSVGIconWindow: BrowserWindow | null
|
||||
// 关于窗口
|
||||
var aboutWindow: BrowserWindow | null
|
||||
// 备份/恢复数据窗口
|
||||
var backupRestoreDataWindow: BrowserWindow | null
|
||||
// 存储关联分类监听
|
||||
var associateFolderWatcher: Map<number, AssociateFolderData>
|
||||
// 设置
|
||||
var setting: Setting | null
|
||||
// 托盘
|
||||
var tray: Tray
|
||||
// 主窗口方向
|
||||
var mainWindowDirection: String | null
|
||||
// 停靠在桌面边缘时自动隐藏timer
|
||||
var autoHideTimer: NodeJS.Timeout
|
||||
// 需要失去焦点时隐藏
|
||||
var blurHide: boolean | null
|
||||
// 双击任务栏显示/隐藏timer
|
||||
var doubleClickTaskbarTimer: NodeJS.Timeout
|
||||
// 双击任务栏显示/隐藏counter
|
||||
var doubleClickTaskbarCounter: number
|
||||
// 监测无效项目interval
|
||||
var checkInvalidItemInterval: NodeJS.Timeout
|
||||
// 存储子进程信息
|
||||
var childProcessMap: Map<number, ChildProcessInfo>
|
||||
// 分类右键菜单显示
|
||||
var classificationRightMenu: boolean | null
|
||||
// 项目右键菜单显示
|
||||
var itemRightMenu: boolean | null
|
||||
}
|
||||
|
||||
export interface ChildProcessInfo {
|
||||
utilityProcess: Electron.UtilityProcess
|
||||
port1: Electron.MessagePortMain
|
||||
port2: Electron.MessagePortMain
|
||||
}
|
||||
|
||||
export interface AssociateFolderData {
|
||||
classificationId: number
|
||||
dir: string
|
||||
hiddenItems: string | null
|
||||
watch: FSWatcher
|
||||
}
|
||||
|
||||
export {}
|
73
packages/electron/package.json
Normal file
@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "@dawn-launcher/electron",
|
||||
"productName": "Dawn Launcher",
|
||||
"version": "1.3.5",
|
||||
"main": "dist-electron/main/index.js",
|
||||
"description": "Windows 快捷启动工具,帮助您整理杂乱无章的桌面,分门别类管理您的桌面快捷方式,让您的桌面保持干净整洁。",
|
||||
"author": "FanChenIO",
|
||||
"private": true,
|
||||
"keywords": [
|
||||
"electron",
|
||||
"vite",
|
||||
"vue"
|
||||
],
|
||||
"debug": {
|
||||
"env": {
|
||||
"VITE_DEV_SERVER_URL": "http://127.0.0.1:3344/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit && vite build && electron-builder",
|
||||
"build:sqlite": "npx electron rebuild.js",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.16.3",
|
||||
"@vicons/ionicons5": "^0.12.0",
|
||||
"@vicons/material": "^0.12.0",
|
||||
"@vicons/utils": "^0.1.4",
|
||||
"@vitejs/plugin-vue": "^4.4.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"better-sqlite3-multiple-ciphers": "^9.0.0",
|
||||
"electron": "^26.4.2",
|
||||
"electron-builder": "^24.6.4",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^11.1.3",
|
||||
"naive-ui": "^2.35.0",
|
||||
"postcss": "^8.4.31",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.4.11",
|
||||
"vite-plugin-electron": "^0.15.4",
|
||||
"vue": "^3.3.7",
|
||||
"vue-tsc": "^1.8.22"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dawn-launcher/addon": "workspace:*",
|
||||
"@types/dompurify": "^3.0.3",
|
||||
"@types/mime": "^3.0.2",
|
||||
"@types/request": "^2.48.10",
|
||||
"@types/retry": "^0.12.3",
|
||||
"@types/sortablejs": "^1.15.3",
|
||||
"@types/urijs": "^1.19.22",
|
||||
"@types/xml2js": "^0.4.12",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"dompurify": "^3.0.6",
|
||||
"electron-log": "^5.0.0",
|
||||
"electron-store": "^8.1.0",
|
||||
"mime": "^3.0.0",
|
||||
"pinia": "^2.1.7",
|
||||
"pinyin-pro": "^3.17.0",
|
||||
"request": "^2.88.2",
|
||||
"retry": "^0.13.1",
|
||||
"simplebar": "^6.2.5",
|
||||
"sortablejs": "^1.15.0",
|
||||
"urijs": "^1.19.11",
|
||||
"vue-router": "^4.2.5",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"installConfig": {
|
||||
"hoistingLimits": "workspaces"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 352 B After Width: | Height: | Size: 352 B |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 440 KiB After Width: | Height: | Size: 440 KiB |
Before Width: | Height: | Size: 428 KiB After Width: | Height: | Size: 428 KiB |