From 4212668f4d1c54cee4860839d5351aa13355ce8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=9C=E7=89=A9=E3=83=AA=E3=83=B3?= Date: Sat, 22 Jul 2023 22:28:16 +0900 Subject: [PATCH] v1.5.0 (#17) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * たまにウィンドウが重複して消えなくなるのを修正 * たまにウィンドウが重複して消えなくなるのを修正 * 機能追加:ウィンドウごとの座標順ソート * 機能追加:すべてのウィンドウで表示 * パフォーマンスの改善 ----- - フィルタ操作の大部分をNodeプロセスで行うように - 起動中のプロセスチェックをディスプレイの枚数分繰り返していたのを1回に変更 - 差分がなければRendererプロセスに送らないように変更 --- package.json | 3 +- src/main/funcs/events.ts | 35 +- src/main/funcs/helper.ts | 46 +- src/main/funcs/store.ts | 11 +- src/main/funcs/windows.ts | 91 +- src/main/main.ts | 8 + src/renderer/renderer-main.ts | 1 - src/renderer/src/pages/index.vue | 75 +- src/renderer/src/pages/option.vue | 28 +- src/renderer/src/utils.ts | 7 +- yarn.lock | 3790 ----------------------------- 11 files changed, 211 insertions(+), 3884 deletions(-) delete mode 100644 yarn.lock diff --git a/package.json b/package.json index 831745e..0bf46b4 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "@vitejs/plugin-vue": "^4.1.0", "@vue/eslint-config-prettier": "^7.1.0", "@vue/eslint-config-typescript": "^11.0.2", - "add": "^2.0.6", "bulma": "^0.9.4", "dotenv": "^16.1.4", "electron": "^22.3.4", @@ -50,6 +49,8 @@ "eslint": "^8.36.0", "eslint-plugin-vue": "^9.10.0", "html-escaper": "^3.0.3", + "just-diff": "^6.0.2", + "just-diff-apply": "^5.5.0", "less": "^4.1.3", "prettier": "^2.8.7", "sass": "^1.62.1", diff --git a/src/main/funcs/events.ts b/src/main/funcs/events.ts index a4c8926..5dd99dd 100644 --- a/src/main/funcs/events.ts +++ b/src/main/funcs/events.ts @@ -1,10 +1,8 @@ // レンダラープロセスからのメッセージを受信する -import { createOptionWindow, mainWindow, windowPosition } from './windows' -import { activateWindow, grantPermission } from './helper' +import { createOptionWindow, taskbars, windowPosition } from './windows' +import { activateWindow, grantPermission, macWindowProcesses } from './helper' import { ipcMain, screen } from 'electron' -import { store } from './store' - -type LayoutType = 'right' | 'left' | 'bottom' +import { Options, store } from './store' export function setEventHandlers() { ipcMain.on('activeWindow', (_event, windowId) => { @@ -13,13 +11,28 @@ export function setEventHandlers() { ipcMain.on('openOption', () => { createOptionWindow() }) + // ウィンドウの準備ができたらプロセスリストを破棄 + // ホットリロードフロントのdataが破棄されても + // nodeプロセスがそのままなので差分なしになるのを防ぐ + ipcMain.on('windowReady', () => { + macWindowProcesses.splice(0, macWindowProcesses.length) + }) + + ipcMain.on('setOptions', (_event, value: Options) => { + const layout = store.get('options.layout') + const displays = screen.getAllDisplays() + + store.set('options', value) + for (const displayId in taskbars) { + taskbars[displayId].webContents.send('updateOptions', value) - ipcMain.on('setLayout', (_event, layout: LayoutType) => { - store.set('layout', layout) - const primaryDisplay = screen.getPrimaryDisplay() - const position = windowPosition(primaryDisplay, layout) - mainWindow.setBounds(position) - mainWindow.webContents.send('setLayout', layout) + // メインプロセスに作用するものを別途処理 + if (layout != value.layout) { + const targetDisplay = displays.find((display) => display.id.toString() === displayId) + if (targetDisplay) + taskbars[displayId].setBounds(windowPosition(targetDisplay, value.layout)) + } + } }) ipcMain.on('grantPermission', () => { diff --git a/src/main/funcs/helper.ts b/src/main/funcs/helper.ts index 4cd6310..3e5f808 100644 --- a/src/main/funcs/helper.ts +++ b/src/main/funcs/helper.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow } from 'electron' +import { app } from 'electron' import path from 'path' const { spawn, exec } = require('child_process') import { MacWindow } from '@/type' @@ -12,22 +12,22 @@ if (app.isPackaged) { binaryPath = path.join(__dirname, '../../resources', 'TaskbarHelper') } -export function getAndSubmitProcesses(win: BrowserWindow): void { +export const macWindowProcesses: MacWindow[] = [] + +export function getAndSubmitProcesses(): void { let rawData = '' try { const taskbarHelper = spawn(binaryPath, ['list']) - // console.log("tick") taskbarHelper.stdout.on('data', (raw) => { rawData += raw - // console.log(raw) }) - // taskbarHelper.on("data", console.info) taskbarHelper.stderr.on('data', (raw) => { console.error(raw) }) taskbarHelper.on('close', async (code) => { if ((await code) === 0) { - win.webContents.send('process', new Buffer(rawData).toString('utf-8')) + const jsoned = JSON.parse(new Buffer(rawData).toString('utf-8')) + applyProcessChange(jsoned) rawData = '' } }) @@ -40,7 +40,23 @@ export function grantPermission(): void { spawn(binaryPath, ['grant']) } +import { diff } from 'just-diff' +import { diffApply } from 'just-diff-apply' + +function applyProcessChange(newProcesses: typeof macWindowProcesses) { + const result = diff(macWindowProcesses, filterProcesses(newProcesses)) + + if (result.length > 0) { + diffApply(macWindowProcesses, result) + for (const taskbarKey in taskbars) { + taskbars[taskbarKey].webContents.send('process', macWindowProcesses) + } + } +} + import { escape } from 'html-escaper' +import { store } from './store' +import { taskbars } from './windows' // ウィンドウをアクティブにする関数 export function activateWindow(window: MacWindow): void { @@ -66,3 +82,21 @@ end tell // console.log(_stdout); }) } + +/** + * 高さ・幅が低すぎるものと、store.filters から条件に一致するものを除外する + */ +function filterProcesses(windows: MacWindow[]) { + return windows.filter((win) => { + if (win.kCGWindowBounds?.Height < 40) return false + if (win.kCGWindowBounds?.Width < 40) return false + + for (const filter of store.store.filters) { + for (const filterElement of filter) { + if (win[filterElement.property] === undefined) return false + if (win[filterElement.property] == filterElement.is) return false + } + } + return true + }) +} diff --git a/src/main/funcs/store.ts b/src/main/funcs/store.ts index 796951f..b156c02 100644 --- a/src/main/funcs/store.ts +++ b/src/main/funcs/store.ts @@ -1,8 +1,12 @@ -import Store from 'electron-store' +import ElectronStore from 'electron-store' -export const store = new Store({ +type LayoutType = 'right' | 'left' | 'bottom' +export const store = new ElectronStore({ defaults: { - layout: 'bottom', + options: { + layout: 'bottom' as LayoutType, + windowSortByPositionInApp: false + }, filters: [ [{ property: 'kCGWindowIsOnscreen', is: false }], [{ property: 'kCGWindowOwnerName', is: 'Dock' }], @@ -24,3 +28,4 @@ export const store = new Store({ ] } }) +export type Options = typeof store.store.options diff --git a/src/main/funcs/windows.ts b/src/main/funcs/windows.ts index 6580038..c965831 100644 --- a/src/main/funcs/windows.ts +++ b/src/main/funcs/windows.ts @@ -1,53 +1,59 @@ import { join } from 'path' import { is } from '@electron-toolkit/utils' -import { BrowserWindow, screen } from 'electron' -import { getAndSubmitProcesses } from './helper' +import { BrowserWindow, ipcMain, screen, Display } from 'electron' import { store } from './store' type LayoutType = 'right' | 'left' | 'bottom' -export let mainWindow: BrowserWindow +type Taskbar = BrowserWindow +type displayId = Display['id'] +export const taskbars: Record = {} +export type Taskbars = typeof taskbars + export function createWindow() { - // Create the browser window. + const allDisplays = screen.getAllDisplays() + allDisplays.forEach((display) => { + const taskbarWindow = new BrowserWindow({ + webPreferences: { + preload: join(__dirname, '../preload/index.js'), + sandbox: false + }, + titleBarStyle: 'hiddenInset', + autoHideMenuBar: true, + // resizable: false, + movable: false, + maximizable: false, + minimizable: false, + alwaysOnTop: true, + skipTaskbar: true, + show: false, + ...windowPosition(display, store.get('layout') as LayoutType) + }) - const primaryDisplay = screen.getPrimaryDisplay() + // 準備ができたら表示 + taskbarWindow.on('ready-to-show', () => { + taskbarWindow.show() + }) + // 閉じるボタンなどを消す + taskbarWindow.setWindowButtonVisibility(false) - mainWindow = new BrowserWindow({ - webPreferences: { - preload: join(__dirname, '../preload/index.js'), - sandbox: false - }, - titleBarStyle: 'hiddenInset', - autoHideMenuBar: true, - // resizable: false, - movable: false, - maximizable: false, - minimizable: false, - alwaysOnTop: true, - skipTaskbar: true, - show: false, - ...windowPosition(primaryDisplay, store.get('layout') as LayoutType) - }) + // HMR for renderer base on electron-vite cli. + // Load the remote URL for development or the local html file for production. + if (is.dev && process.env['ELECTRON_RENDERER_URL']) { + taskbarWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) + } else { + taskbarWindow.loadFile(join(__dirname, '../renderer/index.html')) + } - // 準備ができたら表示 - mainWindow.on('ready-to-show', () => { - mainWindow.show() + taskbars[display.id] = taskbarWindow + ipcMain.on('windowReady', () => { + taskbarWindow.webContents.send('displayInfo', { + label: display.label, + id: display.id, + workArea: display.workArea + }) + }) }) - // 閉じるボタンなどを消す - mainWindow.setWindowButtonVisibility(false) - - // HMR for renderer base on electron-vite cli. - // Load the remote URL for development or the local html file for production. - if (is.dev && process.env['ELECTRON_RENDERER_URL']) { - mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) - } else { - mainWindow.loadFile(join(__dirname, '../renderer/index.html')) - } - - // 1秒ごとにプロセスのリストを取得 - setInterval(() => { - getAndSubmitProcesses(mainWindow) - }, 1000) } export let optionWindow: BrowserWindow @@ -80,7 +86,10 @@ export function windowPosition( return { width: type === 'bottom' ? display.workArea.width : 210, height: type !== 'bottom' ? display.workArea.height : 60, - x: type === 'right' ? display.workArea.width - 210 : 0, - y: type === 'bottom' ? display.workArea.height - 30 : 0 + x: type === 'right' ? display.workArea.x - 210 : display.workArea.x, + y: + type === 'bottom' + ? display.workArea.height + display.workArea.y - 30 + : display.workArea.height + display.workArea.y } } diff --git a/src/main/main.ts b/src/main/main.ts index 9ebe13a..030f62f 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -3,6 +3,7 @@ import { electronApp, optimizer } from '@electron-toolkit/utils' import { createWindow } from './funcs/windows' import { setEventHandlers } from './funcs/events' +import { getAndSubmitProcesses } from './funcs/helper' // This method will be called when Electron has finished // initialization and is ready to create browser windows. @@ -27,8 +28,15 @@ app.whenReady().then(() => { if (BrowserWindow.getAllWindows().length === 0) createWindow() }) }) + +// イベントハンドラーを設定する setEventHandlers() +// 1秒ごとにプロセスを取得する +setInterval(() => { + getAndSubmitProcesses() +}, 1000) + // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits // explicitly with Cmd + Q. diff --git a/src/renderer/renderer-main.ts b/src/renderer/renderer-main.ts index cfd3b16..2449af2 100644 --- a/src/renderer/renderer-main.ts +++ b/src/renderer/renderer-main.ts @@ -9,6 +9,5 @@ const router = createRouter({ history: createWebHistory('#'), routes }) -console.log(routes) createApp(App).use(router).mount('#app') diff --git a/src/renderer/src/pages/index.vue b/src/renderer/src/pages/index.vue index 71f996b..ce836eb 100644 --- a/src/renderer/src/pages/index.vue +++ b/src/renderer/src/pages/index.vue @@ -1,5 +1,5 @@