electron#NativeImage TypeScript Examples
The following examples show how to use
electron#NativeImage.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.ts From bluebubbles-server with Apache License 2.0 | 7 votes |
getAttachmentMetadata = async (attachment: Attachment): Promise<Metadata> => {
let metadata: Metadata;
if (attachment.uti !== "com.apple.coreaudio-format" && !attachment.mimeType) return metadata;
if (attachment.uti === "com.apple.coreaudio-format" || attachment.mimeType.startsWith("audio")) {
metadata = await FileSystem.getAudioMetadata(attachment.filePath);
} else if (attachment.mimeType.startsWith("image")) {
metadata = await FileSystem.getImageMetadata(attachment.filePath);
try {
// If we got no height/width data, let's try to fallback to other code to fetch it
if (handledImageMimes.includes(attachment.mimeType) && (!metadata?.height || !metadata?.width)) {
Server().log("Image metadata empty, getting size from NativeImage...", "debug");
// Load the image data
const image = nativeImage.createFromPath(FileSystem.getRealPath(attachment.filePath));
// If we were able to load the image, get the size
if (image) {
const size = image.getSize();
// If the size if available, set the metadata for it
if (size?.height && size?.width) {
// If the metadata is null, let's give it some data
if (metadata === null) metadata = {};
metadata.height = size.height;
metadata.width = size.width;
}
}
}
} catch (ex: any) {
Server().log("Failed to load size data from NativeImage!", "debug");
}
} else if (attachment.mimeType.startsWith("video")) {
metadata = await FileSystem.getVideoMetadata(attachment.filePath);
}
return metadata;
}
Example #2
Source File: utils.ts From bluebubbles-server with Apache License 2.0 | 7 votes |
getBlurHash = async ({
image,
width = null,
height = null,
quality = "good",
componentX = 3,
componentY = 3
}: {
image: NativeImage;
height?: number;
width?: number;
quality?: "good" | "better" | "best";
componentX?: number;
componentY?: number;
}): Promise<string> => {
const resizeOpts: Electron.ResizeOptions = { quality };
if (width) resizeOpts.width = width;
if (height) resizeOpts.height = height;
// Resize the image (with new quality and size if applicable)
const calcImage: NativeImage = image.resize({ width, quality: "good" });
const size = calcImage.getSize();
// Compute and return blurhash
return blurhashEncode(
Uint8ClampedArray.from(calcImage.toBitmap()),
size.width,
size.height,
componentX,
componentY
);
}
Example #3
Source File: TrayMenu.ts From wiregui with MIT License | 6 votes |
constructor(private readonly window: BrowserWindow, isDevelopement: boolean) {
this.tunnels = [];
this.isQuitting = false;
const iconName = "icon_tray";
const iconActiveName = "icon_tray_active";
const isWin32 = process.platform === "win32";
this.icon = nativeImage.createFromPath(getIconsPath(`${iconName}.${isWin32 ? "ico" : "png"}`, isDevelopement));
this.iconActive = nativeImage.createFromPath(getIconsPath(`${iconActiveName}.${isWin32 ? "ico" : "png"}`, isDevelopement));
this.tray = new Tray(this.icon);
this.tray.setToolTip("Wire GUI");
this.contextMenu = Menu.buildFromTemplate(this.mountTrayMenuItems());
this.tray.setContextMenu(this.contextMenu);
ipcMain.on("WgConfigStateChange", (event, args: TunnelInfo[]) => {
this.tunnels = args;
this.contextMenu = Menu.buildFromTemplate(this.mountTrayMenuItems());
this.tray.setContextMenu(this.contextMenu);
// When calling setContextMenu alongside setImage
// For some reason electron reloads the tray image
// With this hack this doesn't happen
setTimeout(() => {
this.tray.setImage(this.tunnels.some(tunnel => tunnel.active) ? this.iconActive : this.icon);
}, 100);
});
window.on("close", (event) => {
if (!this.isQuitting) {
event.preventDefault();
window.hide();
}
return false;
});
}
Example #4
Source File: attachmentInterface.ts From bluebubbles-server with Apache License 2.0 | 6 votes |
static async getBlurhash({
filePath,
width = null,
height = null,
componentX = 3,
componentY = 3,
quality = "good"
}: any): Promise<string> {
const bh = await getBlurHash({
image: nativeImage.createFromPath(filePath),
width,
height,
quality,
componentX,
componentY
});
return bh;
}
Example #5
Source File: main.ts From desktop with GNU Affero General Public License v3.0 | 6 votes |
WindowIcon = nativeImage.createFromPath(
path.resolve(
App.getAppPath(),
"assets",
// MacOS has special size and naming requirements for tray icons
// https://stackoverflow.com/questions/41664208/electron-tray-icon-change-depending-on-dark-theme/41998326#41998326
process.platform == "darwin" ? "iconTemplate.png" : "icon.png",
),
)
Example #6
Source File: uniShell.ts From kaiheila-universal with MIT License | 6 votes |
private initializeTray(): void {
// Initialize Tray
if (isMac) {
const img = nativeImage.createFromPath(this.iconPath).resize({
width: 16,
height: 16,
})
img.setTemplateImage(true)
this.tray = new Tray(img)
} else {
this.tray = new Tray(this.iconPath)
}
this.tray.setToolTip('开黑啦')
this.tray.setContextMenu(Menu.buildFromTemplate(this.trayMenuTemplate))
this.tray.on('click', (): void => {
this.mainWindow.show()
})
}
Example #7
Source File: process.ts From DittoPlusPlus with MIT License | 6 votes |
writeClipboard = (clipItem: ClipItemDoc) => {
switch (clipItem.type) {
case 'file':
this.lastClipId = clipItem.path;
this.writeClipboardFiles(clipItem);
break;
case 'text':
this.lastClipId = clipItem.text;
clipboard.writeText(clipItem.text);
break;
case 'image':
const image = nativeImage.createFromPath(
path.join(this.imagesDir, `${clipItem._id}.png`),
);
this.lastClipId = image.getBitmap().toString();
clipboard.writeImage(image);
break;
}
};
Example #8
Source File: main.ts From DittoPlusPlus with MIT License | 5 votes |
function onReady() {
createWindow();
registerKeyboardShortcuts();
app.whenReady().then(() => {
tray = new Tray(
nativeImage.createFromPath(iconPath).resize({width: 16, height: 16}),
);
const contextMenu = Menu.buildFromTemplate([
{
label: 'Show/Hide',
type: 'normal',
click: () => {
toggleWindowVisibility();
},
},
{
label: 'Quit',
type: 'normal',
click: () => {
isQuitting = true;
app.quit();
},
},
]);
tray.on('click', () => {
if (process.platform !== 'darwin') {
toggleWindowVisibility();
}
});
tray.setToolTip('Ditto++');
tray.setContextMenu(contextMenu);
if (process.env.IS_Packaged) {
app.setLoginItemSettings({
openAtLogin: true,
openAsHidden: true,
args: [
'--processStart',
`"${exeName}"`,
'--process-start-args',
`"--hidden"`,
],
});
}
ipcMain.on(GlobalEvents.ShowWindow, () => {
showWindow();
});
ipcMain.on(GlobalEvents.HideWindow, () => {
hideWindow();
});
});
}
Example #9
Source File: tray.ts From awakened-poe-trade with MIT License | 5 votes |
export function createTray () {
tray = new Tray(
nativeImage.createFromPath(path.join(__dirname, process.env.STATIC!, process.platform === 'win32' ? 'icon.ico' : 'icon.png'))
)
tray.setToolTip('Awakened PoE Trade')
rebuildTrayMenu()
}
Example #10
Source File: main.ts From blockcore-hub with MIT License | 5 votes |
function createTray() {
// Put the app in system tray
let trayIcon;
if (serve) {
// During development, we can read the icon directly from src folder.
trayIcon = nativeImage.createFromPath('./app.ico');
} else {
// This icon is manually included using "extraResources" on electron-builder.json.
trayIcon = nativeImage.createFromPath(path.resolve(__dirname, '../../resources/app.ico'));
}
const systemTray = new Tray(trayIcon);
const contextMenu = Menu.buildFromTemplate([
{
label: 'Hide/Show',
click: () => {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
}
},
{
label: 'Exit',
click: () => {
mainWindow.close();
}
}
]);
systemTray.setToolTip(coin.tooltip);
systemTray.setContextMenu(contextMenu);
systemTray.on('click', () => {
if (!mainWindow.isVisible()) {
mainWindow.show();
}
if (!mainWindow.isFocused()) {
mainWindow.focus();
}
});
app.on('window-all-closed', () => {
if (systemTray) {
systemTray.destroy();
}
});
}
Example #11
Source File: main.ts From rocketredis with MIT License | 5 votes |
function createWindow() {
const icon = nativeImage.createFromPath(`${app.getAppPath()}/build/icon.png`)
if (app.dock) {
app.dock.setIcon(icon)
}
mainWindow = new BrowserWindow({
...getWindowBounds(),
icon,
minWidth: 1000,
minHeight: 600,
frame: false,
transparent: true,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
})
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:4000')
} else {
mainWindow.loadURL(
url.format({
pathname: path.join(__dirname, 'renderer/index.html'),
protocol: 'file:',
slashes: true
})
)
}
mainWindow.on('close', () => {
setWindowBounds(mainWindow?.getBounds())
})
mainWindow.on('closed', () => {
mainWindow = null
})
}
Example #12
Source File: main.ts From EXOS-Core with MIT License | 5 votes |
function createTray() {
// Put the app in system tray
let trayIcon;
if (serve) {
trayIcon = nativeImage.createFromPath('./src/assets/exos-core/icon-tray.ico');
} else {
trayIcon = nativeImage.createFromPath(path.resolve(__dirname, '../../resources/dist/assets/exos-core/icon-tray.ico'));
}
const systemTray = new Tray(trayIcon);
const contextMenu = Menu.buildFromTemplate([
{
label: 'Hide/Show',
click: () => {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
}
},
{
label: 'Exit',
click: () => {
mainWindow.close();
}
}
]);
systemTray.setToolTip(coin.tooltip);
systemTray.setContextMenu(contextMenu);
systemTray.on('click', () => {
if (!mainWindow.isVisible()) {
mainWindow.show();
}
if (!mainWindow.isFocused()) {
mainWindow.focus();
}
});
app.on('window-all-closed', () => {
if (systemTray) {
systemTray.destroy();
}
});
}
Example #13
Source File: TrayMenu.ts From wiregui with MIT License | 5 votes |
private readonly iconActive: nativeImage;
Example #14
Source File: TrayMenu.ts From wiregui with MIT License | 5 votes |
private readonly icon: nativeImage;
Example #15
Source File: browser-action.ts From electron-browser-shell with GNU General Public License v3.0 | 5 votes |
private handleCrxRequest = (
request: Electron.ProtocolRequest,
callback: (response: Electron.ProtocolResponse) => void
) => {
debug('%s', request.url)
let response: Electron.ProtocolResponse
try {
const url = new URL(request.url)
const { hostname: requestType } = url
switch (requestType) {
case 'extension-icon': {
const tabId = url.searchParams.get('tabId')
const fragments = url.pathname.split('/')
const extensionId = fragments[1]
const imageSize = parseInt(fragments[2], 10)
const resizeType = parseInt(fragments[3], 10) || ResizeType.Up
const extension = this.ctx.session.getExtension(extensionId)
let iconDetails: chrome.browserAction.TabIconDetails | undefined
const action = this.actionMap.get(extensionId)
if (action) {
iconDetails = (tabId && action.tabs[tabId]?.icon) || action.icon
}
let iconImage
if (extension && iconDetails) {
if (typeof iconDetails.path === 'string') {
const iconAbsPath = resolveExtensionPath(extension, iconDetails.path)
if (iconAbsPath) iconImage = nativeImage.createFromPath(iconAbsPath)
} else if (typeof iconDetails.path === 'object') {
const imagePath = matchSize(iconDetails.path, imageSize, resizeType)
const iconAbsPath = imagePath && resolveExtensionPath(extension, imagePath)
if (iconAbsPath) iconImage = nativeImage.createFromPath(iconAbsPath)
} else if (typeof iconDetails.imageData === 'string') {
iconImage = nativeImage.createFromDataURL(iconDetails.imageData)
} else if (typeof iconDetails.imageData === 'object') {
const imageData = matchSize(iconDetails.imageData as any, imageSize, resizeType)
iconImage = imageData ? nativeImage.createFromDataURL(imageData) : undefined
}
}
if (iconImage) {
response = {
statusCode: 200,
mimeType: 'image/png',
data: iconImage.toPNG(),
}
} else {
response = { statusCode: 400 }
}
break
}
default: {
response = { statusCode: 400 }
}
}
} catch (e) {
console.error(e)
response = {
statusCode: 500,
}
}
callback(response)
}
Example #16
Source File: common.ts From electron-browser-shell with GNU General Public License v3.0 | 5 votes |
getIconImage = (extension: Electron.Extension) => {
const iconPath = getIconPath(extension)
const iconAbsolutePath = iconPath && resolveExtensionPath(extension, iconPath)
return iconAbsolutePath ? nativeImage.createFromPath(iconAbsolutePath) : undefined
}
Example #17
Source File: main.ts From StraxUI with MIT License | 5 votes |
function createTray(): void {
// Put the app in system tray
const iconPath = 'stratis/icon-16.png';
let trayIcon;
if (serve) {
trayIcon = nativeImage.createFromPath('./src/assets/images/' + iconPath);
} else {
trayIcon = nativeImage.createFromPath(path.resolve(__dirname, '../../resources/src/assets/images/' + iconPath));
}
const systemTray = new Tray(trayIcon);
const contextMenu = Menu.buildFromTemplate([
{
label: 'Hide/Show',
click: function (): void {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
}
},
{
label: 'Exit',
click: function (): void {
app.quit();
}
}
]);
systemTray.setToolTip(applicationName);
systemTray.setContextMenu(contextMenu);
systemTray.on('click', function () {
if (!mainWindow.isVisible()) {
mainWindow.show();
}
if (!mainWindow.isFocused()) {
mainWindow.focus();
}
});
app.on('window-all-closed', function () {
if (systemTray) {
systemTray.destroy();
}
});
}
Example #18
Source File: attachmentRouter.ts From bluebubbles-server with Apache License 2.0 | 5 votes |
static async download(ctx: RouterContext, _: Next) {
const { guid } = ctx.params;
const { height, width, quality, original } = ctx.request.query;
const useOriginal = isTruthyBool(original as string);
// Fetch the info for the attachment by GUID
const attachment = await Server().iMessageRepo.getAttachment(guid);
if (!attachment) throw new NotFound({ error: "Attachment does not exist!" });
let aPath = FileSystem.getRealPath(attachment.filePath);
let mimeType = attachment.getMimeType();
Server().log(`Handling attachment download for GUID: ${guid}`, "debug");
Server().log(`Detected MIME Type: ${mimeType}`, "debug");
Server().log(`Handling attachment with MIME Type: ${attachment?.mimeType}`);
// If we want to resize the image, do so here
if (!useOriginal) {
const converters = [convertImage, convertAudio];
for (const conversion of converters) {
// Try to convert the attachments using available converters
const newPath = await conversion(attachment, { originalMimeType: mimeType });
if (newPath) {
aPath = newPath;
mimeType = attachment.mimeType ?? mimeType;
break;
}
}
// Handle resizing the image
if (mimeType.startsWith("image/") && mimeType !== "image/gif" && (quality || width || height)) {
const opts: Partial<Electron.ResizeOptions> = {};
// Parse opts
const parsedWidth = width ? Number.parseInt(width as string, 10) : null;
const parsedHeight = height ? Number.parseInt(height as string, 10) : null;
let newName = attachment.transferName;
if (quality) {
newName += `.${quality as string}`;
opts.quality = quality as string;
}
if (parsedHeight) {
newName += `.${parsedHeight}`;
opts.height = parsedHeight;
}
if (parsedWidth) {
newName += `.${parsedWidth}`;
opts.width = parsedWidth;
}
// See if we already have a cached attachment
if (FileSystem.cachedAttachmentExists(attachment, newName)) {
aPath = FileSystem.cachedAttachmentPath(attachment, newName);
} else {
let image = nativeImage.createFromPath(aPath);
image = image.resize(opts);
FileSystem.saveCachedAttachment(attachment, newName, image.toPNG());
aPath = FileSystem.cachedAttachmentPath(attachment, newName);
}
// Force setting it to a PNG because all resized images are PNGs
mimeType = "image/png";
}
}
Server().log(`Sending attachment (${mimeType}) with path: ${aPath}`, "debug");
return new FileStream(ctx, aPath, mimeType).send();
}
Example #19
Source File: main.ts From EXOS-Core with MIT License | 4 votes |
function createWindow() {
// Create the browser window.
let iconpath;
if (serve) {
iconpath = nativeImage.createFromPath('./src/assets/exos-core/logo-tray.png');
} else {
iconpath = nativeImage.createFromPath(path.resolve(__dirname, '..//..//resources//dist//assets//exos-core//logo-tray.png'));
}
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
mainWindow = new BrowserWindow({
width: 1366,
minWidth: 1100,
icon: iconpath,
height: 768,
frame: true,
center: true,
resizable: true,
title: 'EXOS Core',
webPreferences: { webSecurity: false, nodeIntegration: true, contextIsolation: false }
});
contents = mainWindow.webContents;
mainWindow.setMenu(null);
// Make sure links that open new window, e.g. target="_blank" launches in external window (browser).
mainWindow.webContents.on('new-window', function (event, linkUrl) {
event.preventDefault();
shell.openExternal(linkUrl);
});
if (serve) {
require('electron-reload')(__dirname, {
electron: require(`${__dirname}/node_modules/electron`)
});
writeLog('Creating Window and loading: http://localhost:4200?coin=' + coin.identity);
mainWindow.loadURL('http://localhost:4200?coin=' + coin.identity);
} else {
writeLog('Creating Window and loading: ' + path.join(__dirname, 'dist/index.html'));
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'dist/index.html'),
protocol: 'file:',
slashes: true
}));
}
if (serve) {
mainWindow.webContents.openDevTools();
}
autoUpdater.checkForUpdatesAndNotify();
// Emitted when the window is going to close.
mainWindow.on('close', (event) => {
writeLog(`close event on mainWindow was triggered. Calling shutdown method. Daemon state is: ${daemonState}.`);
// If daemon stopping has not been triggered, it means it likely never started and user clicked Exit on the error dialog. Exit immediately.
// Additionally if it was never started, it is already stopped.
if (daemonState === DaemonState.Stopping || daemonState === DaemonState.Stopped) {
writeLog('Daemon was in stopping mode, so exiting immediately without showing status any longer.');
return true;
} else {
// If shutdown not initated yet, perform it.
if (daemonState === DaemonState.Started) {
writeLog('Daemon shutdown initiated... preventing window close, and informing UI that shutdown is in progress.');
daemonState = DaemonState.Stopping;
event.preventDefault();
contents.send('daemon-exiting');
// Call the shutdown while we show progress window.
shutdown(() => { });
return true;
} else { // Else, allow window to be closed. This allows users to click X twice to immediately close the window.
writeLog('ELSE in the CLOSE event. Should only happen on double-click on exit button.');
}
}
});
mainWindow.on('minimize', (event) => {
if (!settings.showInTaskbar) {
event.preventDefault();
// mainWindow.hide();
}
});
// Emitted when the window is closed.
mainWindow.on('closed', () => {
// Dereference the window object, usually you would store window
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null;
});
}
Example #20
Source File: setup.ts From angular-sqlite-app-starter with MIT License | 4 votes |
async init(): Promise<void> {
const icon = nativeImage.createFromPath(
join(app.getAppPath(), 'assets', process.platform === 'win32' ? 'appIcon.ico' : 'appIcon.png')
);
this.mainWindowState = windowStateKeeper({
defaultWidth: 1000,
defaultHeight: 800,
});
// Setup preload script path and construct our main window.
const preloadPath = join(app.getAppPath(), 'build', 'src', 'preload.js');
this.MainWindow = new BrowserWindow({
icon,
show: false,
x: this.mainWindowState.x,
y: this.mainWindowState.y,
width: this.mainWindowState.width,
height: this.mainWindowState.height,
webPreferences: {
nodeIntegration: true,
contextIsolation: true,
// Use preload to inject the electron varriant overrides for capacitor plugins.
// preload: join(app.getAppPath(), "node_modules", "@capacitor-community", "electron", "dist", "runtime", "electron-rt.js"),
preload: preloadPath,
},
});
this.mainWindowState.manage(this.MainWindow);
if (this.CapacitorFileConfig.backgroundColor) {
this.MainWindow.setBackgroundColor(this.CapacitorFileConfig.electron.backgroundColor);
}
// If we close the main window with the splashscreen enabled we need to destory the ref.
this.MainWindow.on('closed', () => {
if (this.SplashScreen?.getSplashWindow() && !this.SplashScreen.getSplashWindow().isDestroyed()) {
this.SplashScreen.getSplashWindow().close();
}
});
// When the tray icon is enabled, setup the options.
if (this.CapacitorFileConfig.electron?.trayIconAndMenuEnabled) {
this.TrayIcon = new Tray(icon);
this.TrayIcon.on('double-click', () => {
if (this.MainWindow) {
if (this.MainWindow.isVisible()) {
this.MainWindow.hide();
} else {
this.MainWindow.show();
this.MainWindow.focus();
}
}
});
this.TrayIcon.on('click', () => {
if (this.MainWindow) {
if (this.MainWindow.isVisible()) {
this.MainWindow.hide();
} else {
this.MainWindow.show();
this.MainWindow.focus();
}
}
});
this.TrayIcon.setToolTip(app.getName());
this.TrayIcon.setContextMenu(Menu.buildFromTemplate(this.TrayMenuTemplate));
}
// Setup the main manu bar at the top of our window.
Menu.setApplicationMenu(Menu.buildFromTemplate(this.AppMenuBarMenuTemplate));
// If the splashscreen is enabled, show it first while the main window loads then dwitch it out for the main window, or just load the main window from the start.
if (this.CapacitorFileConfig.electron?.splashScreenEnabled) {
this.SplashScreen = new CapacitorSplashScreen({
imageFilePath: join(
app.getAppPath(),
'assets',
this.CapacitorFileConfig.electron?.splashScreenImageName ?? 'splash.png'
),
windowWidth: 400,
windowHeight: 400,
});
this.SplashScreen.init(this.loadMainWindow, this);
} else {
this.loadMainWindow(this);
}
// Security
this.MainWindow.webContents.setWindowOpenHandler((details) => {
if (!details.url.includes(this.customScheme)) {
return { action: 'deny' };
} else {
return { action: 'allow' };
}
});
this.MainWindow.webContents.on('will-navigate', (event, _newURL) => {
if (!this.MainWindow.webContents.getURL().includes(this.customScheme)) {
event.preventDefault();
}
});
// Link electron plugins into the system.
setupCapacitorElectronPlugins();
// When the web app is loaded we hide the splashscreen if needed and show the mainwindow.
this.MainWindow.webContents.on('dom-ready', () => {
if (this.CapacitorFileConfig.electron?.splashScreenEnabled) {
this.SplashScreen.getSplashWindow().hide();
}
if (!this.CapacitorFileConfig.electron?.hideMainWindowOnLaunch) {
this.MainWindow.show();
}
setTimeout(() => {
if (electronIsDev) {
this.MainWindow.webContents.openDevTools();
}
CapElectronEventEmitter.emit('CAPELECTRON_DeeplinkListenerInitialized', '');
}, 400);
});
}
Example #21
Source File: setup.ts From react-sqlite-app-starter with MIT License | 4 votes |
async init(): Promise<void> {
const icon = nativeImage.createFromPath(
join(
app.getAppPath(),
'assets',
process.platform === 'win32' ? 'appIcon.ico' : 'appIcon.png',
),
);
this.mainWindowState = windowStateKeeper({
defaultWidth: 1000,
defaultHeight: 800,
});
// Setup preload script path and construct our main window.
const preloadPath = join(app.getAppPath(), 'build', 'src', 'preload.js');
this.MainWindow = new BrowserWindow({
icon,
show: false,
x: this.mainWindowState.x,
y: this.mainWindowState.y,
width: this.mainWindowState.width,
height: this.mainWindowState.height,
webPreferences: {
nodeIntegration: true,
contextIsolation: true,
// Use preload to inject the electron varriant overrides for capacitor plugins.
// preload: join(app.getAppPath(), "node_modules", "@capacitor-community", "electron", "dist", "runtime", "electron-rt.js"),
preload: preloadPath,
},
});
this.mainWindowState.manage(this.MainWindow);
if (this.CapacitorFileConfig.electron?.backgroundColor) {
this.MainWindow.setBackgroundColor(
this.CapacitorFileConfig.electron.backgroundColor,
);
}
// If we close the main window with the splashscreen enabled we need to destory the ref.
this.MainWindow.on('closed', () => {
if (
this.SplashScreen?.getSplashWindow() &&
!this.SplashScreen.getSplashWindow().isDestroyed()
) {
this.SplashScreen.getSplashWindow().close();
}
});
// When the tray icon is enabled, setup the options.
if (this.CapacitorFileConfig.electron?.trayIconAndMenuEnabled) {
this.TrayIcon = new Tray(icon);
this.TrayIcon.on('double-click', () => {
if (this.MainWindow) {
if (this.MainWindow.isVisible()) {
this.MainWindow.hide();
} else {
this.MainWindow.show();
this.MainWindow.focus();
}
}
});
this.TrayIcon.on('click', () => {
if (this.MainWindow) {
if (this.MainWindow.isVisible()) {
this.MainWindow.hide();
} else {
this.MainWindow.show();
this.MainWindow.focus();
}
}
});
this.TrayIcon.setToolTip(app.getName());
this.TrayIcon.setContextMenu(
Menu.buildFromTemplate(this.TrayMenuTemplate),
);
}
// Setup the main manu bar at the top of our window.
Menu.setApplicationMenu(
Menu.buildFromTemplate(this.AppMenuBarMenuTemplate),
);
// If the splashscreen is enabled, show it first while the main window loads then dwitch it out for the main window, or just load the main window from the start.
if (this.CapacitorFileConfig.electron?.splashScreenEnabled) {
this.SplashScreen = new CapacitorSplashScreen({
imageFilePath: join(
app.getAppPath(),
'assets',
this.CapacitorFileConfig.electron?.splashScreenImageName ??
'splash.png',
),
windowWidth: 400,
windowHeight: 400,
});
this.SplashScreen.init(this.loadMainWindow, this);
} else {
this.loadMainWindow(this);
}
// Security
this.MainWindow.webContents.setWindowOpenHandler(details => {
if (!details.url.includes(this.customScheme)) {
return { action: 'deny' };
} else {
return { action: 'allow' };
}
});
this.MainWindow.webContents.on('will-navigate', (event, _newURL) => {
if (!this.MainWindow.webContents.getURL().includes(this.customScheme)) {
event.preventDefault();
}
});
// Link electron plugins into the system.
setupCapacitorElectronPlugins();
// When the web app is loaded we hide the splashscreen if needed and show the mainwindow.
this.MainWindow.webContents.on('dom-ready', () => {
if (this.CapacitorFileConfig.electron?.splashScreenEnabled) {
this.SplashScreen.getSplashWindow().hide();
}
if (!this.CapacitorFileConfig.electron?.hideMainWindowOnLaunch) {
this.MainWindow.show();
}
setTimeout(() => {
if (electronIsDev) {
this.MainWindow.webContents.openDevTools();
}
CapElectronEventEmitter.emit(
'CAPELECTRON_DeeplinkListenerInitialized',
'',
);
}, 400);
});
}
Example #22
Source File: setupViewEventHandlers.ts From TidGi-Desktop with Mozilla Public License 2.0 | 4 votes |
/**
* Bind workspace related event handler to view.webContent
*/
export default function setupViewEventHandlers(
view: BrowserView,
browserWindow: BrowserWindow,
{ workspace, sharedWebPreferences, loadInitialUrlWithCatch }: IViewContext,
): void {
// metadata and state about current BrowserView
const viewMeta: IViewMeta = {
forceNewWindow: false,
};
const workspaceService = container.get<IWorkspaceService>(serviceIdentifier.Workspace);
const workspaceViewService = container.get<IWorkspaceViewService>(serviceIdentifier.WorkspaceView);
const windowService = container.get<IWindowService>(serviceIdentifier.Window);
const preferenceService = container.get<IPreferenceService>(serviceIdentifier.Preference);
view.webContents.on('did-start-loading', async () => {
const workspaceObject = await workspaceService.get(workspace.id);
// this event might be triggered
// even after the workspace obj and BrowserView
// are destroyed. See https://github.com/atomery/webcatalog/issues/836
if (workspaceObject === undefined) {
return;
}
if (workspaceObject.active && (await workspaceService.workspaceDidFailLoad(workspace.id)) && browserWindow !== undefined && !browserWindow.isDestroyed()) {
// fix https://github.com/webcatalog/singlebox-legacy/issues/228
const contentSize = browserWindow.getContentSize();
view.setBounds(await getViewBounds(contentSize as [number, number]));
}
await workspaceService.updateMetaData(workspace.id, {
// eslint-disable-next-line unicorn/no-null
didFailLoadErrorMessage: null,
isLoading: true,
});
});
view.webContents.on('did-navigate-in-page', async () => {
await workspaceViewService.updateLastUrl(workspace.id, view);
});
const throttledDidFinishedLoad = throttle(async () => {
// if have error, don't realignActiveWorkspace, which will hide the error message
if (await workspaceService.workspaceDidFailLoad(workspace.id)) {
return;
}
logger.debug(`throttledDidFinishedLoad() workspace.id: ${workspace.id}, now workspaceViewService.realignActiveWorkspace() then set isLoading to false`);
// focus on initial load
// https://github.com/atomery/webcatalog/issues/398
if (workspace.active && !browserWindow.isDestroyed() && browserWindow.isFocused() && !view.webContents.isFocused()) {
view.webContents.focus();
}
// fix https://github.com/atomery/webcatalog/issues/870
await workspaceViewService.realignActiveWorkspace();
// update isLoading to false when load succeed
await workspaceService.updateMetaData(workspace.id, {
isLoading: false,
});
}, 2000);
view.webContents.on('did-finish-load', () => {
logger.debug('did-finish-load called');
void throttledDidFinishedLoad();
});
view.webContents.on('did-stop-loading', () => {
logger.debug('did-stop-loading called');
void throttledDidFinishedLoad();
});
view.webContents.on('dom-ready', () => {
logger.debug('dom-ready called');
void throttledDidFinishedLoad();
});
// https://electronjs.org/docs/api/web-contents#event-did-fail-load
// https://github.com/webcatalog/neutron/blob/3d9e65c255792672c8bc6da025513a5404d98730/main-src/libs/views.js#L397
view.webContents.on('did-fail-load', async (_event, errorCode, errorDesc, _validateUrl, isMainFrame) => {
const [workspaceObject, workspaceDidFailLoad] = await Promise.all([
workspaceService.get(workspace.id),
workspaceService.workspaceDidFailLoad(workspace.id),
]);
// this event might be triggered
// even after the workspace obj and BrowserView
// are destroyed. See https://github.com/atomery/webcatalog/issues/836
if (workspaceObject === undefined) {
return;
}
if (workspaceDidFailLoad) {
return;
}
if (isMainFrame && errorCode < 0 && errorCode !== -3) {
// Fix nodejs wiki start slow on system startup, which cause `-102 ERR_CONNECTION_REFUSED` even if wiki said it is booted, we have to retry several times
if (errorCode === -102 && view.webContents.getURL().length > 0 && workspaceObject.homeUrl.startsWith('http')) {
setTimeout(async () => {
await loadInitialUrlWithCatch();
}, 1000);
return;
}
await workspaceService.updateMetaData(workspace.id, {
isLoading: false,
didFailLoadErrorMessage: `${errorCode} ${errorDesc}`,
});
if (workspaceObject.active && browserWindow !== undefined && !browserWindow.isDestroyed()) {
// fix https://github.com/atomery/singlebox/issues/228
const contentSize = browserWindow.getContentSize();
view.setBounds(await getViewBounds(contentSize as [number, number], false, 0, 0)); // hide browserView to show error message
}
}
// edge case to handle failed auth, use setTimeout to prevent infinite loop
if (errorCode === -300 && view.webContents.getURL().length === 0 && workspaceObject.homeUrl.startsWith('http')) {
setTimeout(async () => {
await loadInitialUrlWithCatch();
}, 1000);
}
});
view.webContents.on('did-navigate', async (_event, url) => {
const workspaceObject = await workspaceService.get(workspace.id);
// this event might be triggered
// even after the workspace obj and BrowserView
// are destroyed. See https://github.com/atomery/webcatalog/issues/836
if (workspaceObject === undefined) {
return;
}
if (workspaceObject.active) {
await windowService.sendToAllWindows(WindowChannel.updateCanGoBack, view.webContents.canGoBack());
await windowService.sendToAllWindows(WindowChannel.updateCanGoForward, view.webContents.canGoForward());
}
});
view.webContents.on('did-navigate-in-page', async (_event, url) => {
const workspaceObject = await workspaceService.get(workspace.id);
// this event might be triggered
// even after the workspace obj and BrowserView
// are destroyed. See https://github.com/atomery/webcatalog/issues/836
if (workspaceObject === undefined) {
return;
}
if (workspaceObject.active) {
await windowService.sendToAllWindows(WindowChannel.updateCanGoBack, view.webContents.canGoBack());
await windowService.sendToAllWindows(WindowChannel.updateCanGoForward, view.webContents.canGoForward());
}
});
view.webContents.on('page-title-updated', async (_event, title) => {
const workspaceObject = await workspaceService.get(workspace.id);
// this event might be triggered
// even after the workspace obj and BrowserView
// are destroyed. See https://github.com/atomery/webcatalog/issues/836
if (workspaceObject === undefined) {
return;
}
if (workspaceObject.active) {
browserWindow.setTitle(title);
}
});
view.webContents.setWindowOpenHandler((details: Electron.HandlerDetails) =>
handleNewWindow(
details.url,
{
workspace,
sharedWebPreferences,
view,
meta: viewMeta,
},
details.disposition,
view.webContents,
),
);
// Handle downloads
// https://electronjs.org/docs/api/download-item
view.webContents.session.on('will-download', async (_event, item) => {
const { askForDownloadPath, downloadPath } = await preferenceService.getPreferences();
// Set the save path, making Electron not to prompt a save dialog.
if (!askForDownloadPath) {
const finalFilePath = path.join(downloadPath, item.getFilename());
if (!fsExtra.existsSync(finalFilePath)) {
// eslint-disable-next-line no-param-reassign
item.savePath = finalFilePath;
}
} else {
// set preferred path for save dialog
const options = {
...item.getSaveDialogOptions(),
defaultPath: path.join(downloadPath, item.getFilename()),
};
item.setSaveDialogOptions(options);
}
});
// Unread count badge
void preferenceService.get('unreadCountBadge').then((unreadCountBadge) => {
if (unreadCountBadge) {
view.webContents.on('page-title-updated', async (_event, title) => {
const itemCountRegex = /[([{](\d*?)[)\]}]/;
const match = itemCountRegex.exec(title);
const incString = match !== null ? match[1] : '';
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
const inc = Number.parseInt(incString, 10) || 0;
await workspaceService.updateMetaData(workspace.id, {
badgeCount: inc,
});
let count = 0;
const workspaceMetaData = await workspaceService.getAllMetaData();
Object.values(workspaceMetaData).forEach((metaData) => {
if (typeof metaData?.badgeCount === 'number') {
count += metaData.badgeCount;
}
});
app.badgeCount = count;
if (process.platform === 'win32') {
if (count > 0) {
const icon = nativeImage.createFromPath(path.resolve(buildResourcePath, 'overlay-icon.png'));
browserWindow.setOverlayIcon(icon, `You have ${count} new messages.`);
} else {
// eslint-disable-next-line unicorn/no-null
browserWindow.setOverlayIcon(null, '');
}
}
});
}
});
// Find In Page
view.webContents.on('found-in-page', async (_event, result) => {
await windowService.sendToAllWindows(ViewChannel.updateFindInPageMatches, result.activeMatchOrdinal, result.matches);
});
// Link preview
view.webContents.on('update-target-url', (_event, url) => {
try {
view.webContents.send('update-target-url', url);
} catch (error) {
logger.warn(error); // eslint-disable-line no-console
}
});
}
Example #23
Source File: index.ts From TidGi-Desktop with Mozilla Public License 2.0 | 4 votes |
private async handleAttachToMenuBar(windowConfig: BrowserWindowConstructorOptions): Promise<Menubar> {
// setImage after Tray instance is created to avoid
// "Segmentation fault (core dumped)" bug on Linux
// https://github.com/electron/electron/issues/22137#issuecomment-586105622
// https://github.com/atomery/translatium/issues/164
const tray = new Tray(nativeImage.createEmpty());
// icon template is not supported on Windows & Linux
tray.setImage(MENUBAR_ICON_PATH);
const menuBar = menubar({
index: MAIN_WINDOW_WEBPACK_ENTRY,
tray,
activateWithApp: false,
preloadWindow: true,
tooltip: i18n.t('Menu.TidGiMenuBar'),
browserWindow: mergeDeep(windowConfig, {
show: false,
minHeight: 100,
minWidth: 250,
}),
});
menuBar.on('after-create-window', () => {
if (menuBar.window !== undefined) {
menuBar.window.on('focus', () => {
const view = menuBar.window?.getBrowserView();
if (view?.webContents !== undefined) {
view.webContents.focus();
}
});
menuBar.window.removeAllListeners('close');
menuBar.window.on('close', (event) => {
event.preventDefault();
menuBar.hideWindow();
});
}
});
// https://github.com/maxogden/menubar/issues/120
menuBar.on('after-hide', () => {
if (process.platform === 'darwin') {
menuBar.app.hide();
}
});
return await new Promise<Menubar>((resolve) => {
menuBar.on('ready', async () => {
// right on tray icon
menuBar.tray.on('right-click', () => {
// TODO: restore updater options here
const contextMenu = Menu.buildFromTemplate([
{
label: i18n.t('ContextMenu.OpenTidGi'),
click: async () => await this.open(WindowNames.main),
},
{
label: i18n.t('ContextMenu.OpenTidGiMenuBar'),
click: async () => await menuBar.showWindow(),
},
{
type: 'separator',
},
{
label: i18n.t('ContextMenu.About'),
click: async () => await this.open(WindowNames.about),
},
{ type: 'separator' },
{
label: i18n.t('ContextMenu.Preferences'),
click: async () => await this.open(WindowNames.preferences),
},
{
label: i18n.t('ContextMenu.Notifications'),
click: async () => await this.open(WindowNames.notifications),
},
{ type: 'separator' },
{
label: i18n.t('ContextMenu.Quit'),
click: () => {
menuBar.app.quit();
},
},
]);
menuBar.tray.popUpContextMenu(contextMenu);
});
// right click on window content
if (menuBar.window?.webContents !== undefined) {
const unregisterContextMenu = await this.menuService.initContextMenuForWindowWebContents(menuBar.window.webContents);
menuBar.on('after-close', () => {
unregisterContextMenu();
});
}
resolve(menuBar);
});
});
}