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

must create tray icon in main thread on macOS #10

Open
ReenigneArcher opened this issue Mar 14, 2023 · 3 comments
Open

must create tray icon in main thread on macOS #10

ReenigneArcher opened this issue Mar 14, 2023 · 3 comments

Comments

@ReenigneArcher
Copy link

It appears that macOS requires the tray icon to be created/updated from the main thread of an application. I've seen something similar in this python library (https://pystray.readthedocs.io/en/latest/reference.html?highlight=macos#pystray.Icon.run_detached). I have tried the following, but not having any luck. This code runs, but no icon is created on macOS.

#include <thread>

// tray icon macros and includes
#if defined SYSTEM_TRAY && SYSTEM_TRAY >= 1
#if defined(_WIN32) || defined(_WIN64)
#define TRAY_ICON WEB_DIR "images/favicon.ico"
#elif defined(__linux__) || defined(linux) || defined(__linux)
#define TRAY_ICON "tray-icon"
#elif defined(__APPLE__) || defined(__MACH__)
#define TRAY_ICON WEB_DIR "images/tray-icon-16.png"
#include <dispatch/dispatch.h>
#endif
#include "tray/tray.h"
#endif

#if defined SYSTEM_TRAY && SYSTEM_TRAY >= 1
void tray_cb(struct tray_menu *item) {
  // code
}

// Tray menu
static struct tray tray = {
  .icon = TRAY_ICON,
#if defined(_WIN32) || defined(_WIN64)
  .tooltip = const_cast<char *>("Tooltip"), // cast the string literal to a non-const char* pointer
#endif
  .menu =
    (struct tray_menu[]) {
      { .text = "Open", .cb = tray_cb },
      { .text = nullptr } },
};

// function to create system tray, run in detached thread
int create_tray() {
  if(tray_init(&tray) < 0) {
    // logging
    return 1;
  }
  else {
    // logging
  }

  while(tray_loop(1) == 0) {
    // logging
  }

  return 0;
}
#endif

int main(int argc, char *argv[]) {
  // some code

#if defined SYSTEM_TRAY && SYSTEM_TRAY >= 1
  // create tray thread and detach it
#if defined(__APPLE__) || defined(__MACH__)
  // on macOS we need to create the tray on the main thread
  dispatch_async(dispatch_get_main_queue(), ^{
    create_tray();
  });
#else
  std::thread tray_thread(create_tray);
  tray_thread.detach();
#endif
#endif

  // more code

  // stop system tray
#if defined SYSTEM_TRAY && SYSTEM_TRAY >= 1
  tray_exit();
#if !defined(__APPLE__) || !defined(__MACH__)
  if(tray_thread.joinable()) {
    tray_thread.join();
  }
#endif
#endif

  // even more code

  return 0;
}

The most relevant part being

#if defined SYSTEM_TRAY && SYSTEM_TRAY >= 1
  // create tray thread and detach it
#if defined(__APPLE__) || defined(__MACH__)
  // on macOS we need to create the tray on the main thread
  dispatch_async(dispatch_get_main_queue(), ^{
    create_tray();
  });
#else
  std::thread tray_thread(create_tray);
  tray_thread.detach();
#endif
#endif

If I use create_tray(); directly, instead of within the dispatch_async the icon is created, but of course the remainder of the code does not execute due to the endless while loop.

Using the separate tray_thread is working great on Windows and Linux.

I'd be happy to provide more of the code if it will help.

Could you offer any workaround or guidance?

@dmikushin
Copy link
Owner

Hi @ReenigneArcher , this is interesting. I've only tested MacOS Catalina myself. Which version of MacOS fails for you?

I've reviewed your patch to LizardByte, seems like you ended up with compiling tray by your own CMake scripts with some extra C++ harness. Could you please create a PR for us with your changeset? It would be great to see what features you miss and how we could integrate them.

@ReenigneArcher
Copy link
Author

@dmikushin I was likely testing builds in macOS 13, but I think it's been the same behavior in macOS for a while.

I ended up disabling the tray icon for macOS as I couldn't figure out how to get it to run without blocking the main thread, so unfortunately I have no solution to provide.

I am not very experienced with C++. There were some issues with my initial PR that were fixed with the following PRs.

LizardByte/Sunshine#1187 (Fixes issues when running as a service in Windows... cgutman may have already submitted the relevant changes to your repo... I see you merged one of his PRs)

LizardByte/Sunshine#1208 (use cmake install prefix for icon instead of /usr)

@dmikushin
Copy link
Owner

dmikushin commented May 25, 2023

Hi @ReenigneArcher ,

Hold on, I think I start to understand the source of your confusion. So, when you approach this code:

  while (tray_loop(1) == 0) {
    printf("iteration\n");
  }

, do you imply that it is going to block the main thread in the real usecase?

You are right, it will indeed block in this simple example. Out of this you conclude that it should be owned by a dedicated thread. No: this code is not meant to be copy-pasted "as is" into the real application. When we work with GUI, the main thread somehow maintains an event loop. Every GUI flavor does it differently: Win32, Qt, gtk, Cocoa - there is no common code that could be written for all of them. But what remains common is they all have some form of event loop. So I think your goal is actually to plant the tray_loop(1) call somewhere into the actual event loop that you use, where it's appropriate. Does this make sense?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants