Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a tray icon to the application #1676

Merged
merged 1 commit into from
Nov 27, 2017

Conversation

m-pilia
Copy link
Contributor

@m-pilia m-pilia commented Nov 2, 2017

This commit adds a tray icon to the application, shown in the system
tray bar, that can be used to minimise the application window. This
is a common feature on most desktop messaging apps (e.g. Telegram
Desktop or Slack) and allows to save space in the system task bar.

The tray icon provides a context menu that contains a button to
show/hide the application window, and a button to quit the
application. When the tray icon is clicked, the visibility of the
window is toggled. When the close (x) button of the window is
pressed, the application is not terminated but minimised to the tray
icon instead (it can be terminated by using the "Quit" entry in the
File menu or in the context menu of the tray icon).

The tray icon is disabled by default, and two command line arguments
are available to enable it:

  • --use-tray-icon: enables the tray icon
  • --start-in-tray: enables the tray icon and the application starts minimised in the tray bar

Resolves: #1480

First time contributor checklist

Contributor checklist

  • My contribution is fully baked and ready to be merged as is
  • My changes are rebased on the latest master branch
  • My commits are in nice logical chunks
  • I have followed the best practices in my commit messages
  • I have tested my contribution on these platforms:
  • Arch Linux, Linux 4.13.9, Chromium 62.0.3202.75
  • My changes pass all the local tests 100% (actually a test fails, but it was also failing before my commit)
  • I have considered whether my changes need additional tests, and in the case they do, I have written them

Description

@m-pilia m-pilia force-pushed the tray-icon branch 2 times, most recently from 9b07f0b to 518e270 Compare November 2, 2017 13:00
Copy link

@breznak breznak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Was waiting for this feature, thank you 👏

main.js Outdated
{id: 'quit', label: locale.messages.quit.message, click: () => {app.quit();}}
]);

// NOTE: a function equivalent to the following will become part of the
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe open a "remind this" Issue here to remove it when electron time comes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, an issue to track this, linked in the code, would be great. That issue can link to the places we can go to see if it's available yet.

@adisib
Copy link

adisib commented Nov 2, 2017

So this still needs to be hooked up to settings?

@m-pilia
Copy link
Contributor Author

m-pilia commented Nov 2, 2017

Yes, I thought it was safer to have it tested by different devs on different platforms first, if it works and there are no other problems, adding a couple switches to the settings later should be straightforward (and maybe add other features, like a different icon when there are unread messages).

I think keeping a command line switch may be useful, it could be used to launch the app with a specific behaviour regardless of the settings (e.g. when launching the program from some autostart script). I would find it useful, but I guess this may be a matter of taste.

Copy link
Contributor

@scottnonnenberg scottnonnenberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite a lot of work you put into this, thanks! And I really appreciate the test strategy you've come up with here, enabling it for advance testing first.

I've left a number of comments and requests for changes. But a higher level thing to consider is how much of his can be extracted into its own file under /app. main.js is getting really big at this point. The way our top-level menus are generated might be a good example.

main.js Outdated
@@ -167,7 +176,7 @@ function createWindow () {

// Emitted when the window is about to be closed.
mainWindow.on('close', function (e) {
if (process.platform === 'darwin' && !windowState.shouldQuit() && config.environment !== 'test') {
if ((usingTrayIcon || process.platform === 'darwin') && !windowState.shouldQuit() && config.environment !== 'test') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably break this up into explanatory variables, now that it's gotten so long. Can you do that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I broke it in two meaningful if statements, hope it looks better now.

main.js Outdated
function createTrayIcon() {
logger.info('Creating tray icon');

// TODO: better to not use a hardcoded icon file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would you recommend here instead of a hardcoded path?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I should have detailed this better. Having multiple icon styles (light, dark, colour) for the tray and allowing the user to choose in the options would be great for theme integration. But probably this is not the most urgent issue.

main.js Outdated

trayContextMenu = Menu.buildFromTemplate([
{id: 'show', label: locale.messages.show.message, visible: !mainWindow.isVisible(), click: () => {showWindow();}},
{id: 'hide', label: locale.messages.hide.message, visible: mainWindow.isVisible(), click: () => {hideWindow();}},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm seeing a lot of () => { something(); } syntax in this PR, but that can be simplfied to something, no surrounding function required.

And in cases where it's something like app.quit() inside that function, we can change that to app.quit.bind(app). That's a common pattern in this codebase.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was really silly of me, I moved the body of the lambdas to named functions, but I left the lambdas there...

main.js Outdated
{id: 'quit', label: locale.messages.quit.message, click: () => {app.quit();}}
]);

// NOTE: a function equivalent to the following will become part of the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, an issue to track this, linked in the code, would be great. That issue can link to the places we can go to see if it's available yet.

main.js Outdated
// https://github.com/electron/electron/releases/tag/v1.8.1
// https://github.com/electron/electron/pull/10618
trayContextMenu.getMenuItemById = function (id) {
return this.items.find(item => item.id === id) || null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The || null here seems unecessary. Below, where this is used, there's no check before setting its visible property, so it will crash. Maybe the right thing to do here is to throw if we can't find any menu item with the provided id?

Copy link
Contributor Author

@m-pilia m-pilia Nov 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think throwing an exception there would be the cleanest way. But the method implemented in electron does not do that, it just returns null, so if we want to be able to replace this function with it we may need to do null checks (that I added where needed, thank you for pointing it out).
Otherwise we can do differently and not rely on the (future) electron's API for it (after all, this is a very short piece of code and it is called just twice).
This is the method in electron 1.8.1: https://github.com/electron/electron/blob/ba754cf5c3f36d66a3b23f409613a348bfa6b308/lib/browser/api/menu.js#L84

main.js Outdated

tray.setContextMenu(trayContextMenu);

tray.on('clicked', () => {toggleWindowVisibility();});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between the 'clicked' and 'click' events?

Also, another situation where we can dispense with the surrounding function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops. 'click' is the event. 'clicked' was an experiment I made, and somehow I missed to remove that line later.

main.js Outdated
tray.on('clicked', () => {toggleWindowVisibility();});
tray.on('click', () => {toggleWindowVisibility();});

tray.setToolTip('Signal Desktop');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be localized. Let's pull this string from locale.

main.js Outdated
if (mainWindow) {
if (mainWindow.isVisible()) {
hideWindow();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A minor issue, but we generally use } else { brace style in this project.

main.js Outdated
function showWindow() {
if (mainWindow) {
mainWindow.show();

// trick to bring above other windows and get focus
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tell me more about this trick. What platforms has it been tested on (we've had particular difficulty with window placement/focus on windows) and where did it come from?

Also, note the 'show-window' IPC handler just a few lines above this. The right thing here might be to extract that out into a focusWindow method which shows the window if it isn't already showing. Then the tray handlers can use that instead of the simple showWindow method.

Copy link
Contributor Author

@m-pilia m-pilia Nov 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved this in a specific method for the tray.
Sorry, I should definitely have given more detail here. I was aware that on some versions of GNOME there were problems when using this API, and the window may not be restored on top if it was not focused when it was hidden (cfr. also this issue on an app that uses the same API: https://github.com/Enrico204/Whatsapp-Desktop/issues/29 ), so I included it to be on the safe side (tested on Linux, KDE Plasma 5.11 and GNOME 3.26), even if I suspect it may be unnecessary.

@m-pilia
Copy link
Contributor Author

m-pilia commented Nov 12, 2017

Thanks @scottnonnenberg for the review. I am sorry for so much sloppiness in my code, I had to implement this in very little time and probably I should not have submitted it so early.

I moved the tray related code in a new module in the app folder and followed your suggestions for the other issues. I am still not satisfied with the setTrayContextMenuItemsVisibility method, but I saw no other cleaner solution to update the visibility of the show/hide items in the context menu of the tray icon (apart from less elegant solutions, like always having both the elements visible, or having a single "show/hide" element).

@scottnonnenberg scottnonnenberg changed the base branch from master to development November 14, 2017 22:06
@scottnonnenberg
Copy link
Contributor

Note: I just moved this to target the development branch, because this will need to go out in a Beta build before hitting production. It looks like one merge conflict popped up.

Copy link
Contributor

@scottnonnenberg scottnonnenberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes. We're almost there!

app/tray_icon.js Outdated

function createTrayIcon(mainWindow, messages) {

// TODO: for better desktop theme integration, it would be good to provide
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to put a TODO in the code, please create an issue and link to it directly in the code next to the TODO.

main.js Outdated
@@ -273,6 +298,11 @@ app.on('ready', function() {

createWindow();

if (usingTrayIcon) {
const createTrayIcon = require("./app/tray_icon");
tray = createTrayIcon(mainWindow, locale.messages);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like how everything has been pulled into a different file! However, there's one thing to consider: there can be multiple values of mainWindow over the lifetime of the application. I think, instead of passing mainWindow directly here, a function getMainWindow() should be passed instead.

app/tray_icon.js Outdated

// TODO: for better desktop theme integration, it would be good to provide
// multiple tray icons (light, dark, colour) and allow selection from the options
tray = new Tray(path.join(__dirname, '../images/icon_48.png'));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about path.join(__dirname, '..', 'images', 'icon_48.png') instead? Keep those pesky linux-y / characters out of a cross-platform codebase? :0)

@m-pilia
Copy link
Contributor Author

m-pilia commented Nov 19, 2017

I apologise for being so slow to reply, unfortunately I can work on this only during the weekend.
I followed your suggestions, removing *nix file path delimiters (sorry for my bias on this...) and fixing the problem of the changing mainWindow object.
I also tried to make the code for the tray context menu simpler and more clear. However I put that in a separate commit, so if it does not work (or you do not like it) it is easy to restore the previous approach.

@scottnonnenberg
Copy link
Contributor

@m-pilia We're looking good on these changes (nice simplification!), but it looks like there's still a merge conflict to deal with. I think we're ready to merge as soon as that is addressed.

@m-pilia
Copy link
Contributor Author

m-pilia commented Nov 21, 2017

Right, I didn't look at it. Now it should be ok I think!

@scottnonnenberg
Copy link
Contributor

@m-pilia That is one huge commit, that merge from the development branch. Are you familiar with the rebase command in Git? Please rebase on development branch and take this PR down to one or two clean commits.

@m-pilia m-pilia force-pushed the tray-icon branch 2 times, most recently from bf9708b to 9d7cab1 Compare November 21, 2017 16:55
@m-pilia
Copy link
Contributor Author

m-pilia commented Nov 21, 2017

@scottnonnenberg sorry, this was embarassing... I'm familiar with rebase, but apparently I'm definitely not familiar with GitHub. I tried to use the web interface to solve the conflict, but I didn't realise it was also going to merge the entire development branch in my feature branch...

@scottnonnenberg
Copy link
Contributor

Okay, I just tested this - looking good! Fix that merge conflict and I'll merge this PR. If we can get this done in the next couple hours, it will go out in the next beta build. :0)

This commit adds a tray icon to the application, shown in the system
tray bar, that can be used to minimise the application window.  This
is a common feature on most desktop messaging apps (e.g. Telegram
Desktop or Slack) and allows to save space in the system task bar.

The tray icon provides a context menu that contains a button to
show/hide the application window, and a button to quit the
application. When the tray icon is clicked, the visibility of the
window is toggled.  When the close (x) button of the window is
pressed, the application is not terminated but minimised to the tray
icon instead (it can be terminated by using the "Quit" entry in the
File menu or in the context menu of the tray icon).

The tray icon is disabled by default, and two command line arguments
are available to enable it:
  --use-tray-icon: enables the tray icon
  --start-in-tray: enables the tray icon and the application starts
                   minimised in the tray bar

Resolves: signalapp#1480
@m-pilia
Copy link
Contributor Author

m-pilia commented Nov 23, 2017

Sorry, I could not do it in time, apparently we are in fairly different time zones. I do not know exactly where this conflict came from, so I reset, synced my branch with upstream and then re-applied my changes, now hopefully it should be clean. Thank you for your patience, I have done a mess with git!
screenshot_20171122_125128

@peterthomassen
Copy link

Thanks so much!! <3

scottnonnenberg added a commit that referenced this pull request Dec 5, 2017
Add proxy support based on environment variables (#1855)

Fix issue where window would not show new message alerts on some Linux
systesm - thanks @cornerman (#1820)

Add a tray icon to the application behind command-line argument -
thanks @m-pilia (#1676)

Emoji:
  - Fix issue where clicks in emoji panel wouldn't do anything (#1849)
  - Add support for Emoji 5 - thanks @liliakai (#1797)
  - Eliminate unused emoji images from production package (#1849)

Deployment:
  - aptly.sh: support for current and previous ubuntu versions (#1856)
  - deployment: update electron-publisher-s3 to match builder update
  (17f0bb4)

Dev:
  - Update development branch to include everything up to v1.0.40
  (f013eed
  and 3ac29a4)
  - Update readme.md to use signal.org URLs - thanks @scienmind (#1814)
@morrow95
Copy link

morrow95 commented Dec 8, 2017

Any timeframe on when the next build will be released? As in not one that needs compiled manually.

@scottnonnenberg
Copy link
Contributor

The tray is in current beta builds. You can install the beta like this:

  • Mac: install file in 'path' at https://updates.signal.org/desktop/beta-mac.yml
  • Windows: install file in 'path' at https://updates.signal.org/desktop/beta.yml
  • Debian-like linux: same setup, then apt-get install signal-desktop-beta

@GuardianMajor
Copy link

Some feedback on this, since I just got a chance to test it.

1. The start in tray works great, the icon shows there just fine.

  • However, it doesn't actually load the app fully and sync messages until you click on it and it opens.

2. The minimize still goes to taskbar but closing it will keep the icon in the tray, great, I can live with that to some extent.

  • But if we can either remove that minimize menu option, or make it tied to a setting that -> if min to tray = true then minimize sends it back to tray and = false then minimize to taskbar like now, whichever. Current implementation seems a bit disconnect in behavior.

3. Minimizing to tray feature is great but current implementation loses great usefulness

  • In that when you get a new message, there is no notification or anything to let you know, this makes it quite counterproductive since you have no idea unless you keep randomly checking which is greatly reducing the productivity and usefulness of this. A desktop alert/toast would be useful or an option that once a message comes in, open it up from the tray (less desirable as it would be disruptive if you are working on something and suddenly the window pops up), whichever would be greatly needed here.

again, thank you for doing this, it is a great start and hope we can improve on it.

@scottnonnenberg
Copy link
Contributor

scottnonnenberg commented Dec 8, 2017

@GuardianMajor I think it's time to take some of this behavior to other issues. This pull request is long-dead. In particular, your item 3 is definitely a bug. Notifications are supposed to happen when the window is minimized. We'll need more information: platform details, log, etc. as usual.

I'm locking this discussion.

@signalapp signalapp locked and limited conversation to collaborators Dec 8, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

Successfully merging this pull request may close these issues.

[Electron] Minimize/Close to tray
7 participants