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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
private readonly iconActive: nativeImage;
Example #14
Source File: TrayMenu.ts    From wiregui with MIT License 5 votes vote down vote up
private readonly icon: nativeImage;
Example #15
Source File: browser-action.ts    From electron-browser-shell with GNU General Public License v3.0 5 votes vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
/**
 * 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 vote down vote up
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);
      });
    });
  }