From de138955b5e750d08b64453a3546e8c2970dedcf Mon Sep 17 00:00:00 2001 From: muwoo <2424880409@qq.com> Date: Fri, 3 Sep 2021 18:07:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81win=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E8=BD=AF=E4=BB=B6=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 11 ++- src/renderer/assets/common/darwin-app.js | 120 +++++++++++++++++++++++ src/renderer/assets/common/utils.js | 118 ++-------------------- src/renderer/assets/common/win-app.js | 92 +++++++++++++++++ 4 files changed, 226 insertions(+), 115 deletions(-) create mode 100644 src/renderer/assets/common/darwin-app.js create mode 100644 src/renderer/assets/common/win-app.js diff --git a/package.json b/package.json index 1d06beb..808de62 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "dev": "node .electron-vue/dev-runner.js", "rebuild": " ./node_modules/.bin/electron-rebuild", "rebuild_win": "npm rebuild --runtime=electron --target=11.4.10 --disturl=https://atom.io/download/atom-shell --abi=85", - "rebuild_linux": "npm rebuild --runtime=electron --target=12.0.15 --disturl=https://atom.io/download/atom-shell --abi=87" + "rebuild_linux": "npm rebuild --runtime=electron --target=11.4.10 --disturl=https://atom.io/download/atom-shell --abi=85" }, "build": { "asar": true, @@ -56,7 +56,9 @@ }, "linux": { "icon": "build/icons/", - "publish": ["github"] + "publish": [ + "github" + ] } }, "dependencies": { @@ -67,6 +69,7 @@ "download-git-repo": "^3.0.2", "electron-is-dev": "^2.0.0", "electron-store": "^8.0.0", + "icon-extractor": "^1.0.3", "iohook": "^0.9.3", "is-chinese": "^1.4.2", "jian-pinyin": "^0.2.3", @@ -109,7 +112,7 @@ "css-loader": "^0.28.11", "del": "^3.0.0", "devtron": "^1.4.0", - "electron": "^12.0.15", + "electron": "^11.4.10", "electron-builder": "22.10.5", "electron-debug": "^1.5.0", "electron-devtools-installer": "^2.2.4", @@ -140,7 +143,7 @@ "iohook": { "targets": [ "node-83", - "electron-87" + "electron-85" ], "platforms": [ "darwin", diff --git a/src/renderer/assets/common/darwin-app.js b/src/renderer/assets/common/darwin-app.js new file mode 100644 index 0000000..31ad618 --- /dev/null +++ b/src/renderer/assets/common/darwin-app.js @@ -0,0 +1,120 @@ +import fs from "fs"; +import path from "path"; +import {nativeImage} from "electron"; +import translate from "./translate"; +import {APP_FINDER_PATH} from "./constans"; +import iconvLite from "iconv-lite"; +import bpList from "bplist-parser"; + +const fileLists = []; + +const isZhRegex = /[\u4e00-\u9fa5]/; +const getDisplayNameRegex = /\"(?:CFBundleDisplayName)\"\s\=\s\"(.*)\"/; + +async function getAppZhName(rootPath, appName) { + try { + const ERROR_RESULT = ''; + const systemPath = path.join(rootPath, `${appName}/Contents/Resources/zh_CN.lproj/InfoPlist.strings`); + const customizePath = path.join(rootPath, `${appName}/Contents/Resources/zh-Hans.lproj/InfoPlist.strings`); + let appInfoPath = ''; + + if (fs.existsSync(systemPath)) { + appInfoPath = systemPath; + } else if (fs.existsSync(customizePath)) { + appInfoPath = customizePath; + } else { + return ERROR_RESULT; + } + let appZhName = ''; + if (rootPath == '/Applications') { + const container = iconvLite.decode(fs.readFileSync(appInfoPath), 'utf-16'); + if (container) { + const res = container.match(getDisplayNameRegex); + appZhName = res && res[1]; + } else { + return ERROR_RESULT; + } + } else { + const [{ CFBundleDisplayName = '', CFBundleName = '' }] = await bpList.parseFile(appInfoPath); + appZhName = CFBundleDisplayName || CFBundleName; + } + + return appZhName; + } catch (error) { + return ERROR_RESULT; + } +} + +function getDarwinAppList () { + APP_FINDER_PATH.forEach((searchPath, index) => { + fs.readdir(searchPath, async (err, files) => { + try { + for (let i = 0; i < files.length; i++) { + const appName = files[i]; + const extname = path.extname(appName); + const appSubStr = appName.split(extname)[0]; + if ((extname === '.app' || extname === '.prefPane') >= 0) { + try { + const path1 = path.join(searchPath, `${appName}/Contents/Resources/App.icns`); + const path2 = path.join(searchPath, `${appName}/Contents/Resources/AppIcon.icns`); + const path3 = path.join(searchPath, `${appName}/Contents/Resources/${appSubStr}.icns`); + const path4 = path.join(searchPath, `${appName}/Contents/Resources/${appSubStr.replace(' ', '')}.icns`); + let iconPath = path1; + if (fs.existsSync(path1)) { + iconPath = path1; + } else if (fs.existsSync(path2)) { + iconPath = path2; + } else if (fs.existsSync(path3)) { + iconPath = path3; + } else if (fs.existsSync(path4)) { + iconPath = path4; + } else { + // 性能最低的方式 + const resourceList = fs.readdirSync(path.join(searchPath, `${appName}/Contents/Resources`)); + const iconName = resourceList.filter((file) => path.extname(file) === '.icns')[0]; + iconPath = path.join(searchPath, `${appName}/Contents/Resources/${iconName}`); + } + const img = await nativeImage.createThumbnailFromPath(iconPath, { width: 64, height: 64 }); + + const appZhName = await getAppZhName(searchPath, appName); + + const fileOptions = { + value: 'plugin', + icon: img.toDataURL(), + desc: path.join(searchPath, appName), + type: 'app', + action: `open ${path.join(searchPath, appName).replace(' ', '\\ ')}`, + keyWords: [appSubStr] + }; + + if (appZhName && isZhRegex.test(appZhName)) { + const py = translate(appZhName); + const pinyinArr = py.split(','); + const firstLatter = pinyinArr.map(py => py[0]); + // 拼音 + fileOptions.keyWords.push(pinyinArr.join('')); + // 缩写 + fileOptions.keyWords.push(firstLatter.join('')); + // 中文 + fileOptions.keyWords.push(appZhName); + } + + fileLists.push({ + ...fileOptions, + name: appSubStr, + names: JSON.parse(JSON.stringify(fileOptions.keyWords)), + }); + } catch (e) {} + } + } + } catch (e) { + console.log(e); + } + }); + }); +} + +export const getApp = { + init: getDarwinAppList, + fileLists, +} diff --git a/src/renderer/assets/common/utils.js b/src/renderer/assets/common/utils.js index 21f112f..3c10bf6 100644 --- a/src/renderer/assets/common/utils.js +++ b/src/renderer/assets/common/utils.js @@ -1,19 +1,19 @@ import { WINDOW_MAX_HEIGHT, WINDOW_MIN_HEIGHT, PRE_ITEM_HEIGHT, SYSTEM_PLUGINS } from './constans'; import path from 'path'; import fs from 'fs'; -import process from 'child_process'; +import child_process from 'child_process'; import Store from 'electron-store'; import downloadFile from 'download'; -import { nativeImage, ipcRenderer } from 'electron'; -import { APP_FINDER_PATH } from './constans'; +import { ipcRenderer } from 'electron'; import { getlocalDataFile } from '../../../main/common/utils'; -import iconvLite from 'iconv-lite'; -import bpList from 'bplist-parser'; -import translate from './translate' +const getApp = process.platform === 'win32' ? require('./win-app').getApp : require('./darwin-app').getApp; const store = new Store(); +getApp.init(); +const fileLists = getApp.fileLists; + function getWindowHeight(searchList) { if (!searchList) return WINDOW_MAX_HEIGHT; if (!searchList.length) return WINDOW_MIN_HEIGHT; @@ -50,7 +50,7 @@ async function downloadZip(downloadRepoUrl, name) { const temp_dest = `${plugin_path}/${name}`; // 下载模板 if (await existOrNot(temp_dest)) { - await process.execSync(`rm -rf ${temp_dest}`); + await child_process.execSync(`rm -rf ${temp_dest}`); } await downloadFile(downloadRepoUrl, plugin_path, { extract: true }); @@ -158,110 +158,6 @@ function find(p, target = 'plugin.json') { console.log(e); } } -const fileLists = []; -// 默认搜索目录 -const isZhRegex = /[\u4e00-\u9fa5]/; -const getDisplayNameRegex = /\"(?:CFBundleDisplayName)\"\s\=\s\"(.*)\"/; - -async function getAppZhName(rootPath, appName) { - try { - const ERROR_RESULT = ''; - const systemPath = path.join(rootPath, `${appName}/Contents/Resources/zh_CN.lproj/InfoPlist.strings`); - const customizePath = path.join(rootPath, `${appName}/Contents/Resources/zh-Hans.lproj/InfoPlist.strings`); - let appInfoPath = ''; - - if (fs.existsSync(systemPath)) { - appInfoPath = systemPath; - } else if (fs.existsSync(customizePath)) { - appInfoPath = customizePath; - } else { - return ERROR_RESULT; - } - let appZhName = ''; - if (rootPath == '/Applications') { - const container = iconvLite.decode(fs.readFileSync(appInfoPath), 'utf-16'); - if (container) { - const res = container.match(getDisplayNameRegex); - appZhName = res && res[1]; - } else { - return ERROR_RESULT; - } - } else { - const [{ CFBundleDisplayName = '', CFBundleName = '' }] = await bpList.parseFile(appInfoPath); - appZhName = CFBundleDisplayName || CFBundleName; - } - - return appZhName; - } catch (error) { - return ERROR_RESULT; - } -} -APP_FINDER_PATH.forEach((searchPath, index) => { - fs.readdir(searchPath, async (err, files) => { - try { - for (let i = 0; i < files.length; i++) { - const appName = files[i]; - const extname = path.extname(appName); - const appSubStr = appName.split(extname)[0]; - if ((extname === '.app' || extname === '.prefPane') >= 0) { - try { - const path1 = path.join(searchPath, `${appName}/Contents/Resources/App.icns`); - const path2 = path.join(searchPath, `${appName}/Contents/Resources/AppIcon.icns`); - const path3 = path.join(searchPath, `${appName}/Contents/Resources/${appSubStr}.icns`); - const path4 = path.join(searchPath, `${appName}/Contents/Resources/${appSubStr.replace(' ', '')}.icns`); - let iconPath = path1; - if (fs.existsSync(path1)) { - iconPath = path1; - } else if (fs.existsSync(path2)) { - iconPath = path2; - } else if (fs.existsSync(path3)) { - iconPath = path3; - } else if (fs.existsSync(path4)) { - iconPath = path4; - } else { - // 性能最低的方式 - const resourceList = fs.readdirSync(path.join(searchPath, `${appName}/Contents/Resources`)); - const iconName = resourceList.filter((file) => path.extname(file) === '.icns')[0]; - iconPath = path.join(searchPath, `${appName}/Contents/Resources/${iconName}`); - } - const img = await nativeImage.createThumbnailFromPath(iconPath, { width: 64, height: 64 }); - - const appZhName = await getAppZhName(searchPath, appName); - - const fileOptions = { - value: 'plugin', - icon: img.toDataURL(), - desc: path.join(searchPath, appName), - type: 'app', - action: `open ${path.join(searchPath, appName).replace(' ', '\\ ')}`, - keyWords: [appSubStr] - }; - - if (appZhName && isZhRegex.test(appZhName)) { - const py = translate(appZhName); - const pinyinArr = py.split(','); - const firstLatter = pinyinArr.map(py => py[0]); - // 拼音 - fileOptions.keyWords.push(pinyinArr.join('')); - // 缩写 - fileOptions.keyWords.push(firstLatter.join('')); - // 中文 - fileOptions.keyWords.push(appZhName); - } - - fileLists.push({ - ...fileOptions, - name: appSubStr, - names: JSON.parse(JSON.stringify(fileOptions.keyWords)), - }); - } catch (e) {} - } - } - } catch (e) { - console.log(e); - } - }); -}); function debounce(fn, delay) { let timer; diff --git a/src/renderer/assets/common/win-app.js b/src/renderer/assets/common/win-app.js new file mode 100644 index 0000000..2674b51 --- /dev/null +++ b/src/renderer/assets/common/win-app.js @@ -0,0 +1,92 @@ +import fs from "fs"; +import path from "path"; +import os from 'os'; +import child from 'child_process'; +import iconv from 'iconv-lite'; + +const fileLists = []; + +const getico = apps =>{ + const iconExtractor = require('icon-extractor'); + iconExtractor.emitter.on('icon', function (data) { + let icondir = path.join(os.tmpdir(), 'ProcessIcon') + fs.exists(icondir, exists => { + if (!exists) { fs.mkdirSync(icondir) } + let iconpath = path.join(icondir, `${data.Context}.png`) + fs.exists(iconpath, exists => { + if (!exists) { + fs.writeFile(iconpath, data.Base64ImageData, "base64", err => { + if (err) { console.log(err); } + }) + } + }) + }) + }); + + for (var app of apps) { + if (app.DisplayIcon !== undefined) { + app.DisplayIcon = app.DisplayIcon.split(',')[0]; + } + iconExtractor.getIcon(app.LegalName, app.DisplayIcon); + } +} + +const powershell = (cmd, callback) => { + const ps = child.spawn('powershell', ['-NoProfile', '-Command', cmd], { encoding: 'buffer' }) + let chunks = []; + let err_chunks = []; + ps.stdout.on('data', chunk => { + chunks.push(iconv.decode(chunk, 'cp936')) + }) + ps.stderr.on('data', err_chunk => { + err_chunks.push(iconv.decode(err_chunk, 'cp936')) + }) + ps.on('close', code => { + let stdout = chunks.join(""); + let stderr = err_chunks.join(""); + callback(stdout, stderr) + }) +} + +const getWinAppList = () => { + let filterValues = "Select-Object DisplayName,DisplayIcon,UninstallString,DisplayVersion,InstallDate,Publisher,InstallLocation" + let localMatcine = `Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\* | ${filterValues}`; + let currentUser = `Get-ItemProperty HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\* | ${filterValues}`; + let Wow6432Node = `Get-ItemProperty HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\* | ${filterValues}`; + let x64 = process.arch === 'x64' ? `;${Wow6432Node}` : ''; + powershell(`${localMatcine};${currentUser}${x64}`, (stdout, stderr) => { + let applist = []; + let apps = stdout.trim().replace(/\r\n[ ]{10,}/g,"").split('\r\n\r\n'); + for (var app of apps) { + const dict = {} + let lines = app.split('\r\n') + for (var line of lines) { + if (line) { + const key = line.split(/\s+:\s*/)[0]; + const value = line.split(/\s+:\s*/)[1]; + dict[key] = value; + } + } + if (dict.DisplayName && dict.DisplayIcon && dict.DisplayIcon.indexOf('.exe') >= 0) { + dict.LegalName = dict.DisplayName.replace(/[\\\/\:\*\?\"\<\>\|]/g, ""); + dict.Icon = path.join(os.tmpdir(), 'ProcessIcon', `${encodeURIComponent(dict.LegalName)}.png`); + fileLists.push({ + value: 'plugin', + icon: dict.Icon, + desc: dict.DisplayIcon, + type: 'app', + action: `start "dummyclient" "${dict.DisplayIcon}"`, + keyWords: [dict.DisplayName], + name: dict.DisplayName, + names: [dict.DisplayName], + }); + } + } + getico(fileLists); + }); +} + +export const getApp = { + init: getWinAppList, + fileLists, +};