not-the-systray
Version:
Because calling it the system tray [would be wrong](https://devblogs.microsoft.com/oldnewthing/20030910-00/?p=42583).
135 lines (121 loc) • 3.94 kB
JavaScript
const native = require('./notify_icon.node');
const { NotifyIcon, Icon, Menu } = native;
module.exports = { NotifyIcon, Icon, Menu };
Object.defineProperties(Menu, {
createTemplate: { value: createMenuTemplate, enumerable: true },
});
Object.defineProperties(Icon, {
ids: {
enumerable: true,
value: Object.create(null, {
app: { value: 32512, enumerable: true },
error: { value: 32513, enumerable: true },
question: { value: 32514, enumerable: true },
warning: { value: 32515, enumerable: true },
info: { value: 32516, enumerable: true },
winLogo: { value: 32517, enumerable: true },
shield: { value: 32518, enumerable: true },
}),
},
load: {
enumerable: true,
value: function Icon_load(pathOrId, size) {
switch (typeof pathOrId) {
default:
throw new Error("'pathOrId' should be either a file path or a property of Icon.ids.");
case "number":
return Icon.loadBuiltin(pathOrId, size);
case "string":
return Icon.loadFile(pathOrId, size);
}
},
},
});
function createMenuTemplate(items) {
// Generates a MENUEX binary resource structure to be
// loaded by LoadMenuIndirectW().
// Docs are pretty garbarge here, I found the wine resource
// compiler source helpful for some of the edge cases.
// https://github.com/wine-mirror/wine/blob/master/tools/wrc/genres.c
// struct MENUEX_TEMPLATE_HEADER {
// 0 uint16 version = 1;
// 2 uint16 offset = 4;
// 4 uint32 helpId = 0;
// };
const header = Buffer.alloc(8);
header.writeUInt16LE(1, 0); // version
header.writeUInt16LE(4, 2); // offset
const buffers = [header];
// Wrap everything in a menu item so the contents are a
// valid popup menu, otherwise it doesn't display right.
// No idea why it matters.
addItem({ text: "root", items }, true);
return Buffer.concat(buffers);
function addList(items) {
if (items.length < 1) {
addItem({ text: "Empty", disabled: true }, true);
return;
}
const startItems = items.slice();
const lastItem = startItems.pop();
for (const item of startItems) {
addItem(item);
}
addItem(lastItem, true);
}
function addItem({
id = 0,
text = "",
separator = false,
disabled = false,
checked = false,
items,
}, isLast = false) {
// A variable-length structure, that must be aligned to 4-bytes.
// struct MENUEX_TEMPLATE_ITEM {
// 0 uint32 type;
// 4 uint32 state;
// 8 uint32 id;
// 12 uint16 flags;
// 14 utf16[...] text; // '\0' terminated
// ?? padding to 4-byte boundary
// if (items) {
// ?? uint32 helpid;
// ?? MENUEX_TEMPLATE_ITEM[...] items;
// }
// }
let size = 14 + text.length * 2 + 2;
if (items) {
size += 4;
}
if (size % 4) size += 4 - (size % 4);
let type = 0;
if (separator) {
type |= 0x800;
}
let state = 0;
if (disabled) {
state |= 3;
}
if (checked) {
state |= 8;
}
let flags = 0;
if (isLast) {
flags |= 0x80;
}
if (items) {
flags |= 0x01;
}
const buffer = Buffer.alloc(size);
buffer.writeUInt32LE(type, 0);
buffer.writeUInt32LE(state, 4);
buffer.writeUInt32LE(id, 8);
buffer.writeUInt16LE(flags, 12);
buffer.write(text, 14, 'utf16le');
buffers.push(buffer);
if (items) {
addList(items);
}
}
}