Repository: max-mapper/menubar Branch: master Commit: 54eec00351a9 Files: 53 Total size: 71.6 KB Directory structure: gitextract_dw9q4gt_/ ├── .github/ │ ├── ISSUE_TEMPLATE.md │ ├── dependabot.yml │ └── workflows/ │ ├── lint.yml │ └── test.yml ├── .gitignore ├── .nvmrc ├── .vscode/ │ ├── extensions.json │ └── settings.json ├── CHANGELOG.md ├── COLLABORATORS.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── WORKING_PLATFORMS.md ├── assets/ │ └── Icon.icns ├── biome.json ├── docs/ │ ├── README.md │ ├── classes/ │ │ └── _menubar_.menubar.md │ ├── globals.md │ ├── interfaces/ │ │ └── _types_.options.md │ └── modules/ │ ├── _index_.md │ ├── _menubar_.md │ ├── _types_.md │ └── _util_getwindowposition_.md ├── examples/ │ ├── arrow/ │ │ ├── README.md │ │ ├── index.css │ │ ├── index.html │ │ ├── main.js │ │ └── package.json │ ├── hello-world/ │ │ ├── README.md │ │ ├── index.html │ │ ├── main.js │ │ └── package.json │ ├── icon-animation/ │ │ ├── index.html │ │ ├── main.js │ │ └── package.json │ ├── native-menu/ │ │ ├── README.md │ │ ├── main.js │ │ └── package.json │ └── package.json ├── jest.config.js ├── package.json ├── src/ │ ├── Menubar.spec.ts │ ├── Menubar.ts │ ├── __mocks__/ │ │ └── electron.ts │ ├── ambient.d.ts │ ├── index.ts │ ├── types.ts │ └── util/ │ ├── cleanOptions.spec.ts │ ├── cleanOptions.ts │ └── getWindowPosition.ts ├── tsconfig.json └── typedoc.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ [//]: # "Hey there and thank you for using Menubar's issue tracker!" [//]: # "Please fill in these information to describe your issue." ### Description ### Steps to Reproduce the Problem 1. ... 2. ... 3. ... ### Expected Behaviour ### Actual Behaviour ### Specifications - Menubar version: - Platform: - Electron version: (Run `electron -v` or `node_modules/.bin/electron -v` to get it) ### Other information ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: npm directory: "/" schedule: interval: weekly time: "02:00" timezone: Europe/Berlin open-pull-requests-limit: 10 ================================================ FILE: .github/workflows/lint.yml ================================================ name: Linting on: push: branches: - master pull_request: jobs: lint: name: Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - run: yarn install - run: yarn lint ================================================ FILE: .github/workflows/test.yml ================================================ name: Run Tests on: push: branches: - master pull_request: jobs: lint: name: Run Tests runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - run: yarn install - run: yarn test ================================================ FILE: .gitignore ================================================ .DS_Store node_modules coverage lib dist *.app *.log ================================================ FILE: .nvmrc ================================================ 20 ================================================ FILE: .vscode/extensions.json ================================================ { "recommendations": ["biomejs.biome"] } ================================================ FILE: .vscode/settings.json ================================================ { "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit", "source.organizeImports.biome": "explicit", "quickfix.biome": "explicit" }, "editor.defaultFormatter": "biomejs.biome" } ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. ### [9.5.2](https://github.com/maxogden/menubar/compare/v9.5.1...v9.5.2) (2025-10-10) ### [9.5.1](https://github.com/maxogden/menubar/compare/v9.5.0...v9.5.1) (2024-10-11) ## [9.5.0](https://github.com/maxogden/menubar/compare/v9.4.0...v9.5.0) (2024-06-25) ### Features * widen electron support ([#473](https://github.com/maxogden/menubar/issues/473)) ([bd3beb2](https://github.com/maxogden/menubar/commit/bd3beb2c2bc9700f4738a4e0f25085aa8a0f5e86)) ### Bug Fixes * Fix for Broken Native Menubar Example ([#471](https://github.com/maxogden/menubar/issues/471)) ([b000c86](https://github.com/maxogden/menubar/commit/b000c86777cb0cf6fa883fb0256410981e154ce1)) * win32 position ([#479](https://github.com/maxogden/menubar/issues/479)) ([9e5cb86](https://github.com/maxogden/menubar/commit/9e5cb86a276f29efd36d10a07e27fa820343f880)) ## [9.4.0](https://github.com/maxogden/menubar/compare/v9.3.0...v9.4.0) (2023-11-28) ### Features * Update electron to 27 ([#458](https://github.com/maxogden/menubar/issues/458)) ([f3dbac6](https://github.com/maxogden/menubar/commit/f3dbac66582baa322c2c8caa5027f1b8ef480e67)) ### Bug Fixes * Avoid setVisibleOnAllWorkspaces from destroying the original visible state of app.dock ([#442](https://github.com/maxogden/menubar/issues/442)) ([d8df2ab](https://github.com/maxogden/menubar/commit/d8df2ab4b65564c8ec58dc7156128bfd53fab2c9)) ## [9.3.0](https://github.com/maxogden/menubar/compare/v9.2.3...v9.3.0) (2023-02-13) ### Features * add electron 22 support ([#414](https://github.com/maxogden/menubar/issues/414)) ([a39382f](https://github.com/maxogden/menubar/commit/a39382fe843953395bf99267ee83102def70b06e)) ### [9.2.3](https://github.com/maxogden/menubar/compare/v9.2.2...v9.2.3) (2022-10-05) ### Features * electron 21 support ([#382](https://github.com/maxogden/menubar/issues/382)) ([c9d5b61](https://github.com/maxogden/menubar/commit/c9d5b61da2b55e20e9b2d0f4c0181c57f33a27c3)) ### [9.2.2](https://github.com/maxogden/menubar/compare/v9.2.1...v9.2.2) (2022-09-05) ### Features * Add Electron 20 as a peerDependency ([#379](https://github.com/maxogden/menubar/issues/379)) ([4c027b2](https://github.com/maxogden/menubar/commit/4c027b22499c7c83a4d87b9aa17d5e86f3172b68)) ### Bug Fixes * arrow example problem ([#342](https://github.com/maxogden/menubar/issues/342)) ([64b80d5](https://github.com/maxogden/menubar/commit/64b80d5f49ebb367b75e21e88af15fdc874cda86)) ### [9.2.1](https://github.com/maxogden/menubar/compare/v9.2.0...v9.2.1) (2022-07-11) ### Bug Fixes * Add support for electron 19 ([#376](https://github.com/maxogden/menubar/issues/376)) ([6d0feb0](https://github.com/maxogden/menubar/commit/6d0feb0e492681200966dde10a63a73cfb503138)) ## [9.2.0](https://github.com/maxogden/menubar/compare/v9.1.2...v9.2.0) (2022-04-11) ### Features * add `before-load` event ([#370](https://github.com/maxogden/menubar/issues/370)) ([3247986](https://github.com/maxogden/menubar/commit/3247986f164d8b2fb2912ab33beb25c9c6d8ece3)) ### [9.1.2](https://github.com/maxogden/menubar/compare/v9.1.1...v9.1.2) (2022-03-29) ### Bug Fixes * Add electron 17 support ([#373](https://github.com/maxogden/menubar/issues/373)) ([fafc29f](https://github.com/maxogden/menubar/commit/fafc29f168e91369ad5d60a61abc0363076c303a)) ### [9.1.1](https://github.com/maxogden/menubar/compare/v9.1.0...v9.1.1) (2021-12-09) ### Bug Fixes * Add electron 16 support ([#364](https://github.com/maxogden/menubar/issues/364)) ([325e151](https://github.com/maxogden/menubar/commit/325e1517cfc407bc0ccc98be776d8d9590d6f119)) ## [9.1.0](https://github.com/maxogden/menubar/compare/v9.0.6...v9.1.0) (2021-10-27) ### Features * add option activateWithApp to allow not activate with this event ([#361](https://github.com/maxogden/menubar/issues/361)) ([8384bf2](https://github.com/maxogden/menubar/commit/8384bf24abe6138aded26fcd7bd6d1d7a325f319)) ### [9.0.6](https://github.com/maxogden/menubar/compare/v9.0.5...v9.0.6) (2021-10-15) ### Bug Fixes * Add support for electron 14 and 15 ([#358](https://github.com/maxogden/menubar/issues/358)) ([8eaba8c](https://github.com/maxogden/menubar/commit/8eaba8cdfcd3ce9427ee3ffd65b3426c62f65048)) ### [9.0.5](https://github.com/maxogden/menubar/compare/v9.0.4...v9.0.5) (2021-06-28) ### Bug Fixes * Add support for Electron 13 ([#347](https://github.com/maxogden/menubar/issues/347)) ([fbf07bd](https://github.com/maxogden/menubar/commit/fbf07bd0bd24b2aac26cdd1db61eb55924f3ee63)) * window position on linux & windows when taskbar is on the left ([#343](https://github.com/maxogden/menubar/issues/343)) ([5d8e0c8](https://github.com/maxogden/menubar/commit/5d8e0c89996f67b58f059ec767a87db104a90292)) ### [9.0.4](https://github.com/maxogden/menubar/compare/v9.0.3...v9.0.4) (2021-05-03) ### Bug Fixes * Add support for Electron 12 ([#332](https://github.com/maxogden/menubar/issues/332)) ([c1f055d](https://github.com/maxogden/menubar/commit/c1f055daed76be2d0f408fda5d4835defcd59dcc)) ### [9.0.3](https://github.com/maxogden/menubar/compare/v9.0.2...v9.0.3) (2021-02-24) ### Bug Fixes * Add support for Electron 10 and 11 ([#321](https://github.com/maxogden/menubar/issues/321)) ([4a89656](https://github.com/maxogden/menubar/commit/4a8965628a0a1a7a14602fef3add7bef436a508f)) ### [9.0.2](https://github.com/maxogden/menubar/compare/v9.0.1...v9.0.2) (2021-01-20) ### Bug Fixes * Improve 'windows' OS detection of taskbar location ([#307](https://github.com/maxogden/menubar/issues/307)) ([4726584](https://github.com/maxogden/menubar/commit/4726584664148a57656c40872836ebba2d030980)) ### [9.0.1](https://github.com/maxogden/menubar/compare/v9.0.0...v9.0.1) (2020-05-28) ### Bug Fixes * calling showWindow() prevents menubar window from closing ([#287](https://github.com/maxogden/menubar/issues/287)) ([53d8f82](https://github.com/maxogden/menubar/commit/53d8f82b604ad5555f59108a97234ebf32e43f80)) ## [9.0.0](https://github.com/maxogden/menubar/compare/v8.0.2...v9.0.0) (2020-05-27) ### ⚠ BREAKING CHANGES * Please use Electron 9 with this menubar version. ### Features * Support Electron 9 ([#286](https://github.com/maxogden/menubar/issues/286)) ([44cf1b1](https://github.com/maxogden/menubar/commit/44cf1b1e6cce83f9e63a39f1d32fbb664396e7bc)) ### [8.0.2](https://github.com/maxogden/menubar/compare/v8.0.1...v8.0.2) (2020-04-27) ### Bug Fixes * Show window on dock icon click ([#279](https://github.com/maxogden/menubar/issues/279)) ([a8607fa](https://github.com/maxogden/menubar/commit/a8607fa708d229d9124471127482fe461198f1f3)) * update prevent flicker on Windows (fixes [#274](https://github.com/maxogden/menubar/issues/274)) ([#276](https://github.com/maxogden/menubar/issues/276)) ([9592f34](https://github.com/maxogden/menubar/commit/9592f3437ce3660b6464f5b436ed111291eb75d3)) ### [8.0.1](https://github.com/maxogden/menubar/compare/v8.0.0...v8.0.1) (2020-03-14) ### Bug Fixes * **deps:** [Security] bump acorn from 6.1.1 to 6.4.1 ([#272](https://github.com/maxogden/menubar/issues/272)) ([1332b77](https://github.com/maxogden/menubar/commit/1332b774372de69c04e2a098833ae35775c35cad)) ## [8.0.0](https://github.com/maxogden/menubar/compare/v7.2.0...v8.0.0) (2020-02-10) ### ⚠ BREAKING CHANGES * Menubar's recommended peer dependency is `electron@^8.0.0` ### Features * Support Electron 8 ([#268](https://github.com/maxogden/menubar/issues/268)) ([ad99c5a](https://github.com/maxogden/menubar/commit/ad99c5add02ab6d0d751cf6bda8a2c96c674620f)) ## [7.2.0](https://github.com/maxogden/menubar/compare/v7.1.0...v7.2.0) (2020-01-16) ### Features * Adding a loadUrlOptions option ([#263](https://github.com/maxogden/menubar/issues/263)) ([8e6bd01](https://github.com/maxogden/menubar/commit/8e6bd0154aaee02a5d601bbe37c51c55065c3923)) ## [7.1.0](https://github.com/maxogden/menubar/compare/v7.0.0...v7.1.0) (2019-11-25) ### Features * Allow skipping loadUrl ([#257](https://github.com/maxogden/menubar/issues/257)) ([095486a](https://github.com/maxogden/menubar/commit/095486ab338df26fc4d6a1e7a658cfa9fa4a69b7)) # [7.0.0](https://github.com/maxogden/menubar/compare/v6.0.8...v7.0.0) (2019-10-23) * feat!: Support Electron 7 (#250) ([b54dce5](https://github.com/maxogden/menubar/commit/b54dce5)), closes [#250](https://github.com/maxogden/menubar/issues/250) ### BREAKING CHANGES * - Drop support for Electron 4, 5, and 6. - Remove deprecated passing string argument to `menubar`, use `dir` field instead ```diff - menubar('/home/me/MY_ABSOLUTE_PATH'); + menubar({ dir: '/home/me/MY_ABSOLUTE_PATH' }); ``` - Remove deprecated passing `x`, `y`, `height`, `width`, `alwaysOnTop` fields to `menubar`, pass them instead into the `browserWindow` field ```diff - menubar({ - x: 12, - y: 34, - height: 500, - width: 320, - alwaysOnTop: true - }); + menubar({ + browserWindow: { + x: 12, + y: 34, + height: 500, + width: 320, + alwaysOnTop: true + } + }); ``` ## [6.0.8](https://github.com/maxogden/menubar/compare/v6.0.7...v6.0.8) (2019-09-16) ### Bug Fixes * Move doc tool to `devDependencies` ([#245](https://github.com/maxogden/menubar/issues/245)) ([2756c7a](https://github.com/maxogden/menubar/commit/2756c7a)) ## [6.0.7](https://github.com/maxogden/menubar/compare/v6.0.6...v6.0.7) (2019-07-31) ### Bug Fixes * Support Electron 6 ([#242](https://github.com/maxogden/menubar/issues/242)) ([1fd9bd7](https://github.com/maxogden/menubar/commit/1fd9bd7)) ## [6.0.6](https://github.com/maxogden/menubar/compare/v6.0.5...v6.0.6) (2019-07-02) ### Bug Fixes * Fix crash on windows position ([#235](https://github.com/maxogden/menubar/issues/235)) ([cbbe175](https://github.com/maxogden/menubar/commit/cbbe175)) ## [6.0.5](https://github.com/maxogden/menubar/compare/v6.0.4...v6.0.5) (2019-06-11) ### Bug Fixes * Remove postinstall, export taskbarLocation ([#226](https://github.com/maxogden/menubar/issues/226)) ([941b3be](https://github.com/maxogden/menubar/commit/941b3be)) ## [6.0.4](https://github.com/maxogden/menubar/compare/v6.0.3...v6.0.4) (2019-06-11) ### Bug Fixes * Correct position on Windows & multi-taskbar ([#217](https://github.com/maxogden/menubar/issues/217)) ([4f29fe2](https://github.com/maxogden/menubar/commit/4f29fe2)), closes [#196](https://github.com/maxogden/menubar/issues/196) * Fix double 'after-hide' event ([#216](https://github.com/maxogden/menubar/issues/216)) ([a4d900e](https://github.com/maxogden/menubar/commit/a4d900e)) ## [6.0.3](https://github.com/maxogden/menubar/compare/v6.0.2...v6.0.3) (2019-06-05) ### Bug Fixes * Fix accessing Menubar.window ([#214](https://github.com/maxogden/menubar/issues/214)) ([cd5ef73](https://github.com/maxogden/menubar/commit/cd5ef73)) ## [6.0.2](https://github.com/maxogden/menubar/compare/v6.0.1...v6.0.2) (2019-05-31) ### Bug Fixes * Use cat icon if no icon provided ([#205](https://github.com/maxogden/menubar/issues/205)) ([fc02e02](https://github.com/maxogden/menubar/commit/fc02e02)) ## [6.0.1](https://github.com/maxogden/menubar/compare/v6.0.0...v6.0.1) (2019-05-31) ### Bug Fixes * Fix changelog links ([#204](https://github.com/maxogden/menubar/issues/204)) ([de96756](https://github.com/maxogden/menubar/commit/de96756)) # [6.0.0](https://github.com/maxogden/menubar/compare/v5.2.3...v6.0.0) (2019-05-31) ### Bug Fixes * Update to Electron 5 ([#15](https://github.com/amaurym/g/menubar/issues/15)) ([ce86216](https://github.com/maxogden/menubar/commit/ce86216)) * Window does not show when already app is ready ([#8](https://github.com/amaurym/g/menubar/issues/8)) ([251fb21](https://github.com/maxogden/menubar/commit/251fb21)) ### Code Refactoring * Convert all codebase to typescript ([#2](https://github.com/amaurym/g/menubar/issues/2)) ([820d41a](https://github.com/maxogden/menubar/commit/820d41a)) ### Features * Add `browserWindow` field in options, deprecate `height`, `width`, `x`, `y`, `alwaysOnTop` in favor of `browserWindow` ([#18](https://github.com/amaurym/g/menubar/issues/18)) ([0b2d897](https://github.com/maxogden/menubar/commit/0b2d897)) * Support Electron.NativeImage icon argument ([#7](https://github.com/amaurym/g/menubar/issues/7)) ([03d67f3](https://github.com/maxogden/menubar/commit/03d67f3)) ### BREAKING CHANGES * We're using a named export in Typescript now: ```diff - var menubar = require('menubar'); + var { menubar } = require('menubar'); ``` Alternatively, using ES6/TS syntax: ```javascript import { menubar } from 'menubar'; ``` ================================================ FILE: COLLABORATORS.md ================================================ ## Collaborators menubar is only possible due to the excellent work of the following collaborators:
maxogdenGitHub/maxogden
fritzyGitHub/fritzy
pquernaGitHub/pquerna
fabien-dGitHub/fabien-d
jenslindGitHub/jenslind
amaurymGitHub/amaurym
================================================ 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 ================================================ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/max-mapper/menubar/test.yml) [![npm (scoped)](https://img.shields.io/npm/v/menubar.svg)](https://www.npmjs.com/package/menubar) ![Libraries.io dependency status for GitHub repo](https://img.shields.io/librariesio/github/max-mapper/menubar) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/menubar.svg) ![npm bundle size](https://img.shields.io/bundlephobia/min/menubar.svg)


➖ 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) / [![Build Status](https://travis-ci.org/maxogden/menubar.svg?branch=master)](https://travis-ci.org/maxogden/menubar) [![npm (scoped)](https://img.shields.io/npm/v/menubar.svg)](https://www.npmjs.com/package/@maxogden/menubar) [![dependencies Status](https://david-dm.org/maxogden/menubar/status.svg)](https://david-dm.org/maxogden/menubar) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/menubar.svg) ![npm bundle size](https://img.shields.io/bundlephobia/min/menubar.svg)


➖ 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 ![screenshot](./screenshot.png) ## 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 ![screenshot](./screenshot.png) ## 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 ![screenshot](./screenshot.png) ## 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', };