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

Tray fixes #18

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
81db3db
Merge pull request #1 from TheElixZammuto/fix-mingw
TheElixZammuto Dec 31, 2022
06b84cb
Merge branch 'dmikushin:master' into master
TheElixZammuto Jul 22, 2023
0d14da2
Started Windows Support for Balloon Messages
TheElixZammuto Jul 22, 2023
0050b14
Merge branch 'notifications' of github.com:TheElixZammuto/tray into HEAD
TheElixZammuto Jul 22, 2023
d791556
Merge branch 'master' of github.com:TheElixZammuto/tray into balloons
TheElixZammuto Jul 22, 2023
f8179dd
Linux Notification support
TheElixZammuto Jul 23, 2023
d6f00f4
Balloon Callback
TheElixZammuto Jul 23, 2023
6c8600a
Windows fixes
TheElixZammuto Aug 13, 2023
53db133
Balloon Callback on Linux
TheElixZammuto Aug 13, 2023
2594de9
Update CMakeLists.txt
TheElixZammuto Aug 19, 2023
aa7ef42
Merge pull request #1 from TheElixZammuto/balloons
TheElixZammuto Aug 19, 2023
a914b85
Fix icon flag update
TheElixZammuto Aug 20, 2023
945b7fa
Allow balloons to be dismissed automatically
TheElixZammuto Aug 20, 2023
8d4192d
(windows) check notification area availablity before balloon
TheElixZammuto Aug 21, 2023
2921d96
Merge pull request #2 from LizardByte/balloons
TheElixZammuto Sep 2, 2023
6bb242d
Fix for legacy appindicator
TheElixZammuto Sep 17, 2023
f42c655
Merge branch 'origin-fix-legacy-appindicator' into fix-legacy-appindi…
ReenigneArcher Sep 18, 2023
6e3d690
Merge pull request #3 from LizardByte/fix-legacy-appindicator
ReenigneArcher Sep 18, 2023
8da44ff
Fix fedora compile
TheElixZammuto Sep 18, 2023
0b1aa15
Merge pull request #4 from LizardByte/origin-fix-legacy-appindicator
TheElixZammuto Sep 18, 2023
2664388
Use higher res image for baloon icon (#5)
ns6089 Oct 12, 2023
b7a02ef
(fix,linux) Check if the appindicator does actually work before updat…
TheElixZammuto Oct 29, 2023
5da8e87
Fix Ayatana Build
TheElixZammuto Oct 30, 2023
e28e3e3
Merge pull request #6 from LizardByte/fix/ayatana-build
TheElixZammuto Oct 30, 2023
e97c139
Use ifndef instead of relying of the defined header
TheElixZammuto Oct 30, 2023
e08bdbe
Merge pull request #7 from LizardByte/fix/ayatana-build
TheElixZammuto Oct 30, 2023
8bb9978
Constify tooltip string pointer (#8)
cgutman Dec 31, 2023
2bf1c61
Perform all GTK interactions on the tray loop thread (#9)
cgutman Jan 11, 2024
d649813
Allow tray icons to be precached.
brycerocky Feb 4, 2024
a08c102
Merge pull request #10 from brycerocky/icon_cache
TheElixZammuto Mar 10, 2024
32ca496
Avoid possible UAF if an icon path is dynamically allocated memory
cgutman Mar 14, 2024
8d94937
Fix missing small icon on some systems
cgutman Mar 14, 2024
585d4f0
Avoid infinite recursion if icon load fails
cgutman Mar 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ else()
list(APPEND SRCS ${CMAKE_CURRENT_SOURCE_DIR}/tray_darwin.m)
else()
find_package(APPINDICATOR REQUIRED)
find_package(LIBNOTIFY REQUIRED)
list(APPEND SRCS ${CMAKE_CURRENT_SOURCE_DIR}/tray_linux.c)
endif()
endif()
Expand All @@ -48,7 +49,14 @@ else()
target_compile_options(tray PRIVATE ${APPINDICATOR_CFLAGS})
target_link_directories(tray PRIVATE ${APPINDICATOR_LIBRARY_DIRS})
target_compile_definitions(tray PRIVATE TRAY_APPINDICATOR=1)
target_link_libraries(tray PRIVATE ${APPINDICATOR_LIBRARIES})
if(APPINDICATOR_AYATANA)
target_compile_definitions(tray PRIVATE TRAY_AYATANA_APPINDICATOR=1)
endif()
if(APPINDICATOR_LEGACY)
target_compile_definitions(tray PRIVATE TRAY_LEGACY_APPINDICATOR=1)
endif()
target_compile_definitions(tray PRIVATE TRAY_LIBNOTIFY=1)
target_link_libraries(tray PRIVATE ${APPINDICATOR_LIBRARIES} ${LIBNOTIFY_LIBRARIES})
endif()
endif()
endif()
Expand Down
7 changes: 6 additions & 1 deletion cmake/FindAPPINDICATOR.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ include(FindPackageHandleStandardArgs)

PKG_CHECK_MODULES(APPINDICATOR ayatana-appindicator3-0.1)
IF( APPINDICATOR_FOUND )
SET(HAVE_AYATANAAPPINDICATOR 1)
SET(APPINDICATOR_AYATANA 1)
ELSE()
PKG_CHECK_MODULES(APPINDICATOR appindicator3-0.1)
IF( APPINDICATOR_FOUND )
SET(APPINDICATOR_LEGACY 1)
ENDIF()
ENDIF()

mark_as_advanced(APPINDICATOR_INCLUDE_DIR APPINDICATOR_LIBRARY)
55 changes: 55 additions & 0 deletions cmake/FindLIBNOTIFY.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# - Try to find LibNotify
# This module defines the following variables:
#
# LIBNOTIFY_FOUND - LibNotify was found
# LIBNOTIFY_INCLUDE_DIRS - the LibNotify include directories
# LIBNOTIFY_LIBRARIES - link these to use LibNotify
#
# Copyright (C) 2012 Raphael Kubo da Costa <[email protected]>
# Copyright (C) 2014 Collabora Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS
# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

find_package(PkgConfig)
pkg_check_modules(LIBNOTIFY QUIET libnotify)

find_path(LIBNOTIFY_INCLUDE_DIRS
NAMES notify.h
HINTS ${LIBNOTIFY_INCLUDEDIR}
${LIBNOTIFY_INCLUDE_DIRS}
PATH_SUFFIXES libnotify
)

find_library(LIBNOTIFY_LIBRARIES
NAMES notify
HINTS ${LIBNOTIFY_LIBDIR}
${LIBNOTIFY_LIBRARY_DIRS}
)

include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibNotify REQUIRED_VARS LIBNOTIFY_INCLUDE_DIRS LIBNOTIFY_LIBRARIES
VERSION_VAR LIBNOTIFY_VERSION)

mark_as_advanced(
LIBNOTIFY_INCLUDE_DIRS
LIBNOTIFY_LIBRARIES
)
8 changes: 7 additions & 1 deletion tray.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@ struct tray_menu;

struct tray {
const char *icon;
char *tooltip;
const char *tooltip;
const char *notification_icon;
const char *notification_text;
const char *notification_title;
void (*notification_cb)();
struct tray_menu *menu;
const int iconPathCount;
const char *allIconPaths[];
};

struct tray_menu {
Expand Down
127 changes: 110 additions & 17 deletions tray_linux.c
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
#include "tray.h"
#include <string.h>
#include <stddef.h>
#include <stdbool.h>
#ifdef TRAY_AYATANA_APPINDICATOR
#include <libayatana-appindicator/app-indicator.h>
#elif TRAY_LEGACY_APPINDICATOR
#include <libappindicator/app-indicator.h>
#endif
#ifndef IS_APP_INDICATOR
#define IS_APP_INDICATOR APP_IS_INDICATOR
#endif

#include <libnotify/notify.h>
#define TRAY_APPINDICATOR_ID "tray-id"

static AppIndicator *indicator = NULL;
static int loop_result = 0;
static bool async_update_pending = false;
static pthread_cond_t async_update_cv = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t async_update_mutex = PTHREAD_MUTEX_INITIALIZER;

static AppIndicator *indicator = NULL;
static int loop_result = 0;
static NotifyNotification *currentNotification = NULL;
static void _tray_menu_cb(GtkMenuItem *item, gpointer data) {
(void)item;
struct tray_menu *m = (struct tray_menu *)data;
Expand All @@ -16,23 +29,26 @@ static void _tray_menu_cb(GtkMenuItem *item, gpointer data) {

static GtkMenuShell *_tray_menu(struct tray_menu *m) {
GtkMenuShell *menu = (GtkMenuShell *)gtk_menu_new();
for (; m != NULL && m->text != NULL; m++) {
for(; m != NULL && m->text != NULL; m++) {
GtkWidget *item;
if (strcmp(m->text, "-") == 0) {
if(strcmp(m->text, "-") == 0) {
item = gtk_separator_menu_item_new();
} else {
if (m->submenu != NULL) {
}
else {
if(m->submenu != NULL) {
item = gtk_menu_item_new_with_label(m->text);
gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
GTK_WIDGET(_tray_menu(m->submenu)));
} else if (m->checkbox) {
GTK_WIDGET(_tray_menu(m->submenu)));
}
else if(m->checkbox) {
item = gtk_check_menu_item_new_with_label(m->text);
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), !!m->checked);
} else {
}
else {
item = gtk_menu_item_new_with_label(m->text);
}
gtk_widget_set_sensitive(item, !m->disabled);
if (m->cb != NULL) {
if(m->cb != NULL) {
g_signal_connect(item, "activate", G_CALLBACK(_tray_menu_cb), m);
}
}
Expand All @@ -43,11 +59,13 @@ static GtkMenuShell *_tray_menu(struct tray_menu *m) {
}

int tray_init(struct tray *tray) {
if (gtk_init_check(0, NULL) == FALSE) {
if(gtk_init_check(0, NULL) == FALSE) {
return -1;
}
notify_init("tray-icon");
indicator = app_indicator_new(TRAY_APPINDICATOR_ID, tray->icon,
APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
if(indicator == NULL || !IS_APP_INDICATOR(indicator))return -1;
app_indicator_set_status(indicator, APP_INDICATOR_STATUS_ACTIVE);
tray_update(tray);
return 0;
Expand All @@ -58,12 +76,87 @@ int tray_loop(int blocking) {
return loop_result;
}

static gboolean tray_update_internal(gpointer user_data) {
struct tray *tray = user_data;

if(indicator != NULL && IS_APP_INDICATOR(indicator)){
app_indicator_set_icon(indicator, tray->icon);
// GTK is all about reference counting, so previous menu should be destroyed
// here
app_indicator_set_menu(indicator, GTK_MENU(_tray_menu(tray->menu)));
}
if(tray->notification_text != 0 && strlen(tray->notification_text) > 0 && notify_is_initted()) {
if(currentNotification != NULL && NOTIFY_IS_NOTIFICATION(currentNotification)){
notify_notification_close(currentNotification,NULL);
g_object_unref(G_OBJECT(currentNotification));
}
const char *notification_icon = tray->notification_icon != NULL ? tray->notification_icon : tray->icon;
currentNotification = notify_notification_new(tray->notification_title, tray->notification_text, notification_icon);
if(currentNotification != NULL && NOTIFY_IS_NOTIFICATION(currentNotification)){
if(tray->notification_cb != NULL){
notify_notification_add_action(currentNotification,"default","Default",tray->notification_cb,NULL,NULL);
}
notify_notification_show(currentNotification, NULL);
}
}

// Unwait any pending tray_update() calls
pthread_mutex_lock(&async_update_mutex);
async_update_pending = false;
pthread_cond_broadcast(&async_update_cv);
pthread_mutex_unlock(&async_update_mutex);
return G_SOURCE_REMOVE;
}

void tray_update(struct tray *tray) {
app_indicator_set_icon(indicator, tray->icon);
// GTK is all about reference counting, so previous menu should be destroyed
// here
app_indicator_set_menu(indicator, GTK_MENU(_tray_menu(tray->menu)));
// Perform the tray update on the tray loop thread, but block
// in this thread to ensure none of the strings stored in the
// tray icon struct go out of scope before the callback runs.

if (g_main_context_is_owner(g_main_context_default())) {
// Invoke the callback directly if we're on the loop thread
tray_update_internal(tray);
}
else {
// If there's already an update pending, wait for it to complete
// and claim the next pending update slot.
pthread_mutex_lock(&async_update_mutex);
while (async_update_pending) {
pthread_cond_wait(&async_update_cv, &async_update_mutex);
}
async_update_pending = true;
pthread_mutex_unlock(&async_update_mutex);

// Queue the update callback to the tray thread
g_main_context_invoke(NULL, tray_update_internal, tray);

// Wait for the callback to run
pthread_mutex_lock(&async_update_mutex);
while (async_update_pending) {
pthread_cond_wait(&async_update_cv, &async_update_mutex);
}
pthread_mutex_unlock(&async_update_mutex);
}
}

static gboolean tray_exit_internal(gpointer user_data) {
if(currentNotification != NULL && NOTIFY_IS_NOTIFICATION(currentNotification)){
int v = notify_notification_close(currentNotification,NULL);
if(v == TRUE)g_object_unref(G_OBJECT(currentNotification));
}
notify_uninit();
return G_SOURCE_REMOVE;
}

void tray_exit(void) { loop_result = -1; }
void tray_exit(void) {
// Wait for any pending callbacks to complete
pthread_mutex_lock(&async_update_mutex);
while (async_update_pending) {
pthread_cond_wait(&async_update_cv, &async_update_mutex);
}
pthread_mutex_unlock(&async_update_mutex);

// Perform cleanup on the main thread
loop_result = -1;
g_main_context_invoke(NULL, tray_exit_internal, NULL);
}
Loading