Скопируйте данные буфера обмена между приложениями React в нескольких окнах.

Несколько окон в Electron не используют одну и ту же модель данных и должны использовать межпроцессное взаимодействие для обмена данными.

Несмотря на то, что все окна происходят из одного и того же приложения, каждое окно является экземпляром BrowserWindow, содержащим уникальное веб-представление Chromium. Отправка сообщений между окнами аналогична вкладкам или окнам в веб-браузере.

В веб-браузере вы можете общаться с помощью BroadcastChannels.

После определения канала опубликованные сообщения отправляют события для слушателей:

const channel = new BroadcastChannel("mychannel");
// Send a message:
channel.postMessage("Hello, world");
// Receive a message:
channel.onmessage = (event) => {
  console.log(event);
});

В Electron аналогичная стратегия используется путем отправки сообщений из процесса рендеринга в основной процесс. Затем основной процесс отправляет сообщения всем окнам рендеринга.

Пример буфера обмена

Например, рассмотрим расширенные данные буфера обмена, которые мы хотим разделить между окнами. В каждом окне могут быть запущены уникальные приложения; или несколько экземпляров одного и того же приложения.

Я собираюсь продолжить свой пост о приложении Multiple Window Electron:



В этом примере новые окна можно открывать с помощью Файл ≫ Новое окно, что фактически создает новый экземпляр точно такого же приложения React.

Отправка данных

Из веб-контента любого окна сериализуйте полезную нагрузку данных в JSON и отправьте ее через ipcRenderer в нужный канал. В этом примере клиент собирается отправить данные буфера обмена, используя clipboard-send.

const { ipcRenderer } = require('electron');
const copy = (data) => {
  const json = JSON.stringify(data);
  ipcRenderer.send("clipboard-send", json);
}

В основном потоке (main.dev.js) создайте прослушиватель на ipcMain для обработки событий канала clipboard-send, которые были отправлены из процесса рендеринга любого окна. Затем передайте эти данные обратно во все окна, отправив события канала clipboard-receive:

const { ipcMain } = require('electron');

ipcMain.on('clipboard-send', (event, json) => {
  windows.forEach(window => {
    window.send('clipboard-receive', json);
  });
});

Получение данных

Теперь, когда основной поток отправляет сообщение всем окнам, каждое окно должно прослушивать события на канале clipboard-receive:

const { ipcRenderer } = require('electron');
let clipboard = {};
ipcRenderer.on('clipboard-receive', (event, json) => {
  if (!json) return;
  const data = JSON.parse(json);
  clipboard = data;
});

Поток рендеринга каждого окна теперь может десериализовать полезные данные и кэшировать их локально в буфере обмена.

Полный жизненный цикл:

  • Поток рендеринга одного окна отправляет сообщение основному потоку
  • Основной поток повторяет сообщение во всех окнах
  • Все окна получают событие с полезными данными

Новые окна

Одна ошибка здесь — окна, созданные после того, как данные были переданы. Продолжая пример с буфером обмена, скажем:

  • Одно окно скопировало данные в буфер обмена
  • Новое окно было открыто
  • Пользователь пытается вставить в новое окно — это окно не получило никаких событий, так как оно было создано после того, как сообщение было передано.

В этом случае вы можете создать локальный кеш в основном рендерере; затем автоматически отправлять данные буфера обмена при создании новых окон.

const { ipcMain } = require('electron');
let clipboardCache;
export const createWindow = async () => {
  let newWindow = new BrowserWindow();
  // ...
  newWindow.webContents.on('did-finish-load', () => {
    // Send clipboard data
    newWindow.send('clipboard-receive', clipboardCache);
  });
}

Собираем все вместе

В коде JavaScript, отображаемом в ваших окнах, например в приложении React, отправляйте и получайте события в основной поток. Это напоминает общение между клиентом и сервером:

const { ipcRenderer } = require('electron');
// Local clipboard cache
let clipboard = {};
// Send clipboard data to the main thread
const copy = (data) => {
  const json = JSON.stringify(data);
  ipcRenderer.send("clipboard-send", json);
};
// Listen for incoming clipboard data
ipcRenderer.on('clipboard-receive', (event, json) => {
  if (!json) return;
  
  const data = JSON.parse(json);
  clipboard = data;
});
// Do something with the clipboard data
const paste = () => {
  if (!clipboard) return;
  // ...
};

В основном потоке (main.dev.js), добавляя к предыдущему множественному примеру:

const { ipcMain } = require('electron');
// Clipboard cache
let clipboard;
const windows = new Set();
export const createWindow = async () => {
  if (
    process.env.NODE_ENV === 'development' ||
    process.env.DEBUG_PROD === 'true'
  ) {
    await installExtensions();
  }
  let x, y;
  const currentWindow = BrowserWindow.getFocusedWindow();
  if (currentWindow) {
    const [currentWindowX, currentWindowY] = currentWindow.getPosition();
    x = currentWindowX + 24;
    y = currentWindowY + 24;
  }
  let newWindow = new BrowserWindow({
    show: false,
    width: 1200,
    height: 812,
    x,
    y,
    webPreferences: {
      nodeIntegration: true
    }
  });
  newWindow.loadURL(`file://${__dirname}/app.html`);
  newWindow.webContents.on('did-finish-load', () => {
    if (!newWindow) {
      throw new Error('"newWindow" is not defined');
    }
    if (process.env.START_MINIMIZED) {
      newWindow.minimize();
    } else {
      newWindow.show();
      newWindow.focus();
    }
    // Send clipboard data to new window
    newWindow.send('clipboard-receive', clipboard);
  });
  newWindow.on('closed', () => {
    windows.delete(newWindow);
    newWindow = null;
  });
  newWindow.on('focus', () => {
    const menuBuilder = new MenuBuilder(newWindow);
    menuBuilder.buildMenu();
  });
  windows.add(newWindow);
  return newWindow;
};
app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (windows.size === 0) createWindow();
});

// Echo clipboard data back to all windows:
ipcMain.on('clipboard-send', (event, json) => {
  clipboard = json;
  windows.forEach(window => {
    window.send('clipboard-receive', json);
  });
});