- Electronを使用してMac用のクリップボード記録アプリを作成しました。
- このアプリは、コピーした内容を自動的に記録していき、まとめて編集・利用できるようにします。
- ネットショッピングやレポート作成時の情報収集に便利で、簡単な操作で使用できます。
1. クリップボード記録アプリを作った
自分のMac用に「クリップボード記録ツール(monclip:Monitor Clipboard)」を作りました。
パソコンで文字をコピーするたびに、その内容を自動的に記録してくれます。
自分用のツールなのでテストは不十分です。
動作は完全に無保証です。
ネットショッピングで複数の商品の情報を集めるときに便利です。
各商品ページから必要な情報(商品名、価格、送料など)をコピーしていけば、アプリが自動的にそれらをまとめてくれます。
最後に「一時停止」ボタンを押せば、集めた情報をメモ帳やエクセルに一度に貼り付けられます。
また、レポート作成時の参考資料集めにも便利です。
ウェブサイトやPDFから必要な部分をコピーしていけば、後でまとめて見直すことができます。
すでに、Clipy(macOS)やClibor(Windows)を使っていますが、自分用ツールを作りました。
「クリップボード」は、パソコンやスマートフォンで文字や画像をコピーするときに、その情報が一時的に保存される記憶領域です。
普段は目に見えませんが、コピーした内容を別の場所に貼り付けるときに使われています。
2. アプリの画面構成
この窓には大きく分けて3つの部分があります。
- 「オン」と「一時停止」のボタン
- 「リセット」ボタン
- コピーした内容が表示される大きな枠
使い方はとても簡単です。
「オン」ボタンを押すと、アプリがクリップボードの内容を見張り始めます。
何かをコピーすると、その内容が自動的に大きな枠の中に追加されていきます。
もし監視を止めたいときは、「一時停止」ボタンを押します。
すると、それまでに集めた情報がまとめてクリップボードにコピーされます。
つまり、集めた情報を一度にどこかに貼り付けることができるのです。
「リセット」ボタンは、集めた情報をすべて消して、最初からやり直したいときに使います。
クリップボード監視アプリケーション仕様書(monclip)
1. 概要
このアプリケーションは、Electron を使用して開発されたデスクトップアプリケーションで、システムのクリップボードを監視し、コピーされた内容を記録・管理します。
2. 主要機能
2.1 クリップボード監視
- アプリケーション起動時に自動的にクリップボードの監視を開始します。
- 1秒ごとにクリップボードの内容をチェックします。
- 新しい内容がコピーされた場合、それを記録します。
2.2 ユーザーインターフェース
- ウィンドウサイズ:幅 400px、高さ 600px
- 「オン/一時停止」トグルボタン
- 「リセット」ボタン
- バイト数表示
- クリップボード内容表示用テキストエリア
- クリップボード監視がオンの間は、ウィンドウの背景色が #F2FCF9 に変更されます。
2.3 コンテンツ管理
- 新しくコピーされた内容は、既存の内容の後に 2 行の空行を挟んで追加されます。
- 同じ内容が連続してコピーされた場合、重複して追加されません。
- テキストエリアの内容は直接編集可能です。
- このウィンドウがフォーカスされた状態でコピーされた場合は、追加されません。
2.4 データ永続化
- アプリケーション終了時、内容は 'last_clip.txt' ファイルとして保存されます。
- クリップボードの内容はアプリケーション再起動時には初期化されます。
2.5 その他の機能
- テキストエリアの内容が更新されるたびに、バイト数が計算・表示されます。
- テキストエリアの表示位置は、新しい内容が追加されるたびに最下段まで自動スクロールします。
- テキスト入力用テキストエリアに入力された内容は、自動的にクリップボードにコピーされます。
- 背景色の変更にはスムーズなトランジション効果が適用されます。
3. 動作環境
- 対応 OS:Windows、macOS、Linux
- 開発言語:JavaScript (Node.js, Electron)
4. 特記事項
- すべてのプラットフォーム(Windows、macOS、Linux)で、ウィンドウを閉じるとアプリケーションは完全に終了します。
- アプリケーション起動時、直前のクリップボードの内容は記録されません。監視開始後の新しい内容から記録が始まります。
- アプリケーション起動時、オン/オフの切り替え時、リセット時には、その時点のクリップボードの内容は追加されません。
- 起動後、オン後、またはリセット後、クリップボードに新しい変更があった場合(一回目の変更を含む)、その内容が即座に追加されます。
5. 技術的詳細
- ユーザーデータは、OS 標準のアプリケーションデータディレクトリに保存されます。
- クリップボードの監視状態(オン/オフ)は、メインプロセスからレンダラープロセスに通知され、UIに反映されます。
6. 今後の改善点
- 国際化対応
この仕様書は、アプリケーションの現在の機能と動作を概説しています。開発の進行に伴い、適宜更新される可能性があります。
3. アプリ作成の裏側(clipboard.readText())
このアプリは「Electron」という技術を使って作りました。
Electronは、ウェブサイトを作るための技術を使って、パソコンで動くアプリを作れる便利なツールです。
main.jsの処理の中で clipboard.readText() にアクセスしています。
const { app, BrowserWindow, ipcMain, clipboard } = require('electron');
// ...
function startMonitoring() {
updateLastClipboardContent();
setInterval(() => {
if (isMonitoring && !mainWindow.isFocused()) {
const newContent = clipboard.readText();
// ...
}
}, 1000);
}
{
"name": "monclip",
"version": "1.0.0",
"description": "連続コピーツール",
"main": "main.js",
"scripts": {
"start": "electron .",
"pack": "electron-builder --dir",
"dist": "electron-builder",
"build-win": "electron-builder --win --x64"
},
"author": "chiilabo",
"license": "ISC",
"devDependencies": {
"electron": "^28.1.0",
"electron-builder": "^24.9.1"
},
"build": {
"appId": "com.chiilabo.monclip",
"productName": "Monclip",
"mac": {
"category": "public.app-category.utilities"
},
"win": {
"target": [
"nsis",
"portable"
],
"icon": "icon.ico"
}
}
}
const { app, BrowserWindow, ipcMain, clipboard } = require('electron');
const path = require('path');
const fs = require('fs');
let mainWindow;
let clipboardContent = '';
let isMonitoring = true;
let lastClipboardContent = '';
function createWindow() {
mainWindow = new BrowserWindow({
width: 400,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
});
mainWindow.loadFile('index.html');
mainWindow.on('closed', () => {
app.quit();
});
}
app.whenReady().then(() => {
createWindow();
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
startMonitoring();
});
app.on('before-quit', () => {
const userDataPath = app.getPath('userData');
const filePath = path.join(userDataPath, 'last_clip.txt');
fs.writeFileSync(filePath, clipboardContent);
});
function updateLastClipboardContent() {
lastClipboardContent = clipboard.readText();
}
function startMonitoring() {
updateLastClipboardContent();
setInterval(() => {
if (isMonitoring && !mainWindow.isFocused()) {
const newContent = clipboard.readText();
if (newContent !== lastClipboardContent && newContent !== clipboardContent) {
clipboardContent = newContent;
mainWindow.webContents.send('clipboard-update', clipboardContent);
lastClipboardContent = newContent;
}
}
}, 1000);
}
ipcMain.on('toggle-monitoring', (event, status, text) => {
isMonitoring = status;
if (isMonitoring) {
updateLastClipboardContent();
} else {
// 停止ボタンが押されたときの処理
clipboard.writeText(text);
}
mainWindow.webContents.send('monitoring-status-changed', isMonitoring);
});
ipcMain.on('reset-content', () => {
clipboardContent = '';
updateLastClipboardContent();
mainWindow.webContents.send('clipboard-update', clipboardContent);
});
ipcMain.on('update-content', (event, content) => {
clipboardContent = content;
lastClipboardContent = content;
clipboard.writeText(content);
});
const { ipcRenderer } = require('electron');
const toggleBtn = document.getElementById('toggle-btn');
const resetBtn = document.getElementById('reset-btn');
const clipboardContent = document.getElementById('clipboard-content');
const byteCount = document.getElementById('byte-count');
let isMonitoring = true;
toggleBtn.addEventListener('click', () => {
isMonitoring = !isMonitoring;
toggleBtn.textContent = isMonitoring ? '一時停止' : 'オン';
ipcRenderer.send('toggle-monitoring', isMonitoring, clipboardContent.value);
});
resetBtn.addEventListener('click', () => {
clipboardContent.value = '';
updateByteCount();
ipcRenderer.send('reset-content');
});
clipboardContent.addEventListener('input', () => {
updateByteCount();
ipcRenderer.send('update-content', clipboardContent.value);
});
ipcRenderer.on('clipboard-update', (event, content) => {
if (clipboardContent.value) {
clipboardContent.value += '\n\n';
}
clipboardContent.value += content;
updateByteCount();
clipboardContent.scrollTop = clipboardContent.scrollHeight;
});
ipcRenderer.on('monitoring-status-changed', (event, status) => {
document.body.style.backgroundColor = status ? '#F2FCF9' : '';
});
function updateByteCount() {
const bytes = new TextEncoder().encode(clipboardContent.value).length;
byteCount.textContent = `バイト数: ${bytes}`;
}
// 初期状態の設定
document.body.style.backgroundColor = '#F2FCF9';
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Clipboard Monitor</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
height: 100vh;
margin: 0;
padding: 10px;
box-sizing: border-box;
transition: background-color 0.3s ease;
}
#controls {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
#clipboard-content {
flex-grow: 1;
resize: none;
margin-top: 10px;
}
#byte-count {
margin-bottom: 10px;
}
</style>
</head>
<body>
<div id="controls">
<button id="toggle-btn">一時停止</button>
<button id="reset-btn">リセット</button>
</div>
<div id="byte-count">バイト数: 0</div>
<textarea id="clipboard-content"></textarea>
<script src="renderer.js"></script>
</body>
</html>
たった4つのテキストファイルだけで作れるんだね。
3-1. ビルドの仕方(electron-builder)
一応、app形式も用意していますが、おそらく環境によっては動かないでしょう。
セキュリティソフトで弾かれたり。
自分で、コードを元にビルドした方が早いと思います。
ソースコードからWindows, macOSなどのアプリケーションが作れます。
前提として、Node.jsがインストールされている必要があります。
まず、プロジェクトディレクトリに移動し、必要な依存関係をインストールします。
npm install
そしたら、テスト起動できます。
npm start
アプリケーションとして実行ファイルを作るには、いろいろします。
私の場合は、アイコンファイルを追加して、electron-builderをインストールして、実行しました。
npm install --save-dev electron-builder
npm run dist
package.jsonで”dist”は、”electron-builder”と定義しています。
npm run build-win
Windowsの場合は、アイコンを付けるにはちょっと違う気もします。
アプリを作る過程では、いくつかの課題がありました。
例えば、同じ内容を何度もコピーしたときに、重複して記録されないようにする工夫が必要でした。
みなさんも、日常のちょっとした不便を解消するアイデアを持っているかもしれません。
そんなアイデアが、誰かの役に立つアプリになるかもしれません。
ぜひ、自分のアイデアを大切にしてください。