Skip to content

Instantly share code, notes, and snippets.

@ibash

ibash/index.js Secret

Created October 16, 2020 08:18
Show Gist options
  • Save ibash/2c9894a8038b71456aeabe413e532c19 to your computer and use it in GitHub Desktop.
Save ibash/2c9894a8038b71456aeabe413e532c19 to your computer and use it in GitHub Desktop.
// electron-panel-window/index.js from whatsapp electron desktop app
// @flow strict
const { BrowserWindow } = require('electron');
const NativeExtension = require('bindings')('NativeExtension');
const isDarwin = process.platform === 'darwin';
const isWindows = process.platform === 'win32';
class Panel {
_isPanel = false;
_browserWindow = null;
constructor(options) {
const _options = { focusable: true, ...options, show: false };
this._browserWindow = new BrowserWindow(_options);
NativeExtension.MakeControls(this.getNativeWindowHandle());
NativeExtension.MakeVibrant(this.getNativeWindowHandle());
this.setFocusable(_options.focusable);
if (options.show) {
this.show();
}
}
get webContents() {
return this._browserWindow.webContents;
}
getBrowserWindow() {
return this._browserWindow;
}
getNativeWindowHandle() {
return this._browserWindow.getNativeWindowHandle();
}
getBounds() {
return this._browserWindow.getBounds();
}
on(...args) {
this._browserWindow.on(...args);
}
once(...args) {
this._browserWindow.once(...args);
}
removeListener(...args) {
this._browserWindow.removeListener(...args);
}
loadURL(...args) {
return this._browserWindow.loadURL(...args);
}
show(animate) {
animate = animate || false;
if (isDarwin) {
NativeExtension.Show(this.getNativeWindowHandle(), animate);
} else {
this._browserWindow.show();
}
}
hide(animate) {
animate = animate || false;
if (isDarwin) {
NativeExtension.Hide(this.getNativeWindowHandle(), animate);
} else {
this._browserWindow.hide();
}
}
close(animate) {
animate = animate || false;
if (this._isPanel) {
NativeExtension.ClosePanel(this.getNativeWindowHandle(), animate);
} else {
this._browserWindow.close();
}
}
minimize() {
return this._browserWindow.minimize();
}
maximize() {
return this._browserWindow.maximize();
}
unmaximize() {
return this._browserWindow.unmaximize();
}
isFullScreen() {
return this._browserWindow.isFullScreen();
}
setFullScreen(value) {
return this._browserWindow.setFullScreen(value);
}
isAlwaysOnTop() {
return this._browserWindow.isAlwaysOnTop();
}
setAlwaysOnTop(value) {
this._browserWindow.setAlwaysOnTop(value);
if (this._isPanel) {
NativeExtension.SyncPanel(this.getNativeWindowHandle(), false);
}
}
isMinimizable() {
return this._browserWindow.isMinimizable();
}
isMinimized() {
return this._browserWindow.isMinimized();
}
isMaximized() {
return this._browserWindow.isMaximized();
}
setMinimizable(value) {
return this._browserWindow.setMinimizable(value);
}
setMaximizable(value) {
return this._browserWindow.setMaximizable(value);
}
setFullScreenable(value) {
return this._browserWindow.setFullScreenable(value);
}
restore() {
return this._browserWindow.restore();
}
destroy() {
if (this._isPanel) {
NativeExtension.ClosePanel(this.getNativeWindowHandle(), false);
}
this._browserWindow.destroy();
}
isDestroyed() {
return this._browserWindow.isDestroyed();
}
setBounds(bounds, animate) {
animate = animate || false;
// Electron window is pushed down to the top of the screen border if negative Y coordinate is provided in MacOS, but not in Windows.
// Make sure that negative Y coordinate is never set, so window is not positioned too high and part of the content is not hidden.
const windowBounds = {
...bounds,
};
if (windowBounds.y != null && windowBounds.y < 0) {
windowBounds.y = 0;
}
this._browserWindow.setBounds(windowBounds, animate && !this._isPanel);
if (this._isPanel) {
NativeExtension.SyncPanel(this.getNativeWindowHandle(), animate);
}
}
setPosition(x, y, animate) {
animate = animate || false;
// Electron window is pushed down to the top of the screen border if negative Y coordinate is provided in MacOS, but not in Windows.
// Make sure that negative Y coordinate is never set, so window is not positioned too high and part of the content is not hidden.
let yCoord = y;
if (yCoord != null && yCoord < 0) {
yCoord = 0;
}
this._browserWindow.setPosition(x, yCoord, animate && !this._isPanel);
if (this._isPanel) {
NativeExtension.SyncPanel(this.getNativeWindowHandle(), animate);
}
}
setSize(w, h, animate) {
animate = animate || false;
this._browserWindow.setSize(w, h, animate && !this._isPanel);
if (this._isPanel) {
NativeExtension.SyncPanel(this.getNativeWindowHandle(), animate);
}
}
setMinimumSize(minWidth, minHeight) {
this._browserWindow.setMinimumSize(minWidth, minHeight);
if (this._isPanel) {
NativeExtension.SyncPanel(this.getNativeWindowHandle(), false);
}
}
setResizable(value) {
this._browserWindow.setResizable(value);
if (this._isPanel) {
NativeExtension.SyncPanel(this.getNativeWindowHandle(), false);
}
if (isWindows && value) {
// Fixes window style to make it snappable again.
NativeExtension.MakeResizable(this.getNativeWindowHandle());
}
}
setAspectRatio(value) {
this._browserWindow.setAspectRatio(value);
if (this._isPanel) {
NativeExtension.SyncPanel(this.getNativeWindowHandle(), false);
}
}
setFocusable(value) {
this._browserWindow.setFocusable(value);
if (!isDarwin) {
return;
}
if (!value && !this._isPanel) {
// converting to a native Panel to make the window non-focusable and non-activating on mac
NativeExtension.MakePanel(this.getNativeWindowHandle());
this._isPanel = true;
} else if (value && this._isPanel) {
// converting to a native Window to make the window focusable and activating on mac
NativeExtension.MakeWindow(this.getNativeWindowHandle());
this._isPanel = false;
}
}
setVisibleOnAllWorkspaces(...args) {
this._browserWindow.setVisibleOnAllWorkspaces(...args);
}
setAppDetails(options) {
this._browserWindow.setAppDetails(options);
}
initiateWindowMove() {
if (isWindows) {
NativeExtension.InitiateWindowMove(this.getNativeWindowHandle());
}
}
orderBack() {
NativeExtension.OrderBack(this.getNativeWindowHandle());
}
}
module.exports = { Panel };
@castroCrea
Copy link

Hi,
Do you have the functions_mac.cc also ?
I try to find an alternative to make NsPanel work again.
I found your solution here goabstract/electron-panel-window#6 but there is missing part the file functions_mac.cc

Did you found any other good solution ?

Thank you

@ibash
Copy link
Author

ibash commented Nov 18, 2020

@castroCrea I do not, and I haven't found a good solution to this yet. The code above I pulled out of the WhatsApp desktop client, but I'm not even sure how it's used. You can do the same with:

npx asar extract /Applications/WhatsApp.app/Contents/Resources/app.asar unpacked

and then look through unpacked for the. code above.

@castroCrea
Copy link

castroCrea commented Nov 18, 2020

@ibash find it thanks

@castroCrea
Copy link

castroCrea commented Nov 19, 2020

@ibash I make it work but it behave like a regular browser window, so sad
I create a Feature request in electron electron/electron#26596

@ibash
Copy link
Author

ibash commented Nov 19, 2020

dang, yeah. If I come across a solution I'll let you know.

@castroCrea
Copy link

castroCrea commented Nov 19, 2020

thanks 👍 same

@yarinsa
Copy link

yarinsa commented Jan 4, 2021

Hack!

Hey guys was able to solve this issue.

Make sure that you add this flag to your browser window when creating it .

this._electronWindow = new electron.BrowserWindow({ ...this.createOptions, titleBarStyle: 'customButtonsOnHover' });

Screen Shot 2021-01-04 at 18 40 48

@stefanoTron
Copy link

@yarinsa
I tried adding titleBarStyle: 'customButtonsOnHover' but that did not fix the issue for me. I'm on Big Sur on a M1 MBP.
Could you share you setup? Thanks

@viseator
Copy link

viseator commented Jun 7, 2023

For who are fining solution here:
Nowadays, electron has supported create NSPanel directly by passing type:'panel' in BrowserWindow config.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment