Skip to content

Commit

Permalink
Resolve DOO-94 "Electron splash and window styles and transparent can…
Browse files Browse the repository at this point in the history
…vas" (#59)

* Updated Websocket

* Websocket mocks.

* Add logger.

* Fix live collab problems

* Producer sharing.

* Live collab with all edits except opacity

* Consumer is working.

* Disable sharing in a call.

* customizability for rotate, resize, move

* fix small linting problem

* remove console logs

* live collab with redo and undo

* live collab with delete

* live collab for text

* debouncing opacity

* live collab for text custimizability

* Resized video

* Working window alignment

* Sharing works with pan and zoom

* Documentation

* Cleanup.

* Workflow

* Fix workflow

* Add yarn cache dependency path.

* Test emulator cache

* Update tests and install firetools

* Fix node tests

* Electron IPC

* Cleanup ipc listeners.

* Screenshare from electron.

* Draw transparent window, screen sources work, windowed kind of. Must be full screen.

* Notifications bridged.

* Comments.

* More comments.

* Fix linting.

* Fix d.ts type definitions.

* Move install firetools after cache.

* Remove install fire-tools

---------

Co-authored-by: Abdalla Abdelhadi <[email protected]>
  • Loading branch information
Yyassin and AbdallaAbdelhadi authored Jan 8, 2024
1 parent ab4c436 commit 65e5dbc
Show file tree
Hide file tree
Showing 49 changed files with 1,991 additions and 277 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/node.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ jobs:
node-version: "18.x"
cache: "yarn"
cache-dependency-path: "./mod/fastfire/yarn.lock"
- run: npm install -g firebase-tools

- uses: pnpm/action-setup@v2
with:
Expand All @@ -50,6 +49,7 @@ jobs:
key: ${{ runner.os }}-firebase-emulators-${{
hashFiles('./emulator-cache/**') }}
continue-on-error: true
# - run: npm install -g firebase-tools

- name: Get pnpm store directory
shell: bash
Expand Down
4 changes: 2 additions & 2 deletions client/electron/electron-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ declare namespace NodeJS {
* │
* ```
*/
DIST: string
DIST: string;
/** /dist/ or /public/ */
PUBLIC: string
PUBLIC: string;
}
}
10 changes: 10 additions & 0 deletions client/electron/ipc/ipcActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Defines the action handles that can be sent to the main process from the renderer process via IPC.
*/
export const IPC_ACTIONS = {
MAXIMIZE_WINDOW: 'maximize',
UNMAXIMIZE_WINDOW: 'unmaximize',
MINIMIZE_WINDOW: 'minimize',
CLOSE_WINDOW: 'close',
HANDLE_NOTIFICATION: 'notification',
};
86 changes: 86 additions & 0 deletions client/electron/ipc/ipcHandlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { BrowserWindow, IpcMainEvent, ipcMain } from 'electron';
import { IPC_ACTIONS } from './ipcActions';
import { notification } from '../main';

const {
MAXIMIZE_WINDOW,
UNMAXIMIZE_WINDOW,
MINIMIZE_WINDOW,
CLOSE_WINDOW,
HANDLE_NOTIFICATION,
} = IPC_ACTIONS;

/**
* Defines Electron IPC handlers and utility functions for window management.
* @author Yousef Yassin
*/

/**
* Shared state object for storing global flags and variables.
* @property global_RecvMaximizedEventFlag Flag to track the reception of maximize events.
*/
export const shared = { global_RecvMaximizedEventFlag: false };

/**
* Retrieves the BrowserWindow associated with the given IPC event.
* @param event The IPC event.
* @returns The associated BrowserWindow or null if not found.
*/
const getWindow = (event: IpcMainEvent) => {
const webContents = event?.sender;
return BrowserWindow.fromWebContents(webContents);
};

/**
* Handles title bar events such as maximizing, unmaximizing, minimizing, and closing the window.
* @param event The IPC event.
* @param type The type of title bar event.
*/
const handleWindowTitlebarEvent = (event: IpcMainEvent, type: string) => {
if (type === MAXIMIZE_WINDOW) {
shared.global_RecvMaximizedEventFlag = true;
}
const window = getWindow(event);
// The type for title bar events is the same as the method name
window?.[type]();
if (type === CLOSE_WINDOW) {
window?.destroy();
}
};

/**
* Handles notification events by updating notification properties and showing the notification.
* @param _event The IPC event.
* @param params Notification parameters.
* @param params.title The title of the notification.
* @param params.body The body/content of the notification.
*/
const handleNotification = (
_event: IpcMainEvent,
{ title, body }: { title: string; body: string },
) => {
notification.title = title;
notification.body = body;
notification.show();
};

/**
* Mapping of IPC events to their corresponding callback functions.
*/
const eventToCallback = {
[MAXIMIZE_WINDOW]: handleWindowTitlebarEvent,
[UNMAXIMIZE_WINDOW]: handleWindowTitlebarEvent,
[MINIMIZE_WINDOW]: handleWindowTitlebarEvent,
[CLOSE_WINDOW]: handleWindowTitlebarEvent,
[HANDLE_NOTIFICATION]: handleNotification,
};

/**
* Registers IPC event handlers (on the main process)
* based on the defined mappings.
*/
export const registerIPCHandlers = () => {
Object.entries(eventToCallback).forEach(([event, callback]) =>
ipcMain.on(event, callback),
);
};
107 changes: 92 additions & 15 deletions client/electron/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
import { app, BrowserWindow } from 'electron';
import {
app,
BrowserWindow,
desktopCapturer,
ipcMain,
Notification,
Tray,
} from 'electron';
import path from 'node:path';
import { registerIPCHandlers } from './ipc/ipcHandlers';
import { registerGlobalShortcuts, setupTray, setupWindow } from './window';

/**
* @file Main Electron process handling application initialization and window creation.
* This is the entry point for the main process.
* @author Yousef Yassin
*/

// Set environment variables for the build directory structure
// The built directory structure
//
// ├─┬─┬ dist
Expand All @@ -15,33 +31,94 @@ process.env.PUBLIC = app.isPackaged
? process.env.DIST
: path.join(process.env.DIST, '../public');

let win: BrowserWindow | null;
// 🚧 Use ['ENV_NAME'] avoid vite:define plugin - [email protected]
const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL'];
const APP_NAME = 'Doodles';
const iconPath = path.join(process.env.PUBLIC ?? './', 'doodles-icon.png');

function createWindow() {
// Global variables for notification, main window, and tray. They
// must be global to prevent garbage collection.
export let notification: Notification;
let win: BrowserWindow | null;
let tray: Tray | null;

/**
* Creates the main application window.
*/
const createWindow = () => {
win = new BrowserWindow({
icon: path.join(process.env.PUBLIC ?? './', 'electron-vite.svg'),
icon: iconPath,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
webSecurity: false,
},
transparent: true,
frame: false,
title: APP_NAME,
});
setupWindow(win, VITE_DEV_SERVER_URL ?? '');
win.on('closed', () => (win = null));

// Setup the tray icon
tray = new Tray(iconPath);
setupTray(win, tray, APP_NAME);

// Test active push message to Renderer-process.
win.webContents.on('did-finish-load', () => {
win?.webContents.send('main-process-message', new Date().toLocaleString());
// Setup notification with click handling
notification = new Notification({ icon: iconPath });
notification.on('click', () => {
if (!win?.isVisible() || win?.isMinimized()) {
win?.show();
}
});

if (VITE_DEV_SERVER_URL) {
win.loadURL(VITE_DEV_SERVER_URL);
} else {
// win.loadFile('dist/index.html')
win.loadFile(path.join(process.env.DIST ?? './', 'index.html'));
}
}
// Register global shortcuts for the main window
registerGlobalShortcuts(win);
};

// Event handlers for app lifecycle and window focus/blur
app.on('window-all-closed', () => {
win = null;
});
app.on('browser-window-focus', () => {
win && win.webContents.send('focused');
});
app.on('browser-window-blur', () => {
win && win.webContents.send('blurred');
});
// Handle 'close-event' IPC event to hide the window instead of closing
ipcMain.handle('close-event', (e) => {
e.preventDefault();
win && win.hide();
e.returnValue = false;
});

app.whenReady().then(createWindow);
// Application setup when app is ready
app.whenReady().then(() => {
// Register IPC handlers, create the main window.
registerIPCHandlers();
createWindow();
// Bridge the get-sources IPC event, fired from the renderer process,
// to the main process. This is necessary because desktopCapturer
// is only available in the main process.
ipcMain.handle('get-sources', () => {
try {
return desktopCapturer
.getSources({ types: ['window', 'screen'] })
.then((sources) =>
sources.map((source) => ({
...source,
thumbnail: {
// We need to invoke the thumbnail image methods
// in the main process since they can't be
// passed through the context bridge.
dataURL: source.thumbnail.toDataURL(),
aspect: source.thumbnail.getAspectRatio(),
},
})),
);
} catch (e) {
return [];
}
});
});
Loading

0 comments on commit 65e5dbc

Please sign in to comment.