├── .gitignore ├── icons ├── grey.png ├── icon.psd ├── red.png ├── black.png ├── green.png ├── white.png └── yellow.png ├── lib ├── traywin.v12.suo ├── traywin.sln ├── traywin.csproj └── tray.cs ├── icons.js ├── package.json ├── example.js ├── LICENSE.md ├── compat.js ├── index.js ├── README.md └── CONTRIBUTING.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bin 3 | obj 4 | .suo -------------------------------------------------------------------------------- /icons/grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junosuarez/tray-windows/HEAD/icons/grey.png -------------------------------------------------------------------------------- /icons/icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junosuarez/tray-windows/HEAD/icons/icon.psd -------------------------------------------------------------------------------- /icons/red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junosuarez/tray-windows/HEAD/icons/red.png -------------------------------------------------------------------------------- /icons/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junosuarez/tray-windows/HEAD/icons/black.png -------------------------------------------------------------------------------- /icons/green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junosuarez/tray-windows/HEAD/icons/green.png -------------------------------------------------------------------------------- /icons/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junosuarez/tray-windows/HEAD/icons/white.png -------------------------------------------------------------------------------- /icons/yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junosuarez/tray-windows/HEAD/icons/yellow.png -------------------------------------------------------------------------------- /lib/traywin.v12.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junosuarez/tray-windows/HEAD/lib/traywin.v12.suo -------------------------------------------------------------------------------- /icons.js: -------------------------------------------------------------------------------- 1 | var file = function (path) { return function () { return require('fs').readFileSync(path); } } 2 | module.exports = { 3 | red: file(__dirname + '/icons/red.png'), 4 | yellow: file(__dirname + '/icons/yellow.png'), 5 | green: file(__dirname + '/icons/green.png'), 6 | black: file(__dirname + '/icons/black.png'), 7 | white: file(__dirname + '/icons/white.png'), 8 | grey: file(__dirname + '/icons/grey.png'), 9 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tray-windows", 3 | "version": "1.0.1", 4 | "description": "npm module to create system tray apps on Windows", 5 | "tags": [ 6 | "tray", 7 | "system tray", 8 | "status", 9 | "windows" 10 | ], 11 | "main": "index.js", 12 | "scripts": { 13 | "test": "echo ok" 14 | }, 15 | "author": "jden ", 16 | "license": "ISC", 17 | "dependencies": { 18 | "debug": "^2.1.3", 19 | "edge": "^0.10.1", 20 | "xtend": "^4.0.0" 21 | }, 22 | "repository": "https://github.com/jden/tray-windows" 23 | } 24 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var tray = require('./.') 2 | 3 | tray({ 4 | name: 'jden', 5 | items: ['+','--', 'C', '-', 'Exit'], 6 | icon: tray.icons.grey() 7 | }, function (err, app) { 8 | if (err) { return console.log('e', err) } 9 | console.log('app running') 10 | 11 | app.on('click:menuItem', function (menuItem) { 12 | console.log('clicked',menuItem) 13 | 14 | 15 | if (menuItem.text === '+') { 16 | app.addMenuItem('bob') 17 | } 18 | if (menuItem.text === '--') { 19 | app.delMenuItem('bob') 20 | } 21 | if (menuItem.text === 'Exit') { 22 | app.exit() 23 | } 24 | 25 | // app.delMenuItemAt(menuItem.index) 26 | if (menuItem.text === 'C') { 27 | app.dropMenu() 28 | } 29 | 30 | }) 31 | 32 | 33 | var white = tray.icons.white() 34 | var black = tray.icons.black() 35 | var i = 0 36 | setInterval(function () { 37 | if (i++ % 2) { 38 | app.updateIcon(white) 39 | } else { 40 | app.updateIcon(black) 41 | } 42 | }, 1000) 43 | 44 | 45 | 46 | 47 | }) -------------------------------------------------------------------------------- /lib/traywin.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "traywin", "traywin.csproj", "{2BF3230B-8670-4BA6-9BA5-232E88BB604E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {2BF3230B-8670-4BA6-9BA5-232E88BB604E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {2BF3230B-8670-4BA6-9BA5-232E88BB604E}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {2BF3230B-8670-4BA6-9BA5-232E88BB604E}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {2BF3230B-8670-4BA6-9BA5-232E88BB604E}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2015 Jason Denizac jden 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /compat.js: -------------------------------------------------------------------------------- 1 | var tray = require('./index') 2 | 3 | module.exports = function createTray(app, cb) { 4 | // app isn't used, but kept for api compatibility with [node-tray](https://github.com/brandonhorst/node-tray) 5 | // for that matter, we don't need this initial callback, since our "specify" is done during init 6 | var state = {closed: false, map: {}} 7 | 8 | function set(opt) { 9 | state.app.dropMenu() 10 | state.map = {} 11 | opt.menuItems.forEach(function (item) { 12 | state.map[item.title] = item 13 | state.app.addMenuItem(item.title) 14 | } 15 | } 16 | 17 | cb(null, { 18 | specify: function (opt) { 19 | 20 | if (!state.app) { 21 | state.app = tray({ 22 | name: opt.title, 23 | items: [] 24 | }, function (err, app) { 25 | if (err) { throw err } 26 | app.on('click:menuItem', function (menuItem) { 27 | var item = state.map[menuItem] 28 | if (item.action) { 29 | item.action(item.data) 30 | } 31 | }) 32 | }) 33 | } 34 | 35 | set(opt) 36 | }, 37 | close: function () { 38 | if (!state.closed) { 39 | if (state.app) { 40 | state.app.exit() 41 | delete state.app 42 | } 43 | state.closed = true 44 | } else { 45 | throw new Error('App closed') 46 | } 47 | } 48 | }) 49 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var edge = require('edge') 2 | var extend = require('xtend') 3 | var EventEmitter = require('events').EventEmitter 4 | var icons = require('./icons') 5 | var debug = require('debug')('tray-windows') 6 | var path = require('path') 7 | 8 | var raw = edge.func({source: path.resolve(__dirname, 'lib/tray.cs'), references: ['System.Drawing.dll','System.Windows.Forms.dll']}) 9 | 10 | function tray(opt, cb) { 11 | var ctx = new EventEmitter 12 | opt = opt || {} 13 | opt.icon = opt.icon || icons.grey() 14 | 15 | ctx.addMenuItem = function (item) { 16 | messages.push({ 17 | e: 'add:menuItem', 18 | data: item 19 | }) 20 | return this 21 | } 22 | 23 | ctx.delMenuItem = function (item) { 24 | messages.push({ 25 | e: 'del:menuItem', 26 | data: item 27 | }) 28 | return this 29 | } 30 | 31 | ctx.delMenuItemAt = function (index) { 32 | messages.push({ 33 | e: 'del:menuItem:at', 34 | data: index 35 | }) 36 | return this 37 | } 38 | 39 | ctx.exit = function () { 40 | messages.push({ 41 | e: 'exit', 42 | data: null 43 | }) 44 | // stop additional messages from being queued 45 | messages.push = function () {} 46 | return this 47 | } 48 | 49 | ctx.dropMenu = function () { 50 | messages.push({ 51 | e: 'drop:menu', 52 | data: null 53 | }) 54 | return this 55 | } 56 | 57 | ctx.updateIcon = function (buffer) { 58 | messages.push({ 59 | e: 'updateIcon', 60 | data: buffer 61 | }) 62 | } 63 | 64 | var messages = [] 65 | 66 | var args = extend(opt, { 67 | eventHandler: eventHandler(ctx), 68 | checkMessage: check(messages) 69 | }) 70 | 71 | ctx.on('start', function (send) { 72 | cb(null, ctx) 73 | }) 74 | 75 | try { 76 | raw(args, function (err, r) { 77 | if (err) { 78 | ctx.emit('error', err) 79 | } 80 | }) 81 | 82 | } catch (e) { 83 | e.message = 'Startup Error: ' + e.message 84 | cb(e) 85 | } 86 | } 87 | 88 | function check(messages) { 89 | return function (obj, cb) { 90 | var message = messages.shift() 91 | // var message = messages[0] 92 | cb(null, message) 93 | } 94 | } 95 | 96 | function eventHandler(ctx) { 97 | return function (obj, cb) { 98 | debug('event', obj) 99 | cb(null, "ok") 100 | ctx.emit(obj.e, obj.data) 101 | } 102 | } 103 | 104 | module.exports = tray 105 | module.exports.icons = icons -------------------------------------------------------------------------------- /lib/traywin.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2BF3230B-8670-4BA6-9BA5-232E88BB604E} 8 | Library 9 | Properties 10 | traywin 11 | traywin 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | Form 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tray-windows 2 | npm module to create system tray apps on Windows 3 | 4 | ## Installation 5 | ``` 6 | > npm install --save tray-windows 7 | ``` 8 | 9 | 10 | ## Usage 11 | See `example.js` for more 12 | ```js 13 | var tray = require('tray-windows') 14 | 15 | tray({ 16 | name: 'Tooltip title', 17 | items: ['A','B','C'], 18 | icon: tray.icons.green() 19 | }, function (err, app) { 20 | if (err) { throw err } 21 | 22 | app.on('click:menuItem', function (item) { 23 | console.log(item) 24 | // {index: 0, text: 'A'} 25 | }) 26 | 27 | app.addMenuItem('D') 28 | app.delMenuItem('B') 29 | app.delMenuItemAt(1) // 0-based index 30 | app.dropMenu() // remove all menu items 31 | app.exit() // close the tray app; node still running 32 | 33 | app.updateIcon(tray.icons.red()) 34 | }) 35 | ``` 36 | 37 | 38 | ## Icons 39 | Icons should be Buffers of 20x20 PNGs (alpha ok). 40 | Basic icons are included for the following colors: 41 | 42 | Tray.icons.red() 43 | Tray.icons.yellow() 44 | Tray.icons.green() 45 | Tray.icons.black() 46 | Tray.icons.white() 47 | Tray.icons.grey() 48 | 49 | The PSD used to produce these icons is included in the git repo in `icons/icon.psd` 50 | 51 | 52 | ## Compatibility 53 | This module was inspired by [tray](https://github.com/brandonhorst/node-tray) for OS X. If you're trying to 54 | write something that will work cross-platform, you can try the compatibility layer: 55 | 56 | ```js 57 | var createTray = require('tray-windows/compat') 58 | var createApp = function (cb) { 59 | // since `native-app`is only for OSX, we don't depend on it- 60 | // this function isn't needed, but shown here mocked out 61 | // to use the example code 62 | cb(null, null) 63 | } 64 | 65 | // sample taken verbatim from `tray` readme: 66 | 67 | createApp(function (err, app) { 68 | createTray(app, function (err, tray) { 69 | tray.specify({ 70 | title: 'Hello, world!', 71 | menuItems: [ 72 | {title: 'Informational'}, 73 | { 74 | title: 'Do something', 75 | shortcut: 'x', 76 | action: function () { console.log('You pressed a menuItem!') } 77 | } 78 | ] 79 | }) 80 | }) 81 | }) 82 | 83 | ``` 84 | 85 | ## Requirements 86 | 87 | - Node 88 | - Windows 89 | - .Net 4.5+ 90 | 91 | Tested on Windows 8 x64. If you try another configuration, please let me know in an issue if it worked or not. 92 | 93 | 94 | ## Contributing 95 | 96 | Contributions welcome! Please see CONTRIBUTING.md 97 | 98 | - jden 99 | 100 | ## License 101 | 102 | (c) MMXV jden. ISC License. See LICENSE.md. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Request for contributions 2 | 3 | Please contribute to this repository if any of the following is true: 4 | - You have expertise in Windows desktop development 5 | - You want to make writing cross-platform desktop apps in node better 6 | 7 | # How to contribute 8 | 9 | Prerequisites: 10 | 11 | - Familiarity with [pull requests](https://help.github.com/articles/using-pull-requests) and [issues](https://guides.github.com/features/issues/). 12 | 13 | 14 | In particular, this community seeks the following types of contributions: 15 | 16 | - **Ideas**: participate in an issue thread or start your own to have your voice 17 | heard. 18 | - **Code**: this project uses JavaScript and C# (via [edge.js](https://github.com/tjanczuk/edge)) 19 | - **Writing**: contribute your expertise in an area by helping us expand the included 20 | documentation and examples. 21 | - **Formatting**: help keep code and content easy to read with consistent formatting. 22 | 23 | # Conduct 24 | 25 | We are committed to providing a friendly, safe and welcoming environment for 26 | all, regardless of gender, sexual orientation, disability, ethnicity, religion, 27 | or similar personal characteristic. 28 | 29 | On IRC, please avoid using overtly sexual nicknames or other nicknames that 30 | might detract from a friendly, safe and welcoming environment for all. 31 | 32 | Please be kind and courteous. There's no need to be mean or rude. 33 | Respect that people have differences of opinion and that every design or 34 | implementation choice carries a trade-off and numerous costs. There is seldom 35 | a right answer, merely an optimal answer given a set of values and 36 | circumstances. 37 | 38 | Please keep unstructured critique to a minimum. If you have solid ideas you 39 | want to experiment with, make a fork and see how it works. 40 | 41 | We will exclude you from interaction if you insult, demean or harass anyone. 42 | That is not welcome behaviour. We interpret the term "harassment" as 43 | including the definition in the 44 | [Citizen Code of Conduct](http://citizencodeofconduct.org/); 45 | if you have any lack of clarity about what might be included in that concept, 46 | please read their definition. In particular, we don't tolerate behavior that 47 | excludes people in socially marginalized groups. 48 | 49 | Private harassment is also unacceptable. No matter who you are, if you feel 50 | you have been or are being harassed or made uncomfortable by a community 51 | member, please contact one of the 52 | [tray-windows](https://github.com/jden/tray-windows) core team 53 | immediately. Whether you're a regular contributor or a newcomer, we care about 54 | making this community a safe place for you and we've got your back. 55 | 56 | Likewise any spamming, trolling, flaming, baiting or other attention-stealing 57 | behaviour is not welcome. 58 | 59 | # Communication 60 | 61 | GitHub issues are the primary way for communicating about specific proposed 62 | changes to this project. 63 | 64 | In all communication, please follow the conduct guidelines above. Language issues 65 | are often contentious and we'd like to keep discussion brief, civil and focused 66 | on what we're actually doing, not wandering off into too much imaginary stuff. 67 | 68 | # Frequently Asked Questions 69 | 70 | *please add* 71 | -------------------------------------------------------------------------------- /lib/tray.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Windows.Forms; 4 | using System.Threading.Tasks; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Reflection; 9 | 10 | public class Startup 11 | { 12 | private Func> eventHandler; 13 | private Func> checkMessage; 14 | private SysTrayApp app; 15 | 16 | public async Task Invoke(dynamic input) 17 | { 18 | var name = (string)input.name; 19 | var items = ((object[])input.items).ToList().Cast(); 20 | 21 | this.eventHandler = (Func>)input.eventHandler; 22 | this.checkMessage = (Func>)input.checkMessage; 23 | 24 | 25 | 26 | var i = new Input 27 | { 28 | Name = name, 29 | Items = items, 30 | TriggerEvent = this.SendEvent 31 | }; 32 | 33 | try 34 | { 35 | var bytes = (byte[])input.icon; 36 | var source = Image.FromStream(new System.IO.MemoryStream(bytes)); 37 | 38 | var target = new Bitmap(40, 40, System.Drawing.Imaging.PixelFormat.Format32bppArgb); 39 | var g = Graphics.FromImage(target); 40 | g.DrawImage(source, 0, 0, 40, 40); 41 | i.Icon = Icon.FromHandle(target.GetHicon()); 42 | 43 | } 44 | catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException) 45 | { 46 | // no icon specified 47 | } 48 | 49 | // for some reason, have to call with await first, then it works for app lifetime 50 | //await eventHandler(new { e = "start", data = false }); 51 | Func> receive = (x) => 52 | { 53 | ReceiveEvent("ev", x); 54 | return Task.FromResult(true); 55 | }; 56 | await eventHandler(new { e = "start", data = receive}); 57 | 58 | 59 | 60 | app = new SysTrayApp(i); 61 | var t = new System.Timers.Timer(20); 62 | t.AutoReset = true; 63 | t.Elapsed += onTick; 64 | t.Start(); 65 | Application.Run(app); 66 | SendEvent("stop", null); 67 | 68 | return 0; 69 | } 70 | 71 | void onTick(object sender, EventArgs t) 72 | { 73 | Check(); 74 | } 75 | 76 | private async void Check() 77 | { 78 | var m = await checkMessage(null); 79 | var message = (dynamic)m; 80 | try 81 | { 82 | var e = (string)message.e; 83 | var data = (object)message.data; 84 | ReceiveEvent(e, data); 85 | } 86 | catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException) 87 | { 88 | // no message 89 | } 90 | } 91 | 92 | public void SendEvent (string e, object data) { 93 | var message = new 94 | { 95 | e = e, 96 | data = data ?? false 97 | }; 98 | 99 | eventHandler(message).ContinueWith(x => 100 | { 101 | // required to invoke node fn 102 | }); 103 | } 104 | 105 | public void ReceiveEvent(string name, dynamic data) 106 | { 107 | switch (name) 108 | { 109 | case "add:menuItem": 110 | addMenuItem((string) data); 111 | break; 112 | case "del:menuItem": 113 | delMenuItem((string)data); 114 | break; 115 | case "del:menuItem:at": 116 | delMenuItemAt((int)data); 117 | break; 118 | case "exit": 119 | Application.Exit(); 120 | break; 121 | case "drop:menu": 122 | app.trayMenu.MenuItems.Clear(); 123 | break; 124 | case "updateIcon": 125 | updateIcon((byte[])data); 126 | break; 127 | } 128 | } 129 | 130 | private void updateIcon(byte[] data) 131 | { 132 | var source = Image.FromStream(new System.IO.MemoryStream(data)); 133 | 134 | var target = new Bitmap(40, 40, System.Drawing.Imaging.PixelFormat.Format32bppArgb); 135 | var g = Graphics.FromImage(target); 136 | g.DrawImage(source, 0, 0, 40, 40); 137 | var icon = Icon.FromHandle(target.GetHicon()); 138 | 139 | app.trayIcon.Icon = icon; 140 | } 141 | 142 | private void addMenuItem(string item) 143 | { 144 | app.trayMenu.MenuItems.Add(app.CreateMenuItem(item)); 145 | } 146 | 147 | private void delMenuItem(string item) 148 | { 149 | var menuItem = Tools.ToList(app.trayMenu.MenuItems).Where(i => i.Text == item).FirstOrDefault(); 150 | if (menuItem != null) 151 | { 152 | app.trayMenu.MenuItems.Remove(menuItem); 153 | } 154 | 155 | } 156 | private void delMenuItemAt(int index) 157 | { 158 | app.trayMenu.MenuItems.RemoveAt(index); 159 | } 160 | 161 | 162 | } 163 | 164 | public static class Tools 165 | { 166 | public static List ToList(this IEnumerable source) where T : class 167 | { 168 | var list = new List(); 169 | var e = source.GetEnumerator(); 170 | foreach (var i in source) { 171 | list.Add(i as T); 172 | } 173 | return list; 174 | } 175 | } 176 | 177 | public class Input 178 | { 179 | public string Name { get; set; } 180 | public IEnumerable Items { get; set; } 181 | public Icon Icon { get; set; } 182 | public Action TriggerEvent { get; set; } 183 | 184 | public Input() 185 | { 186 | 187 | } 188 | } 189 | 190 | public class SysTrayApp : Form 191 | { 192 | 193 | public NotifyIcon trayIcon; 194 | public ContextMenu trayMenu; 195 | private Input input; 196 | 197 | public SysTrayApp(Input input) 198 | { 199 | this.input = input; 200 | // Create a simple tray menu with only one item. 201 | trayMenu = new ContextMenu(); 202 | 203 | trayMenu.MenuItems.AddRange(input.Items.Select(CreateMenuItem).ToArray()); 204 | 205 | trayIcon = new NotifyIcon(); 206 | 207 | trayIcon.Text = input.Name; 208 | trayIcon.Icon = input.Icon ?? new Icon(SystemIcons.Application, 40, 40); 209 | 210 | // Add menu to tray icon and show it. 211 | trayIcon.ContextMenu = trayMenu; 212 | trayIcon.Visible = true; 213 | trayIcon.Click += OnIconClick; 214 | } 215 | 216 | public MenuItem CreateMenuItem(string text) 217 | { 218 | return new MenuItem(text, OnMenuItemClick); 219 | } 220 | 221 | private void OnIconClick(object sender, EventArgs e) 222 | { 223 | 224 | var show = typeof(NotifyIcon).GetMethod("ShowContextMenu", BindingFlags.Instance | BindingFlags.NonPublic); 225 | show.Invoke(trayIcon, null); 226 | } 227 | 228 | private void OnMenuItemClick(object sender, EventArgs e) 229 | { 230 | var item = (MenuItem)sender; 231 | var data = new 232 | { 233 | index = item.Index, 234 | text = item.Text 235 | }; 236 | 237 | this.input.TriggerEvent("click:menuItem", data); 238 | } 239 | 240 | protected override void OnLoad(EventArgs e) 241 | { 242 | Visible = false; // Hide form window. 243 | ShowInTaskbar = false; // Remove from taskbar. 244 | 245 | base.OnLoad(e); 246 | } 247 | 248 | private void OnExit(object sender, EventArgs e) 249 | { 250 | Application.Exit(); 251 | } 252 | 253 | protected override void Dispose(bool isDisposing) 254 | { 255 | if (isDisposing) 256 | { 257 | // Release the icon resource. 258 | trayIcon.Dispose(); 259 | } 260 | 261 | base.Dispose(isDisposing); 262 | } 263 | } 264 | --------------------------------------------------------------------------------