This commit is contained in:
DarkSky 2023-12-19 00:28:57 +08:00 committed by GitHub
commit abfd136f8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
128 changed files with 9090 additions and 5194 deletions

4
.gitattributes vendored Normal file
View File

@ -0,0 +1,4 @@
/.yarn/** linguist-vendored
/.yarn/releases/* binary
/.yarn/plugins/**/* binary
/.pnp.* binary linguist-generated

11
.gitignore vendored
View File

@ -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

File diff suppressed because one or more lines are too long

9
.yarnrc.yml Normal file
View 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
View File

@ -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
View File

@ -0,0 +1,9 @@
[workspace]
resolver = "2"
members = ["./packages/addon"]
[profile.release]
lto = true
codegen-units = 1
opt-level = 3
strip = "symbols"

View File

@ -1,6 +0,0 @@
extern crate napi_build;
fn main() {
static_vcruntime::metabuild();
napi_build::setup();
}

View File

@ -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 };

View File

@ -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);
});
}

View File

@ -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 {};

View File

@ -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
View 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
View File

@ -0,0 +1,13 @@
target
Cargo.lock
.cargo
.github
npm
.eslintrc
.prettierignore
rustfmt.toml
yarn.lock
*.node
.yarn
__test__
renovate.json

View File

@ -0,0 +1 @@
nodeLinker: node-modules

View File

@ -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
View 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
View 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
View 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,
}

View 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"
}
}

View File

@ -0,0 +1,2 @@
tab_spaces = 2
edition = "2021"

View File

@ -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()
}

View 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)
}

View File

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"noEmit": false,
"outDir": "lib",
"composite": true
},
"include": ["index.d.ts"]
}

View File

@ -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: [

View 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 }

View 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)
})
}

View File

@ -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();
// 获取语言

View File

@ -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) => {

View 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 {}

View 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"
}
}

View File

Before

Width:  |  Height:  |  Size: 352 B

After

Width:  |  Height:  |  Size: 352 B

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 440 KiB

After

Width:  |  Height:  |  Size: 440 KiB

View File

Before

Width:  |  Height:  |  Size: 428 KiB

After

Width:  |  Height:  |  Size: 428 KiB

Some files were not shown because too many files have changed in this diff Show More