================================================
FILE: CONTRIBUTING.md
================================================
## Before opening issues
**If you are asking a question**:
Remember that `menubar` is just a lightweight wrapper around Electron. Most of the time you can probably find the answer to your question already answered in the [Electron Issue Tracker](https://github.com/electron/electron/issues)
**For bug reports/technical issues**:
Please provide the following information when opening issues:
- Which version of menubar are you using?
- What cli arguments are you passing?
- What platform are you running menubar on?
- Is there a stack trace in the error message you're seeing?
- If possible, please provide instructions to reproduce your problem.
Thanks!
## Releases
- commit your changes
- `npm version `
- `git push && git push --tags` (or `git push` with `git config --global push.followTags true` on latest git)
- `npm publish`
================================================
FILE: LICENSE
================================================
BSD 2-Clause License
Copyright (c) 2015-2019, Max Ogden
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================

[](https://www.npmjs.com/package/menubar)



➖ Menubar
High level way to create menubar desktop applications with Electron.
This module provides boilerplate for setting up a menubar application using Electron. All you have to do is point it at your `index.html` and `menubar` will handle the rest.
✅ Only one dependency, and one peer-dependency.
✅ Works on macOS, Windows and most Linuxes. See [details](./WORKING_PLATFORMS.md).
✅ 💥 [**3.6kB minified + gzipped**](https://bundlephobia.com/result?p=menubar) 💥
| | | |
| :-----------------------------------------------------------: | :--------------------------------------------------------: | :------------------------------------------------------: |
| macOS Mojave 10.14 | Windows 10 | Ubuntu 18.04 |
## Installation
```bash
yarn add menubar
```
## Usage
Starting with your own new project, run these commands:
```bash
$ yarn add menubar
$ touch myApp.js
$ touch index.html
```
Fill `index.html` with some HTML, and `myApp.js` like this:
```javascript
const { menubar } = require('menubar');
const mb = menubar();
mb.on('ready', () => {
console.log('app is ready');
// your app code here
});
```
Then use `electron` to run the app:
```bash
$ electron myApp.js
```
Alternatively, see [`examples/hello-world`](/examples/hello-world) folder for a simple working example.
## `Menubar` Class
The return value of `menubar()` is a `Menubar` class instance, which has these properties:
- `app`: the [Electron App](https://electronjs.org/docs/api/app) instance,
- `window`: the [Electron Browser Window](https://electronjs.org/docs/api/browser-window) instance,
- `tray`: the [Electron Tray](https://electronjs.org/docs/api/tray) instance,
- `positioner`: the [Electron Positioner](https://github.com/jenslind/electron-positioner) instance,
- `setOption(option, value)`: change an option after menubar is created,
- `getOption(option)`: get an menubar option,
- `showWindow()`: show the menubar window,
- `hideWindow()`: hide the menubar window
See the reference [API docs](./docs/classes/_menubar_.menubar.md).
## `menubar()` Options
You can pass an optional options object into the `menubar({ ... })` function:
- `dir` (default `process.cwd()`) - the app source directory
- `index` (default `file:// + opts.dir + index.html`) - The URL to load the menubar's browserWindow with. The url can be a remote address (e.g. `http://`) or a path to a local HTML file using the `file://` protocol.
- `browserWindow` - BrowserWindow options to be passed to the BrowserWindow constructor, see [Electron docs](https://electronjs.org/docs/api/browser-window#new-browserwindowoptions). Some interesting fields to passed down are:
- `x` (default `undefined`) - the x position of the window
- `y` (default `undefined`) - the y position of the window
- `width` (default 400) - window width
- `height` (default 400) - window height
- `alwaysOnTop` (default false) - if true, the window will not hide on blur
- `icon` (default `opts.dir + IconTemplate.png`) - the png icon to use for the menubar. A good size to start with is 20x20. To support retina, supply a 2x sized image (e.g. 40x40) with `@2x` added to the end of the name, so `icon.png` and `icon@2x.png` and Electron will automatically use your `@2x` version on retina screens.
- `tooltip` (default empty) - menubar tray icon tooltip text
- `tray` (default created on-the-fly) - an electron `Tray` instance. if provided `opts.icon` will be ignored
- `preloadWindow` (default false) - Create [BrowserWindow](https://electronjs.org/docs/api/browser-window#new-browserwindowoptions) instance before it is used -- increasing resource usage, but making the click on the menubar load faster.
- `loadUrlOptions` - (default undefined) The options passed when loading the index URL in the menubar's browserWindow. Everything browserWindow.loadURL supports is supported; this object is simply passed onto [browserWindow.loadURL](https://electronjs.org/docs/api/browser-window#winloadurlurl-options)
- `showOnAllWorkspaces` (default true) - Makes the window available on all OS X workspaces.
- `windowPosition` (default trayCenter and trayBottomCenter on Windows) - Sets the window position (x and y will still override this), check [positioner docs](https://github.com/jenslind/electron-positioner#docs) for valid values.
- `showDockIcon` (default false) - Configure the visibility of the application dock icon.
- `showOnRightClick` (default false) - Show the window on 'right-click' event instead of regular 'click'
See the reference [API docs](./docs/interfaces/_types_.options.md).
## Events
The `Menubar` class is an event emitter:
- `ready` - when `menubar`'s tray icon has been created and initialized, i.e. when `menubar` is ready to be used. Note: this is different than Electron app's `ready` event, which happens much earlier in the process
- `create-window` - the line before `new BrowserWindow()` is called
- `before-load` - after create window, before loadUrl (can be used for `require("@electron/remote/main").enable(webContents)`)
- `after-create-window` - the line after all window init code is done and url was loaded
- `show` - the line before `window.show()` is called
- `after-show` - the line after `window.show()` is called
- `hide` - the line before `window.hide()` is called (on window blur)
- `after-hide` - the line after `window.hide()` is called
- `after-close` - after the `.window` (BrowserWindow) property has been deleted
- `focus-lost` - emitted if always-on-top option is set and the user clicks away
## Compatibility with Electron
| menubar | Electron | Notes |
| -------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| 9.x.x | >= 9.x.x <= 34.x.x | |
| 8.x.x | 8.x.xx | |
| 7.x.x | 7.x.xx | |
| 6.x.x | >= 4.x.x < 7.x.x | Not recommended for [security reasons](https://electronjs.org/docs/tutorial/security#17-use-a-current-version-of-electron) |
| <= 5.x.x | <= 3.x.x | Please, _please_ don't use these old versions |
## API Docs
See the reference [API docs](./docs/globals.md).
## Tips
- Use `mb.on('after-create-window', callback)` to run things after your app has loaded. For example you could run `mb.window.openDevTools()` to open the developer tools for debugging, or load a different URL with `mb.window.loadURL()`
- Use `mb.on('focus-lost')` if you would like to perform some operation when using the option `browserWindow.alwaysOnTop: true`
- To restore focus of previous window after menubar hide, use `mb.on('after-hide', () => { mb.app.hide() } )` or similar
- To create a native menu, you can use `tray.setContextMenu(contextMenu)`, and pass this custom tray to menubar: `const mb = menubar({ tray });`. See [this example](https://github.com/maxogden/menubar/tree/master/examples/native-menu) for more information.
- To avoid a flash when opening your menubar app, you can disable backgrounding the app using the following: `mb.app.commandLine.appendSwitch('disable-backgrounding-occluded-windows', 'true');`
================================================
FILE: WORKING_PLATFORMS.md
================================================
# Platforms where `menubar` is known to work
This document is still a work-in-progress. If you have tested `menubar` with a platform that is not listed under here, I would greatly appreciate a PR!
## macOS
| Version | Working Status | Known Issues |
| ----------------- | -------------- | -------------------------------------------------------------------------------------------------------------- |
| 14.15 Sonoma | ✅ Good | |
| 10.14 Mojave | ✅ Good | [#147](https://github.com/maxogden/menubar/issues/147), [#215](https://github.com/maxogden/menubar/issues/215) |
| 10.13 High Sierra | ✅ Good | |
## Windows
| Version | Working Status | Known Issues |
| ---------- | -------------- | ------------ |
| Windows 11 | ✅ Good | |
| Windows 10 | ✅ Good | |
| Windows 8 | ✅ Good | |
## Linux
| Distribution | Desktop Environment | Working Status | Known Issues |
| ------------- | ------------------- | -------------- | ------------------------------------------------------ |
| openSUSE 13.1 | Xfce 4.10.1 | ❌ Bad | [#123](https://github.com/maxogden/menubar/issues/123) |
| Ubuntu 18.04 | Unity | ✅ Good | |
| Ubuntu 14.04 | Unity | ❌ Bad | [#68](https://github.com/maxogden/menubar/issues/68) |
================================================
FILE: biome.json
================================================
{
"$schema": "https://biomejs.dev/schemas/1.9.3/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"style": {
"noParameterAssign": "warn",
"noNonNullAssertion": "warn",
"useNodejsImportProtocol": "warn"
},
"suspicious": {
"noAssignInExpressions": "warn"
}
}
},
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"jsxQuoteStyle": "double"
}
},
"json": {
"parser": {
"allowComments": true
}
}
}
================================================
FILE: docs/README.md
================================================
> **[menubar](README.md)**
[Globals](globals.md) /
[](https://travis-ci.org/maxogden/menubar)
[](https://www.npmjs.com/package/@maxogden/menubar)
[](https://david-dm.org/maxogden/menubar)


➖ Menubar
High level way to create menubar desktop applications with Electron.
This module provides boilerplate for setting up a menubar application using Electron. All you have to do is point it at your `index.html` and `menubar` will handle the rest.
✅ Only one dependency, and one peer-dependency.
✅ Works on macOS, Windows and most Linuxes. See [details](./WORKING_PLATFORMS.md).
✅ 💥 [**3.6kB minified + gzipped**](https://bundlephobia.com/result?p=menubar) 💥
| | | |
| :-----------------------------------------------------------: | :--------------------------------------------------------: | :------------------------------------------------------: |
| macOS Mojave 10.14 | Windows 10 | Ubuntu 18.04 |
## Installation
```bash
yarn add menubar
```
## Usage
Starting with your own new project, run these commands:
```bash
$ yarn add menubar
$ touch myApp.js
$ touch index.html
```
Fill `index.html` with some HTML, and `myApp.js` like this:
```javascript
const { menubar } = require('menubar');
const mb = menubar();
mb.on('ready', () => {
console.log('app is ready');
// your app code here
});
```
Then use `electron` to run the app:
```bash
$ electron myApp.js
```
Alternatively, see [`examples/hello-world`](/examples/hello-world) folder for a simple working example.
## `Menubar` Class
The return value of `menubar()` is a `Menubar` class instance, which has these properties:
- `app`: the [Electron App](https://electronjs.org/docs/api/app) instance,
- `window`: the [Electron Browser Window](https://electronjs.org/docs/api/browser-window) instance,
- `tray`: the [Electron Tray](https://electronjs.org/docs/api/tray) instance,
- `positioner`: the [Electron Positioner](https://github.com/jenslind/electron-positioner) instance,
- `setOption(option, value)`: change an option after menubar is created,
- `getOption(option)`: get an menubar option,
- `showWindow()`: show the menubar window,
- `hideWindow()`: hide the menubar window
See the reference [API docs](./docs/classes/_menubar_.menubar.md).
## `menubar()` Options
You can pass an optional options object into the `menubar({ ... })` function:
- `dir` (default `process.cwd()`) - the app source directory
- `index` (default `file:// + opts.dir + index.html`) - The URL to load the menubar's browserWindow with. The url can be a remote address (e.g. `http://`) or a path to a local HTML file using the `file://` protocol.
- `browserWindow` - BrowserWindow options to be passed to the BrowserWindow constructor, see [Electron docs](https://electronjs.org/docs/api/browser-window#new-browserwindowoptions). Some interesting fields to passed down are:
- `x` (default `undefined`) - the x position of the window
- `y` (default `undefined`) - the y position of the window
- `width` (default 400) - window width
- `height` (default 400) - window height
- `alwaysOnTop` (default false) - if true, the window will not hide on blur
- `icon` (default `opts.dir + IconTemplate.png`) - the png icon to use for the menubar. A good size to start with is 20x20. To support retina, supply a 2x sized image (e.g. 40x40) with `@2x` added to the end of the name, so `icon.png` and `icon@2x.png` and Electron will automatically use your `@2x` version on retina screens.
- `tooltip` (default empty) - menubar tray icon tooltip text
- `tray` (default created on-the-fly) - an electron `Tray` instance. if provided `opts.icon` will be ignored
- `preloadWindow` (default false) - Create [BrowserWindow](https://electronjs.org/docs/api/browser-window#new-browserwindowoptions) instance before it is used -- increasing resource usage, but making the click on the menubar load faster.
- `showOnAllWorkspaces` (default true) - Makes the window available on all OS X workspaces.
- `windowPosition` (default trayCenter and trayBottomCenter on Windows) - Sets the window position (x and y will still override this), check [positioner docs](https://github.com/jenslind/electron-positioner#docs) for valid values.
- `showDockIcon` (default false) - Configure the visibility of the application dock icon.
- `showOnRightClick` (default false) - Show the window on 'right-click' event instead of regular 'click'
See the reference [API docs](./docs/interfaces/_types_.options.md).
## Events
The `Menubar` class is an event emitter:
- `ready` - when `menubar`'s tray icon has been created and initialized, i.e. when `menubar` is ready to be used. Note: this is different than Electron app's `ready` event, which happens much earlier in the process
- `create-window` - the line before `new BrowserWindow()` is called
- `after-create-window` - the line after all window init code is done
- `show` - the line before `window.show()` is called
- `after-show` - the line after `window.show()` is called
- `hide` - the line before `window.hide()` is called (on window blur)
- `after-hide` - the line after `window.hide()` is called
- `after-close` - after the `.window` (BrowserWindow) property has been deleted
- `focus-lost` - emitted if always-on-top option is set and the user clicks away
## Compatibility with Electron
| menubar | Electron | Notes |
| -------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| 7.x.x | 7.x.x |
| 6.x.x | 4.x.x \| 5.x.x \| 6.x.x | Not recommended for [security reasons](https://electronjs.org/docs/tutorial/security#17-use-a-current-version-of-electron) |
| <= 5.x.x | <= 3.x.x | Please, _please_ don't use these old versions |
## API Docs
See the reference [API docs](./docs/globals.md).
## Tips
- Use `mb.on('after-create-window', callback)` to run things after your app has loaded. For example you could run `mb.window.openDevTools()` to open the developer tools for debugging, or load a different URL with `mb.window.loadUrl()`
- Use `mb.on('focus-lost')` if you would like to perform some operation when using the option `browserWindow.alwaysOnTop: true`
- To restore focus of previous window after menubar hide, use `mb.on('after-hide', () => { mb.app.hide() } )` or similar
- To create a native menu, you can use `tray.setContextMenu(contextMenu)`, and pass this custom tray to menubar: `const mb = menubar({ tray });`. See [this example](https://github.com/maxogden/menubar/tree/master/examples/native-menu) for more information.
- To avoid a flash when opening your menubar app, you can disable backgrounding the app using the following: `mb.app.commandLine.appendSwitch('disable-backgrounding-occluded-windows', 'true');`
================================================
FILE: docs/classes/_menubar_.menubar.md
================================================
> **[menubar](../README.md)**
[Globals](../globals.md) / ["Menubar"](../modules/_menubar_.md) / [Menubar](_menubar_.menubar.md) /
# Class: Menubar
## Hierarchy
* `EventEmitter`
* **Menubar**
## Index
### Constructors
* [constructor](_menubar_.menubar.md#constructor)
### Accessors
* [app](_menubar_.menubar.md#app)
* [positioner](_menubar_.menubar.md#positioner)
* [tray](_menubar_.menubar.md#tray)
* [window](_menubar_.menubar.md#window)
### Methods
* [getOption](_menubar_.menubar.md#getoption)
* [hideWindow](_menubar_.menubar.md#hidewindow)
* [setOption](_menubar_.menubar.md#setoption)
* [showWindow](_menubar_.menubar.md#showwindow)
## Constructors
### constructor
\+ **new Menubar**(`app`: `App`, `options?`: `Partial`): *[Menubar](_menubar_.menubar.md)*
*Defined in [Menubar.ts:24](https://github.com/adam-lynch/menubar/blob/6b93752/src/Menubar.ts#L24)*
**Parameters:**
Name | Type |
------ | ------ |
`app` | `App` |
`options?` | `Partial` |
**Returns:** *[Menubar](_menubar_.menubar.md)*
## Accessors
### app
• **app**:
*Defined in [Menubar.ts:47](https://github.com/adam-lynch/menubar/blob/6b93752/src/Menubar.ts#L47)*
___
### positioner
• **positioner**:
*Defined in [Menubar.ts:56](https://github.com/adam-lynch/menubar/blob/6b93752/src/Menubar.ts#L56)*
___
### tray
• **tray**:
*Defined in [Menubar.ts:69](https://github.com/adam-lynch/menubar/blob/6b93752/src/Menubar.ts#L69)*
___
### window
• **window**:
*Defined in [Menubar.ts:83](https://github.com/adam-lynch/menubar/blob/6b93752/src/Menubar.ts#L83)*
## Methods
### getOption
▸ **getOption**<**K**>(`key`: `K`): *`Options[K]`*
*Defined in [Menubar.ts:92](https://github.com/adam-lynch/menubar/blob/6b93752/src/Menubar.ts#L92)*
**Type parameters:**
▪ **K**: *keyof Options*
**Parameters:**
Name | Type | Description |
------ | ------ | ------ |
`key` | `K` | The option key to retrieve, see [Options](../interfaces/_types_.options.md). |
**Returns:** *`Options[K]`*
___
### hideWindow
▸ **hideWindow**(): *void*
*Defined in [Menubar.ts:99](https://github.com/adam-lynch/menubar/blob/6b93752/src/Menubar.ts#L99)*
**Returns:** *void*
___
### setOption
▸ **setOption**<**K**>(`key`: `K`, `value`: `Options[K]`): *void*
*Defined in [Menubar.ts:115](https://github.com/adam-lynch/menubar/blob/6b93752/src/Menubar.ts#L115)*
**Type parameters:**
▪ **K**: *keyof Options*
**Parameters:**
Name | Type | Description |
------ | ------ | ------ |
`key` | `K` | The option key to modify, see [Options](../interfaces/_types_.options.md). |
`value` | `Options[K]` | The value to set. |
**Returns:** *void*
___
### showWindow
▸ **showWindow**(`trayPos?`: `Electron.Rectangle`): *`Promise`*
*Defined in [Menubar.ts:124](https://github.com/adam-lynch/menubar/blob/6b93752/src/Menubar.ts#L124)*
**Parameters:**
Name | Type | Description |
------ | ------ | ------ |
`trayPos?` | `Electron.Rectangle` | The bounds to show the window in. |
**Returns:** *`Promise`*
================================================
FILE: docs/globals.md
================================================
> **[menubar](README.md)**
[Globals](globals.md) /
# menubar
## Index
### External modules
* ["Menubar"](modules/_menubar_.md)
* ["index"](modules/_index_.md)
* ["types"](modules/_types_.md)
* ["util/getWindowPosition"](modules/_util_getwindowposition_.md)
================================================
FILE: docs/interfaces/_types_.options.md
================================================
> **[menubar](../README.md)**
[Globals](../globals.md) / ["types"](../modules/_types_.md) / [Options](_types_.options.md) /
# Interface: Options
## Hierarchy
* **Options**
## Table of Contents
### Properties
* [browserWindow](_types_.options.md#browserwindow)
* [dir](_types_.options.md#dir)
* [icon](_types_.options.md#optional-icon)
* [index](_types_.options.md#index)
* [loadUrlOptions](_types_.options.md#optional-loadurloptions)
* [preloadWindow](_types_.options.md#optional-preloadwindow)
* [showDockIcon](_types_.options.md#optional-showdockicon)
* [showOnAllWorkspaces](_types_.options.md#optional-showonallworkspaces)
* [showOnRightClick](_types_.options.md#optional-showonrightclick)
* [tooltip](_types_.options.md#tooltip)
* [tray](_types_.options.md#optional-tray)
* [windowPosition](_types_.options.md#optional-windowposition)
## Properties
### browserWindow
• **browserWindow**: *`BrowserWindowConstructorOptions`*
*Defined in [types.ts:23](https://github.com/maxogden/menubar/blob/c7d6640/src/types.ts#L23)*
___
### dir
• **dir**: *string*
*Defined in [types.ts:27](https://github.com/maxogden/menubar/blob/c7d6640/src/types.ts#L27)*
___
### `Optional` icon
• **icon**? : *string | `NativeImage`*
*Defined in [types.ts:34](https://github.com/maxogden/menubar/blob/c7d6640/src/types.ts#L34)*
___
### index
• **index**: *string | false*
*Defined in [types.ts:43](https://github.com/maxogden/menubar/blob/c7d6640/src/types.ts#L43)*
___
### `Optional` loadUrlOptions
• **loadUrlOptions**? : *`LoadURLOptions`*
*Defined in [types.ts:51](https://github.com/maxogden/menubar/blob/c7d6640/src/types.ts#L51)*
___
### `Optional` preloadWindow
• **preloadWindow**? : *undefined | false | true*
*Defined in [types.ts:56](https://github.com/maxogden/menubar/blob/c7d6640/src/types.ts#L56)*
___
### `Optional` showDockIcon
• **showDockIcon**? : *undefined | false | true*
*Defined in [types.ts:61](https://github.com/maxogden/menubar/blob/c7d6640/src/types.ts#L61)*
___
### `Optional` showOnAllWorkspaces
• **showOnAllWorkspaces**? : *undefined | false | true*
*Defined in [types.ts:66](https://github.com/maxogden/menubar/blob/c7d6640/src/types.ts#L66)*
___
### `Optional` showOnRightClick
• **showOnRightClick**? : *undefined | false | true*
*Defined in [types.ts:70](https://github.com/maxogden/menubar/blob/c7d6640/src/types.ts#L70)*
___
### tooltip
• **tooltip**: *string*
*Defined in [types.ts:74](https://github.com/maxogden/menubar/blob/c7d6640/src/types.ts#L74)*
___
### `Optional` tray
• **tray**? : *`Tray`*
*Defined in [types.ts:78](https://github.com/maxogden/menubar/blob/c7d6640/src/types.ts#L78)*
___
### `Optional` windowPosition
• **windowPosition**? : *"trayLeft" | "trayBottomLeft" | "trayRight" | "trayBottomRight" | "trayCenter" | "trayBottomCenter" | "topLeft" | "topRight" | "bottomLeft" | "bottomRight" | "topCenter" | "bottomCenter" | "leftCenter" | "rightCenter" | "center"*
*Defined in [types.ts:83](https://github.com/maxogden/menubar/blob/c7d6640/src/types.ts#L83)*
================================================
FILE: docs/modules/_index_.md
================================================
> **[menubar](../README.md)**
[Globals](../globals.md) / ["index"](_index_.md) /
# External module: "index"
## Index
### Functions
* [menubar](_index_.md#menubar)
## Functions
### menubar
▸ **menubar**(`options?`: `Partial`): *[Menubar](../classes/_menubar_.menubar.md)*
*Defined in [index.ts:25](https://github.com/adam-lynch/menubar/blob/6b93752/src/index.ts#L25)*
**Parameters:**
Name | Type | Description |
------ | ------ | ------ |
`options?` | `Partial` | Options for creating a menubar application, see [Options](../interfaces/_types_.options.md) |
**Returns:** *[Menubar](../classes/_menubar_.menubar.md)*
================================================
FILE: docs/modules/_menubar_.md
================================================
> **[menubar](../README.md)**
[Globals](../globals.md) / ["Menubar"](_menubar_.md) /
# External module: "Menubar"
## Index
### Classes
* [Menubar](../classes/_menubar_.menubar.md)
================================================
FILE: docs/modules/_types_.md
================================================
> **[menubar](../README.md)**
[Globals](../globals.md) / ["types"](_types_.md) /
# External module: "types"
## Index
### Interfaces
* [Options](../interfaces/_types_.options.md)
================================================
FILE: docs/modules/_util_getwindowposition_.md
================================================
> **[menubar](../README.md)**
[Globals](../globals.md) / ["util/getWindowPosition"](_util_getwindowposition_.md) /
# External module: "util/getWindowPosition"
## Index
### Functions
* [getWindowPosition](_util_getwindowposition_.md#getwindowposition)
* [taskbarLocation](_util_getwindowposition_.md#taskbarlocation)
## Functions
### getWindowPosition
▸ **getWindowPosition**(`tray`: `Tray`): *`WindowPosition`*
*Defined in [util/getWindowPosition.ts:52](https://github.com/adam-lynch/menubar/blob/6b93752/src/util/getWindowPosition.ts#L52)*
**Parameters:**
Name | Type | Description |
------ | ------ | ------ |
`tray` | `Tray` | The Electron Tray instance. |
**Returns:** *`WindowPosition`*
___
### taskbarLocation
▸ **taskbarLocation**(`tray`: `Tray`): *`TaskbarLocation`*
*Defined in [util/getWindowPosition.ts:18](https://github.com/adam-lynch/menubar/blob/6b93752/src/util/getWindowPosition.ts#L18)*
**Parameters:**
Name | Type | Description |
------ | ------ | ------ |
`tray` | `Tray` | The Electron Tray instance. |
**Returns:** *`TaskbarLocation`*
================================================
FILE: examples/arrow/README.md
================================================
# example-menubar-arrow
## Description
A menubar app with an arrow on top, that gives the app a popover look.
Thanks @leilarrossi for this example, see https://github.com/maxogden/menubar/issues/78.
## Screenshot

## Instructions
- Clone the repository.
- Run `yarn install` from the root folder.
- Run `yarn build` from the root folder.
- `cd` into this directory.
- Run `yarn install` to install this example's dependencies.
- Run `yarn start` from this directory to run app.
================================================
FILE: examples/arrow/index.css
================================================
.myarrow {
position: relative;
padding: 12px 0;
}
.myarrow:before {
content: "";
height: 0;
width: 0;
border-width: 0 8px 12px 8px;
border-style: solid;
border-color: transparent transparent #eeeeee transparent;
position: absolute;
top: 0px;
left: 50%;
transform: translateX(-50%);
}
.page {
background: #eeeeee;
width: 340px;
height: 500px;
margin: 0 auto;
}
.darwin.page {
border-radius: 5px;
overflow: hidden;
}
================================================
FILE: examples/arrow/index.html
================================================
Example App
Hello World.
================================================
FILE: examples/arrow/main.js
================================================
const { menubar } = require('../..');
const mb = menubar({
browserWindow: {
transparent: true,
width: 350,
height: 550,
},
});
mb.on('ready', () => {
console.log('Menubar app is ready.');
});
================================================
FILE: examples/arrow/package.json
================================================
{
"name": "example-menubar-arrow",
"version": "0.1.0",
"description": "Example menubar electron app",
"main": "main.js",
"private": true,
"scripts": {
"start": "electron ."
}
}
================================================
FILE: examples/hello-world/README.md
================================================
# example-menubar-hello-world
## Description
This is a simple Hello World menubar app that will just open a window that says "Hello World".
## Screenshot

## Instructions
- Clone the repository.
- Run `yarn install` from the root folder.
- Run `yarn build` from the root folder.
- `cd` into this directory.
- Run `yarn install` to install this example's dependencies.
- Run `yarn start` from this directory to run app.
================================================
FILE: examples/hello-world/index.html
================================================
Example App
Hello World.
================================================
FILE: examples/hello-world/main.js
================================================
const { menubar } = require('../..');
const mb = menubar();
mb.on('ready', () => {
console.log('Menubar app is ready.');
});
================================================
FILE: examples/hello-world/package.json
================================================
{
"name": "example-menubar-hello-world",
"version": "0.1.0",
"description": "Example menubar electron app",
"main": "main.js",
"private": true,
"scripts": {
"start": "electron ."
}
}
================================================
FILE: examples/icon-animation/index.html
================================================
Example App
Hello World.
================================================
FILE: examples/icon-animation/main.js
================================================
const { menubar } = require('../..');
const mb = menubar();
mb.on('ready', () => {
setOkIcon();
const trayAnimation = setInterval(frame, 1000);
// simulate data fetching
sleep(3000).then(() => {
clearInterval(trayAnimation);
setOkIcon();
});
});
function setOkIcon() {
mb.tray.setImage('state-ok-20.png');
}
function frame() {
setTimeout(() => mb.tray.setImage('state-sync-20.png'), 300);
setTimeout(() => mb.tray.setImage('state-sync-20-60.png'), 600);
setTimeout(() => mb.tray.setImage('state-sync-20-120.png'), 900);
}
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
================================================
FILE: examples/icon-animation/package.json
================================================
{
"name": "example-menubar-icon-animation",
"version": "0.1.0",
"description": "Example menubar electron app",
"main": "main.js",
"private": true,
"scripts": {
"start": "electron ."
}
}
================================================
FILE: examples/native-menu/README.md
================================================
# example-menubar-native-menu
## Description
This will create a menubar app with a native menu.
Courtesy of @tslater in https://github.com/maxogden/menubar/issues/178.
## Screenshot

## Instructions
- Clone the repository.
- Run `yarn install` from the root folder.
- Run `yarn build` from the root folder.
- `cd` into this directory.
- Run `yarn install` to install this example's dependencies.
- Run `yarn start` from this directory to run app.
================================================
FILE: examples/native-menu/main.js
================================================
const { app, Menu, Tray } = require('electron');
const path = require('path');
const { menubar } = require('../../');
const iconPath = path.join(__dirname, '..', '..', 'assets', 'IconTemplate.png');
app.on('ready', () => {
const tray = new Tray(iconPath);
const contextMenu = Menu.buildFromTemplate([
{ label: 'Item1', type: 'radio' },
{ label: 'Item2', type: 'radio' },
{ label: 'Item3', type: 'radio', checked: true },
{ label: 'Item4', type: 'radio' },
]);
tray.setContextMenu(contextMenu);
const mb = menubar({
tray,
});
mb.on('ready', () => {
// needed for macos to remove white screen
// ref: https://github.com/max-mapper/menubar/issues/345
tray.removeAllListeners();
console.log('Menubar app is ready.');
// your app code here
});
});
================================================
FILE: examples/native-menu/package.json
================================================
{
"name": "example-menubar-native-menu",
"version": "0.1.0",
"description": "Example menubar electron app",
"main": "main.js",
"private": true,
"scripts": {
"start": "electron ."
}
}
================================================
FILE: examples/package.json
================================================
{
"name": "example-menubar",
"version": "0.1.0",
"description": "Examples of menubar electron apps",
"private": true,
"workspaces": ["arrow", "hello-world", "icon-animation", "native-menu"],
"devDependencies": {
"electron": "^34.3.0"
}
}
================================================
FILE: jest.config.js
================================================
module.exports = {
collectCoverageFrom: ['**/*.{ts,tsx}', '!**/node_modules/**', '!**/*.d.ts'],
moduleFileExtensions: ['js', 'ts', 'tsx'],
rootDir: '.',
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest',
},
testRegex: 'spec\\.(ts|tsx)$',
};
================================================
FILE: package.json
================================================
{
"name": "menubar",
"version": "9.5.2",
"author": "Max Ogden",
"bugs": {
"url": "https://github.com/maxogden/menubar/issues"
},
"description": "high level way to create menubar desktop applications with electron",
"files": [
"/assets",
"/lib"
],
"homepage": "https://github.com/maxogden/menubar",
"keywords": [
"electron",
"shell",
"menubar",
"menu",
"taskbar",
"tray",
"traybar",
"mac",
"linux",
"windows",
"app"
],
"license": "BSD-2-Clause",
"main": "lib/index.js",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/maxogden/menubar.git"
},
"scripts": {
"build": "rimraf lib/ && tsc",
"deploy": "yarn build && standard-version",
"docs": "typedoc",
"lint:check": "biome check",
"lint": "biome check --fix",
"test": "jest"
},
"types": "lib/index.d.ts",
"dependencies": {
"electron-positioner": "^4.1.0"
},
"devDependencies": {
"@biomejs/biome": "^1.9.3",
"@types/jest": "^25.2.3",
"electron": "^34.3.0",
"jest": "^26.0.1",
"rimraf": "^3.0.2",
"standard-version": "^8.0.0",
"ts-jest": "^26.0.0",
"typedoc": "^0.17.7",
"typedoc-plugin-markdown": "^2.2.17",
"typedoc-plugin-no-inherit": "^1.1.10",
"typescript": "^4.6.2"
},
"peerDependencies": {
"electron": ">=9.0.0 <35.0.0"
},
"packageManager": "yarn@1.22.22"
}
================================================
FILE: src/Menubar.spec.ts
================================================
import { BrowserWindow, Tray, app } from 'electron';
import { Menubar } from './Menubar';
describe('Menubar', () => {
let mb: Menubar | undefined;
beforeEach(() => {
mb = new Menubar(app, { preloadWindow: true });
});
it('should have property `app`', () => {
expect(mb!.app).toBeDefined();
});
it('should have property `positioner`', () => {
expect(() => mb!.positioner as unknown).toThrow();
return new Promise((resolve) => {
mb!.on('after-create-window', () => {
expect(mb!.positioner).toBeDefined();
resolve();
});
});
});
it('should have property `tray`', () => {
expect(() => mb!.tray).toThrow();
return new Promise((resolve) => {
mb!.on('ready', () => {
expect(mb!.tray).toBeInstanceOf(Tray);
resolve();
});
});
});
it('should have property `window`', () => {
expect(mb!.window).toBeUndefined();
return new Promise((resolve) => {
mb!.on('ready', () => {
expect(mb!.window).toBeInstanceOf(BrowserWindow);
resolve();
});
});
});
});
================================================
FILE: src/Menubar.ts
================================================
import { EventEmitter } from 'events';
import fs from 'fs';
import path from 'path';
import { BrowserWindow, Tray } from 'electron';
import Positioner from 'electron-positioner';
import type { Options } from './types';
import { cleanOptions } from './util/cleanOptions';
import { getWindowPosition } from './util/getWindowPosition';
/**
* The main Menubar class.
*
* @noInheritDoc
*/
export class Menubar extends EventEmitter {
private _app: Electron.App;
private _browserWindow?: BrowserWindow;
private _blurTimeout: NodeJS.Timeout | null = null; // track blur events with timeout
private _isVisible: boolean; // track visibility
private _cachedBounds?: Electron.Rectangle; // _cachedBounds are needed for double-clicked event
private _options: Options;
private _positioner: Positioner | undefined;
private _tray?: Tray;
constructor(app: Electron.App, options?: Partial) {
super();
this._app = app;
this._options = cleanOptions(options);
this._isVisible = false;
if (app.isReady()) {
// See https://github.com/maxogden/menubar/pull/151
process.nextTick(() =>
this.appReady().catch((err) => console.error('menubar: ', err)),
);
} else {
app.on('ready', () => {
this.appReady().catch((err) => console.error('menubar: ', err));
});
}
}
/**
* The Electron [App](https://electronjs.org/docs/api/app)
* instance.
*/
get app(): Electron.App {
return this._app;
}
/**
* The [electron-positioner](https://github.com/jenslind/electron-positioner)
* instance.
*/
get positioner(): Positioner {
if (!this._positioner) {
throw new Error(
'Please access `this.positioner` after the `after-create-window` event has fired.',
);
}
return this._positioner;
}
/**
* The Electron [Tray](https://electronjs.org/docs/api/tray) instance.
*/
get tray(): Tray {
if (!this._tray) {
throw new Error(
'Please access `this.tray` after the `ready` event has fired.',
);
}
return this._tray;
}
/**
* The Electron [BrowserWindow](https://electronjs.org/docs/api/browser-window)
* instance, if it's present.
*/
get window(): BrowserWindow | undefined {
return this._browserWindow;
}
/**
* Retrieve a menubar option.
*
* @param key - The option key to retrieve, see {@link Options}.
*/
getOption(key: K): Options[K] {
return this._options[key];
}
/**
* Hide the menubar window.
*/
hideWindow(): void {
if (!this._browserWindow || !this._isVisible) {
return;
}
this.emit('hide');
this._browserWindow.hide();
this.emit('after-hide');
this._isVisible = false;
if (this._blurTimeout) {
clearTimeout(this._blurTimeout);
this._blurTimeout = null;
}
}
/**
* Change an option after menubar is created.
*
* @param key - The option key to modify, see {@link Options}.
* @param value - The value to set.
*/
setOption(key: K, value: Options[K]): void {
this._options[key] = value;
}
/**
* Show the menubar window.
*
* @param trayPos - The bounds to show the window in.
*/
async showWindow(trayPos?: Electron.Rectangle): Promise {
if (!this.tray) {
throw new Error('Tray should have been instantiated by now');
}
if (!this._browserWindow) {
await this.createWindow();
}
// Use guard for TypeScript, to avoid ! everywhere
if (!this._browserWindow) {
throw new Error('Window has been initialized just above. qed.');
}
// 'Windows' taskbar: sync windows position each time before showing
// https://github.com/maxogden/menubar/issues/232
if (['win32', 'linux'].includes(process.platform)) {
// Fill in this._options.windowPosition when taskbar position is available
this._options.windowPosition = getWindowPosition(this.tray);
}
this.emit('show');
if (trayPos && trayPos.x !== 0) {
// Cache the bounds
this._cachedBounds = trayPos;
} else if (this._cachedBounds) {
// Cached value will be used if showWindow is called without bounds data
trayPos = this._cachedBounds;
} else if (this.tray.getBounds) {
// Get the current tray bounds
trayPos = this.tray.getBounds();
}
// Default the window to the right if `trayPos` bounds are undefined or null.
let noBoundsPosition = undefined;
if (
(trayPos === undefined || trayPos.x === 0) &&
this._options.windowPosition &&
this._options.windowPosition.startsWith('tray')
) {
noBoundsPosition =
process.platform === 'win32' ? 'bottomRight' : 'topRight';
}
const position = this.positioner.calculate(
this._options.windowPosition || noBoundsPosition,
trayPos,
) as { x: number; y: number };
// Not using `||` because x and y can be zero.
const x =
this._options.browserWindow.x !== undefined
? this._options.browserWindow.x
: position.x;
const y =
this._options.browserWindow.y !== undefined
? this._options.browserWindow.y
: position.y;
// `.setPosition` crashed on non-integers
// https://github.com/maxogden/menubar/issues/233
this._browserWindow.setPosition(Math.round(x), Math.round(y));
this._browserWindow.show();
this._isVisible = true;
this.emit('after-show');
return;
}
private async appReady(): Promise {
if (this.app.dock && !this._options.showDockIcon) {
this.app.dock.hide();
}
if (this._options.activateWithApp) {
this.app.on('activate', (_event, hasVisibleWindows) => {
if (!hasVisibleWindows) {
this.showWindow().catch(console.error);
}
});
}
let trayImage =
this._options.icon || path.join(this._options.dir, 'IconTemplate.png');
if (typeof trayImage === 'string' && !fs.existsSync(trayImage)) {
trayImage = path.join(__dirname, '..', 'assets', 'IconTemplate.png'); // Default cat icon
}
const defaultClickEvent = this._options.showOnRightClick
? 'right-click'
: 'click';
this._tray = this._options.tray || new Tray(trayImage);
// Type guards for TS not to complain
if (!this.tray) {
throw new Error('Tray has been initialized above');
}
this.tray.on(
defaultClickEvent as Parameters[0],
this.clicked.bind(this),
);
this.tray.on('double-click', this.clicked.bind(this));
this.tray.setToolTip(this._options.tooltip);
if (!this._options.windowPosition) {
this._options.windowPosition = getWindowPosition(this.tray);
}
if (this._options.preloadWindow) {
await this.createWindow();
}
this.emit('ready');
}
/**
* Callback on tray icon click or double-click.
*
* @param e
* @param bounds
*/
private async clicked(
event?: Electron.KeyboardEvent,
bounds?: Electron.Rectangle,
): Promise {
if (event && (event.shiftKey || event.ctrlKey || event.metaKey)) {
return this.hideWindow();
}
// if blur was invoked clear timeout
if (this._blurTimeout) {
clearInterval(this._blurTimeout);
}
if (this._browserWindow && this._isVisible) {
return this.hideWindow();
}
this._cachedBounds = bounds || this._cachedBounds;
await this.showWindow(this._cachedBounds);
}
private async createWindow(): Promise {
this.emit('create-window');
// We add some default behavior for menubar's browserWindow, to make it
// look like a menubar
const defaults = {
show: false, // Don't show it at first
frame: false, // Remove window frame
};
this._browserWindow = new BrowserWindow({
...defaults,
...this._options.browserWindow,
});
this._positioner = new Positioner(this._browserWindow);
this._browserWindow.on('blur', () => {
if (!this._browserWindow) {
return;
}
// hack to close if icon clicked when open
this._browserWindow.isAlwaysOnTop()
? this.emit('focus-lost')
: (this._blurTimeout = setTimeout(() => {
this.hideWindow();
}, 100));
});
if (this._options.showOnAllWorkspaces !== false) {
// https://github.com/electron/electron/issues/37832#issuecomment-1497882944
this._browserWindow.setVisibleOnAllWorkspaces(true, {
skipTransformProcessType: true, // Avoid damaging the original visible state of app.dock
});
}
this._browserWindow.on('close', this.windowClear.bind(this));
this.emit('before-load');
// If the user explicity set options.index to false, we don't loadURL
// https://github.com/maxogden/menubar/issues/255
if (this._options.index !== false) {
await this._browserWindow.loadURL(
this._options.index,
this._options.loadUrlOptions,
);
}
this.emit('after-create-window');
}
private windowClear(): void {
this._browserWindow = undefined;
this.emit('after-close');
}
}
================================================
FILE: src/__mocks__/electron.ts
================================================
// https://github.com/electron/electron/issues/3909#issuecomment-190990825
export const MOCK_APP_GETAPPPATH = 'mock.app.getAppPath';
export const app = {
getAppPath: jest.fn(() => MOCK_APP_GETAPPPATH),
isReady: (): Promise => Promise.resolve(),
on: (): void => {
/* Do nothing */
},
};
export class BrowserWindow {
loadURL(): void {
// Do nothing
}
on(): void {
// Do nothing
}
setVisibleOnAllWorkspaces(): void {
// Do nothing
}
}
export class Tray {
on(): void {
// Do nothing
}
setToolTip(): void {
// Do nothing
}
}
================================================
FILE: src/ambient.d.ts
================================================
// TODO https://github.com/jenslind/electron-positioner/issues/15
declare module 'electron-positioner' {
export default class {
constructor(window: Electron.BrowserWindow);
calculate(
position?: string,
rectangle?: Electron.Rectangle,
): { x: number; y: number };
}
}
================================================
FILE: src/index.ts
================================================
/**
* Entry point of menubar
* @example
* ```typescript
* import { menubar } from 'menubar';
* ```
*/
/** */
import { app } from 'electron';
import { Menubar } from './Menubar';
import type { Options } from './types';
export * from './util/getWindowPosition';
export { Menubar };
/**
* Factory function to create a menubar application
*
* @param options - Options for creating a menubar application, see
* {@link Options}
*/
export function menubar(options?: Partial): Menubar {
return new Menubar(app, options);
}
================================================
FILE: src/types.ts
================================================
import type {
BrowserWindowConstructorOptions,
LoadURLOptions,
Tray,
} from 'electron';
/**
* Options for creating a menubar application
*/
export interface Options {
/**
* Listen on `app.on('activate')` to open menubar when app is activated.
* @default `true`
*/
activateWithApp?: boolean;
/**
* An Electron BrowserWindow instance, or an options object to be passed into
* the BrowserWindow constructor.
* @example
* ```typescript
* const options = { height: 640, width: 480 };
*
* const mb = new Menubar({
* browserWindow: options
* });
* ```
*/
browserWindow: BrowserWindowConstructorOptions;
/**
* The app source directory.
*/
dir: string;
/**
* The png icon to use for the menubar. A good size to start with is 20x20.
* To support retina, supply a 2x sized image (e.g. 40x40) with @2x added to
* the end of the name, so icon.png and icon@2x.png and Electron will
* automatically use your @2x version on retina screens.
*/
icon?: string | Electron.NativeImage;
/**
* The URL to load the menubar's browserWindow with. The url can be a remote
* address (e.g. `http://`) or a path to a local HTML file using the
* `file://` protocol. If false, then menubar won't call `loadURL` on
* start.
* @default `file:// + options.dir + index.html`
* @see https://electronjs.org/docs/api/browser-window#winloadurlurl-options
*/
index: string | false;
/**
* The options passed when loading the index URL in the menubar's
* browserWindow. Everything browserWindow.loadURL supports is supported;
* this object is simply passed onto browserWindow.loadURL
* @default `{}`
* @see https://electronjs.org/docs/api/browser-window#winloadurlurl-options
*/
loadUrlOptions?: LoadURLOptions;
/**
* Create BrowserWindow instance before it is used -- increasing resource
* usage, but making the click on the menubar load faster.
*/
preloadWindow?: boolean;
/**
* Configure the visibility of the application dock icon, macOS only. Calls
* [`app.dock.hide`](https://electronjs.org/docs/api/app#appdockhide-macos).
*/
showDockIcon?: boolean;
/**
* Makes the window available on all OS X workspaces. Calls
* [`setVisibleOnAllWorkspaces`](https://electronjs.org/docs/api/browser-window#winsetvisibleonallworkspacesvisible-options).
*/
showOnAllWorkspaces?: boolean;
/**
* Show the window on 'right-click' event instead of regular 'click'.
*/
showOnRightClick?: boolean;
/**
* Menubar tray icon tooltip text. Calls [`tray.setTooltip`](https://electronjs.org/docs/api/tray#traysettooltiptooltip).
*/
tooltip: string;
/**
* An electron Tray instance. If provided, `options.icon` will be ignored.
*/
tray?: Tray;
/**
* Sets the window position (x and y will still override this), check
* electron-positioner docs for valid values.
*/
windowPosition?:
| 'trayLeft'
| 'trayBottomLeft'
| 'trayRight'
| 'trayBottomRight'
| 'trayCenter'
| 'trayBottomCenter'
| 'topLeft'
| 'topRight'
| 'bottomLeft'
| 'bottomRight'
| 'topCenter'
| 'bottomCenter'
| 'leftCenter'
| 'rightCenter'
| 'center';
}
================================================
FILE: src/util/cleanOptions.spec.ts
================================================
import * as path from 'path';
import { MOCK_APP_GETAPPPATH } from '../__mocks__/electron';
import { cleanOptions } from './cleanOptions';
const DEFAULT_OPTIONS = {
activateWithApp: true,
browserWindow: {
height: 400,
width: 400,
},
dir: path.resolve(MOCK_APP_GETAPPPATH),
index: `file://${path.join(path.resolve(MOCK_APP_GETAPPPATH), 'index.html')}`,
loadUrlOptions: {},
tooltip: '',
};
describe('cleanOptions', () => {
it('should handle undefined', () => {
expect(cleanOptions(undefined)).toEqual(DEFAULT_OPTIONS);
});
it('should handle a dir string with relative path', () => {
expect(cleanOptions({ dir: 'MY_RELATIVE_PATH' })).toEqual({
...DEFAULT_OPTIONS,
dir: path.resolve('MY_RELATIVE_PATH'),
index: `file://${path.join(
path.resolve('MY_RELATIVE_PATH'),
'index.html',
)}`,
});
});
it('should handle a dir string with absolute path', () => {
expect(cleanOptions({ dir: '/home/me/MY_ABSOLUTE_PATH' })).toEqual({
...DEFAULT_OPTIONS,
dir: '/home/me/MY_ABSOLUTE_PATH',
index: 'file:///home/me/MY_ABSOLUTE_PATH/index.html',
});
});
it('should handle a false index', () => {
expect(cleanOptions({ index: false })).toEqual({
...DEFAULT_OPTIONS,
index: false,
});
});
it('should handle an object with multiple fields', () => {
expect(
cleanOptions({
browserWindow: {
height: 100,
},
index: 'file:///home/abc/index.html',
showDockIcon: true,
windowPosition: 'trayCenter',
}),
).toEqual({
...DEFAULT_OPTIONS,
browserWindow: {
...DEFAULT_OPTIONS.browserWindow,
height: 100,
},
index: 'file:///home/abc/index.html',
showDockIcon: true,
windowPosition: 'trayCenter',
});
});
});
================================================
FILE: src/util/cleanOptions.ts
================================================
/**
* @ignore
*/
/** */
import path from 'path';
import url from 'url';
import { app } from 'electron';
import type { Options } from '../types';
const DEFAULT_WINDOW_HEIGHT = 400;
const DEFAULT_WINDOW_WIDTH = 400;
/**
* Take as input some options, and return a sanitized version of it.
*
* @param opts - The options to clean.
* @ignore
*/
export function cleanOptions(opts?: Partial): Options {
const options: Partial = { ...opts };
if (options.activateWithApp === undefined) {
options.activateWithApp = true;
}
if (!options.dir) {
options.dir = app.getAppPath();
}
if (!path.isAbsolute(options.dir)) {
options.dir = path.resolve(options.dir);
}
// Note: options.index can be `false`
if (options.index === undefined) {
options.index = url.format({
pathname: path.join(options.dir, 'index.html'),
protocol: 'file:',
slashes: true,
});
}
options.loadUrlOptions = options.loadUrlOptions || {};
options.tooltip = options.tooltip || '';
// `icon`, `preloadWindow`, `showDockIcon`, `showOnAllWorkspaces`,
// `showOnRightClick` don't need any special treatment
// Now we take care of `browserWindow`
if (!options.browserWindow) {
options.browserWindow = {};
}
// Set width/height on options to be usable before the window is created
options.browserWindow.width =
// Note: not using `options.browserWindow.width || DEFAULT_WINDOW_WIDTH` so
// that users can put a 0 width
options.browserWindow.width !== undefined
? options.browserWindow.width
: DEFAULT_WINDOW_WIDTH;
options.browserWindow.height =
options.browserWindow.height !== undefined
? options.browserWindow.height
: DEFAULT_WINDOW_HEIGHT;
return options as Options;
}
================================================
FILE: src/util/getWindowPosition.ts
================================================
/**
* Utilities to get taskbar position and consequently menubar's position
*/
/** */
import { type Rectangle, type Tray, screen as electronScreen } from 'electron';
const isLinux = process.platform === 'linux';
const trayToScreenRects = (tray: Tray): [Rectangle, Rectangle] => {
// There may be more than one screen, so we need to figure out on which screen our tray icon lives.
const { workArea, bounds: screenBounds } = electronScreen.getDisplayMatching(
tray.getBounds(),
);
workArea.x -= screenBounds.x;
workArea.y -= screenBounds.y;
return [screenBounds, workArea];
};
type TaskbarLocation = 'top' | 'bottom' | 'left' | 'right';
/**
* Determine taskbard location: "top", "bottom", "left" or "right".
*
* Only tested on Windows for now, and only used in Windows.
*
* @param tray - The Electron Tray instance.
*/
export function taskbarLocation(tray: Tray): TaskbarLocation {
const [screenBounds, workArea] = trayToScreenRects(tray);
// TASKBAR LEFT
if (workArea.x > 0) {
// Most likely Ubuntu hence assuming the window should be on top
if (isLinux && workArea.y > 0) return 'top';
// The workspace starts more on the right
return 'left';
}
// TASKBAR TOP
if (workArea.y > 0) {
return 'top';
}
// TASKBAR RIGHT
// Here both workArea.y and workArea.x are 0 so we can no longer leverage them.
// We can use the workarea and display width though.
// Determine taskbar location
if (workArea.width < screenBounds.width) {
// The taskbar is either on the left or right, but since the LEFT case was handled above,
// we can be sure we're dealing with a right taskbar
return 'right';
}
// TASKBAR BOTTOM
// Since all the other cases were handled, we can be sure we're dealing with a bottom taskbar
return 'bottom';
}
type WindowPosition =
| 'trayCenter'
| 'topRight'
| 'trayBottomCenter'
| 'bottomLeft'
| 'bottomRight';
/**
* Depending on where the taskbar is, determine where the window should be
* positioned.
*
* @param tray - The Electron Tray instance.
*/
export function getWindowPosition(tray: Tray): WindowPosition {
switch (process.platform) {
// macOS
// Supports top taskbars
case 'darwin':
return 'trayCenter';
// Linux
// Windows
// Supports top/bottom/left/right taskbar
case 'linux':
case 'win32': {
const traySide = taskbarLocation(tray);
// Assign position for menubar
if (traySide === 'top') {
return isLinux ? 'topRight' : 'trayCenter';
}
if (traySide === 'bottom') {
return 'bottomRight';
}
if (traySide === 'left') {
return 'bottomLeft';
}
if (traySide === 'right') {
return 'bottomRight';
}
}
}
// When we really don't know, we just show the menubar on the top-right
return 'topRight';
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"strict": true,
"declaration": true,
"outDir": "./lib",
"skipLibCheck": true,
"target": "es5"
}
}
================================================
FILE: typedoc.js
================================================
module.exports = {
exclude: ['**/*spec.ts', '**/__mocks__/**'],
excludeExternals: true,
excludeNotExported: true,
excludePrivate: true,
excludeProtected: true,
hideGenerator: true,
includes: './src',
module: 'commonjs',
out: 'docs',
stripInternal: 'true',
theme: 'markdown',
};