【スポンサーリンク】

[Electron]連続コピーアプリを作ってみた(monclip)

[Electron]連続コピーアプリを作ってみた(monclip)
  • Electronを使用してMac用のクリップボード記録アプリを作成しました。
  • このアプリは、コピーした内容を自動的に記録していき、まとめて編集・利用できるようにします。
  • ネットショッピングやレポート作成時の情報収集に便利で、簡単な操作で使用できます。
\記事が役に立ったらシェアしてね/
【スポンサーリンク】

1. クリップボード記録アプリを作った

自分のMac用に「クリップボード記録ツール(monclip:Monitor Clipboard)」を作りました。
パソコンで文字をコピーするたびに、その内容を自動的に記録してくれます。

クリップボード記録アプリを作った
(ダウンロード:動作は無保証です)
クリップボード記録アプリを作った

自分用のツールなのでテストは不十分です。
動作は完全に無保証です。

例えば、

ネットショッピングで複数の商品の情報を集めるときに便利です。
各商品ページから必要な情報(商品名、価格、送料など)をコピーしていけば、アプリが自動的にそれらをまとめてくれます。
最後に「一時停止」ボタンを押せば、集めた情報をメモ帳やエクセルに一度に貼り付けられます。

また、レポート作成時の参考資料集めにも便利です。
ウェブサイトやPDFから必要な部分をコピーしていけば、後でまとめて見直すことができます。

クリップボード記録アプリを作った

すでに、Clipy(macOS)やClibor(Windows)を使っていますが、自分用ツールを作りました。

クリップボード

クリップボード」は、パソコンやスマートフォンで文字や画像をコピーするときに、その情報が一時的に保存される記憶領域です。
普段は目に見えませんが、コピーした内容を別の場所に貼り付けるときに使われています。

2. アプリの画面構成

この窓には大きく分けて3つの部分があります。

アプリの画面構成
  1. 「オン」と「一時停止」のボタン
  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>
アプリ作成の裏側(clipboard.readText())

たった4つのテキストファイルだけで作れるんだね。

3-1. ビルドの仕方(electron-builder)

ビルドの仕方(electron-builder)

一応、app形式も用意していますが、おそらく環境によっては動かないでしょう。
セキュリティソフトで弾かれたり。
自分で、コードを元にビルドした方が早いと思います。

ソースコードからWindows, macOSなどのアプリケーションが作れます。

ビルドの仕方(electron-builder)

前提として、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
ビルドの仕方(electron-builder)

Windowsの場合は、アイコンを付けるにはちょっと違う気もします。

アプリを作る過程では、いくつかの課題がありました。
例えば、同じ内容を何度もコピーしたときに、重複して記録されないようにする工夫が必要でした。

ビルドの仕方(electron-builder)

みなさんも、日常のちょっとした不便を解消するアイデアを持っているかもしれません。
そんなアイデアが、誰かの役に立つアプリになるかもしれません。
ぜひ、自分のアイデアを大切にしてください。

こちらもどうぞ。
[Electron]今日の日付カレンダーアプリを作った(Calectron)
[Electron]今日の日付カレンダーアプリを作った(Calectron)
Electronを使用して、Windows 7のガジェットにあったカレンダーのようなアプリケーションを作成しました。 このアプリケーションは、今日の日付と曜日を表示するだけのシンプルなものです。 macOS風にデザインを変更し、タイトルバーを削除して、ウィンドウ全体をドラッグできるようにしました。 Windows 7のカレンダーのようなアプリを作った Electronで Windows 7の「ガジェット」にあったカレンダーのようなものを作りました。 ただ、今日の日付・曜日を表...

[PHP]ウェブページの内容を抽出するオンラインツールを作るには?
[PHP]ウェブページの内容を抽出するオンラインツールを作るには?
ウェブページから必要な記事を簡単に抽出するツールを作りました。 使い方は、URLを入力して「Extract Article」ボタンを押すだけで、記事内容が表示されます。 信頼できるソースのURLのみを使用し、個人利用や研究目的に限定してください。 ツールの使い方 インターネット上には膨大な情報があふれていて、必要な情報だけを見るのは大変な作業です。そこで、ウェブページから記事を簡単に抽出できるツールを作りました。 (ツール) ツールの使い方を具体的に説明しましょう。 まず、ウ...

Electronで自作したツールを修正できた【Collup】
Electronで自作したツールを修正できた【Collup】
Electronで自作したファイル整理ツール「Collup」の機能を改善しました。 ドラッグ&ドロップでファイルをリストに追加し、フォルダ名を入力するだけで効率的にファイルを整理できます。 一つ上のフォルダにサブフォルダを作成する仕様に変更し、より使いやすくなりました。 こんなツール 先日、修正しようと思っていた、自作ツールがなんとか動作するようになりました。 プログラムのドラッグ領域にファイルをドラッグ&ドロップすると、リストに追加されます(複数のファイルをまとめてドラッグ...

Electronでレンダラープロセスからメインプロセスに処理を渡して、また戻る【async/await】
Electronでレンダラープロセスからメインプロセスに処理を渡して、また戻る【async/await】
レンダラープロセスから、メインプロセスで処理を移した後、レンダラープロセスに処理を戻すには、どうすればよいかハマったのでメモをしておきます。 結果としては、IPC通信とasync, awaitという非同期の関数を利用することで、解決しました。 プロセス間通信と処理の流れ やりたいことは、・UIの実行ボタンが押して、・端末のファイル操作を行い、・終わってからUIに貯めた入力データをクリアする、ということです。 UIに関するデータは、レンダラープロセスにあります。 ただ、node...

Electron, npm, nodeを久々に更新した(nvm)
Electron, npm, nodeを久々に更新した(nvm)
npm、nodeを久々に更新しようとしたところ、ESMモジュールシステムと古いCommonJSモジュールシステムの競合によるエラーが発生しました。 Node.jsとnpmのバージョンに互換性がない可能性があるため、Node.jsのバージョン管理ツールであるnvmをインストールしました。 nvmを使ってNode.jsの最新LTSバージョンをインストールし、npmを10.7.0にアップデートすることで問題を解決しました。 npmの更新通知 久々にElectronプログラムを作ろう...

毎回ログイン項目の通知が表示される理由(macOS VenturaとClipy)
毎回ログイン項目の通知が表示される理由(macOS VenturaとClipy)
Macを起動すると「ログイン項目」の通知が表示されたのですが、これはクリップボード管理アプリClipyが原因のようです。 Clipyアプリを起動するたびに、アプリ自身がログイン項目を更新する仕様になっているため、macOS Ventura以降では通知が表示されてしまうのです。 この通知を回避するには、Clipyの自動ログインを無効にし、Automatorでオートメーションを作成してログイン項目に追加する方法があります。 ログイン項目の通知があるけれど意味がわからない Macを...
QRコードを読み込むと、関連記事を確認できます。

[Electron]連続コピーアプリを作ってみた(monclip)
【スポンサーリンク】
タイトルとURLをコピーしました