Tray icons
ArcOS has a system tray which is in many ways comparable to Windows. Any given app can add an icon to this tray by sending it to the trayHost
, which is a built-in Shell Component responsible for managing tray icons; everything from creating, deleting and rendering is handled there.
The Tray Process
ArcOS has a process type built-in called the TrayIconProcess
, which inherits from the main Process
class. If your tray icon has a popup, this class will render it. It's got a bunch of properties, as well as a couple functions that make the tray icon work.
Creating a TrayIconProcess
TrayIconProcess
Helper processes might be implemented into the CLI in the future to automatically create tray icon processes.
First of all, I'll create a file called tray.js
in which I'll create a class that inherits off of the TrayIconProcess class, and super its parent by adding a constructor, like so:
class TrayIcon extends TrayIconProcess {
constructor(handler, pid, parentPid, data) {
super(handler, pid, parentPid, data);
}
}
Next, I'll add a renderPopup
function to this process, which gives the body of the popup as well as the parent process:
class TrayIcon extends TrayIconProcess {
constructor(handler, pid, parentPid, data) {
super(handler, pid, parentPid, data);
}
async renderPopup(body, target) {
// Do something cool here
}
}
Finally, I'll add a return statement at the bottom of the file to "export" the newly created tray icon process:
// . . .
async renderPopup(body, target) {
// Do something cool here
}
}
return { TrayIcon };
Making the actual tray icon
Now that you have the tray icon process, import it in the file where your main app process is located:
const { TrayIcon } = await load("tray.js");
Then, in the render
or start
methods, create the tray icon by accessing the shell:
// Just getting a PNG file to use as the actual image
const icon = await this.fs.direct(util.join(workingDirectory, "tray.png"));
this.shell.trayHost.createTrayIcon(this.pid, "icon_id_goes_here", {
icon,
popup: { // Popup dimensions are fixed and cannot be scaled dynamically.
width: 320,
height: 240
}
}, TrayIcon);
popup
is optional: omit it to not have a tray popup at all. Additionally, if you don't want a popup, you can safely omit the renderPopup
method in the tray icon process. However, if you don't have a popup, it is recommended to add an action.
Tray icon action
A tray icon action is the action that is performed when the icon is clicked. This can either be to toggle something or to focus a minimized window, you can see it as a basic javascript event listener. The action is a callback with one argument: the process from which the tray icon was created, being your app's main process. Here's an example:
// Just getting a PNG file to use as the actual image
const icon = await this.fs.direct(util.join(workingDirectory, "tray.png"));
this.shell.trayHost.createTrayIcon(this.pid, "TrayIcon", {
icon,
action: (proc) => {
proc.handler.renderer?.focusPid(proc.pid); // This focuses a window by its pid
}
}, GooseTray);
Removing tray icons
A tray icon is set to destroy itself if the parent process is terminated. That said, you can also manually remove a tray icon, or even all tray icons of a process.
If you want to:
Remove all tray icons of the process:
this.shell.trayHost.disposeProcessTrayIcons(this.pid);
Remove just a single tray icon:
this.shell.trayHost.disposeTrayIcon(this.pid, "TrayIcon");
Changing a tray icon's image dynamically
This functionality is quite unrefined and may cause problems with the tray host if incorrectly applied. Read the instructions carefully before proceeding.
What we're going to do can be represented nicely in a quaint list of tasks:
Get the tray host's internal store
Change the
icon
property of the relevant iconWrite the changes to the internal store
Really, this can all be unified into one function call, being a Svelte Store update
call. Before we do that though, I must first explain how tray icons are stored internally: they're saved as a sort of discriminator: the invocating process PID followed by the ID of the tray icon. So, if your main process on pid 19
created a tray icon called MineSweeperTray
, it'll be stored as 19#MineSweeperTray
. Alright, here's how to do it:
const id = "MineSweeperTray"; // Store this programmatically if possible
const discriminator = `${this.pid}#${id}`;
const newPath = await this.fs.direct(util.join(workingDirectory, "tray.png"));
this.shell.trayHost.trayIcons.update((v) => {
if (!v[discriminator]) return v; // In case you made a typo.
v[discriminator].icon = newPath;
return v;
});
Because of the magical nature of Svelte, the changes made to this store should immediately be reflected in the system tray.
Styling a tray popup
Just like your app's window, you can simply just use the ID of your tray icon as the selectors for your CSS, as long as the ID of your tray icon is original enough not to interfere with anything else. If your tray popup is called MineSweeperTray
, you can select it and anything inside of it as such:
#MineSweeperTray {
/* Do stuff here */
}
Ofcourse you're welcome to seperate this tray popup CSS from your main CSS file by creating a second one and adding it to your HTML using a <link>
.
Last updated