Skip to content

Commit

Permalink
Comments.
Browse files Browse the repository at this point in the history
  • Loading branch information
Yyassin committed Jan 8, 2024
1 parent c3aa0ac commit 022596b
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 103 deletions.
4 changes: 3 additions & 1 deletion client/electron/ipc/ipcActions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// VALUES MUST BE UNIQUE
/**
* 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',
Expand Down
43 changes: 38 additions & 5 deletions client/electron/ipc/ipcHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { BrowserWindow, IpcMainEvent, ipcMain } from 'electron';
import { IPC_ACTIONS } from './ipcActions';
import { notification } from '../main';

export const shared = { global_RecvMaximizedEventFlag: false };

const {
MAXIMIZE_WINDOW,
UNMAXIMIZE_WINDOW,
Expand All @@ -12,32 +10,63 @@ const {
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);
console.log(type);
window?.[type](); // The type for title bar events is the same as the method name
// 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,
_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,
Expand All @@ -46,6 +75,10 @@ const eventToCallback = {
[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),
Expand Down
123 changes: 39 additions & 84 deletions client/electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@ import {
app,
BrowserWindow,
desktopCapturer,
globalShortcut,
ipcMain,
Menu,
Notification,
Tray,
} from 'electron';
import isDev from 'electron-is-dev';
import path from 'node:path';
import { registerIPCHandlers, shared } from './ipc/ipcHandlers';
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 @@ -26,16 +31,21 @@ process.env.PUBLIC = app.isPackaged
? process.env.DIST
: path.join(process.env.DIST, '../public');

// 🚧 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');

let win: BrowserWindow | null;
// 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;
// 🚧 Use ['ENV_NAME'] avoid vite:define plugin - [email protected]
const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL'];

let isClickThrough = false;
function createWindow() {
/**
* Creates the main application window.
*/
const createWindow = () => {
win = new BrowserWindow({
icon: iconPath,
webPreferences: {
Expand All @@ -45,110 +55,52 @@ function createWindow() {
},
transparent: true,
frame: false,
title: 'Doodles',
});
tray = new Tray(iconPath);
tray.setIgnoreDoubleClickEvents(true);
const trayMenu = Menu.buildFromTemplate([
{
label: 'Show App',
click: () => {
win && win.show();
},
},
{
label: 'Exit',
click: () => {
win && win.close();
},
},
]);
tray.on('click', () => {
if (win === null) return;
if (win.isVisible()) {
win.hide();
} else {
win.show();
}
title: APP_NAME,
});
setupWindow(win, VITE_DEV_SERVER_URL ?? '');
win.on('closed', () => (win = null));

tray.setContextMenu(trayMenu);
tray.setToolTip('Doodles');
// Setup the tray icon
tray = new Tray(iconPath);
setupTray(win, tray, APP_NAME);

// Setup notification with click handling
notification = new Notification({ icon: iconPath });
notification.on('click', () => {
if (!win?.isVisible() || win?.isMinimized()) {
win?.show();
}
});

// Test active push message to Renderer-process.
win.webContents.on('did-finish-load', () => {
win?.webContents.send('main-process-message', new Date().toLocaleString());
});
win.on('maximize', () => {
win && win.webContents.send('maximized');
});

win.on('unmaximize', () => {
win && win.webContents.send('unmaximized');
});
win.on('move', () => {
if (!shared.global_RecvMaximizedEventFlag) {
win?.unmaximize();
} else {
shared.global_RecvMaximizedEventFlag = false;
}
win && win.webContents.send('bounds-changed', win.getBounds());
});
win.on('resize', () => {
win && win.webContents.send('unmaximized');
win && win.webContents.send('bounds-changed', win.getBounds());
});
win.on('closed', () => (win = null));
// Register global shortcuts for the main window
registerGlobalShortcuts(win);
};

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'));
}
globalShortcut.register('Alt+1', () => {
if (isDev) {
win && win.webContents.openDevTools({ mode: 'detach' });
}
});
globalShortcut.register('Ctrl+T', () => {
if (isDev) {
isClickThrough = !isClickThrough;
win?.setIgnoreMouseEvents(isClickThrough);
win?.setAlwaysOnTop(isClickThrough);
win?.webContents.send('click-through', isClickThrough);
}
});
}
// 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;
});

// 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
Expand All @@ -157,6 +109,9 @@ app.whenReady().then(() => {
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(),
},
Expand Down
Loading

0 comments on commit 022596b

Please sign in to comment.