@@ -4,288 +4,790 @@
# include <jni.h>
# include <vector>
# include <string>
# include <mutex>
# include <atomic>
# include <algorithm>
# include <uxtheme.h>
# include <windowsx.h>
# pragma comment(lib, "uxtheme.lib")
# include "com_axis_innovators_box_tools_RegisterTray.h"
// 调试输出
# define DEBUG_LOG(msg) OutputDebugStringW(L"[Tray] " msg L"\n")
// 调试输出宏
# define DEBUG_LOG(msg) { OutputDebugStringW(L"[Tray] " msg L"\n"); }
// 全局 JVM 缓存(懒取)
static std : : atomic < JavaVM * > gJvm { nullptr } ;
struct MenuItemData {
jint menuId ;
jobject eventObjGlobal ; // global ref to the Event object for this item
jmethodID onClickMethod ;
jobject eventObj ;
int menuId ;
std : : wstring title ;
} ;
struct TrayData {
HMENU hMenu = NULL ;
std : : vector < MenuItemData > menuItems ;
jobject eventObj = NULL ;
jmethodID onClickMethod = NULL ;
HWND hwnd = NULL ;
UINT trayId = 0 ;
HICON hIcon = NULL ;
std : : vector < MenuItemData > menuItems ;
jobject eventObjGlobal = NULL ; // global ref for the primary event callback
jmethodID onClickMethod = NULL ;
std : : wstring name ;
std : : wstring iconPath ;
std : : wstring description ;
// 使用 Win32 线程句柄替代 std::thread
HANDLE threadHandle = NULL ;
DWORD threadId = 0 ;
std : : mutex mutex ;
std : : atomic < bool > running { false } ;
} ;
std : : vector < TrayData * > t rayData List;
static std : : vector < TrayData * > gT rayList;
static std : : mutex gTrayListMutex ;
HICON LoadTrayIcon ( const wchar_t * path ) {
HICON hIcon = ( HICON ) LoadImageW (
NULL , path , IMAGE_ICON ,
GetSystemMetrics ( SM_CXSMICON ) ,
GetSystemMetrics ( SM_CYSMICON ) ,
LR_LOADFROMFILE | LR_SHARED
) ;
return hIcon ? hIcon : LoadIconW ( NULL , IDI_APPLICATION ) ;
// ---------- 主题/字体 ----------
struct ThemeColors {
COLORREF bg ;
COLORREF hover ;
COLORREF text ;
COLORREF border ;
} ;
static bool IsLightTheme ( )
{
DWORD val = 1 ; // 默认为浅色
DWORD cb = sizeof ( val ) ;
LONG r = RegGetValueW (
HKEY_CURRENT_USER ,
L " Software \\ Microsoft \\ Windows \\ CurrentVersion \\ Themes \\ Personalize " ,
L " AppsUseLightTheme " ,
RRF_RT_REG_DWORD , nullptr , & val , & cb ) ;
return ( r ! = ERROR_SUCCESS ) ? true : ( val ! = 0 ) ;
}
LRESULT CALLBACK WndProc ( HWND hwnd , UINT msg , WPARAM wParam , LPARAM lParam ) {
TrayData * pData = ( TrayData * ) GetWindowLongPtrW ( hwnd , GWLP_USERDATA ) ;
static ThemeColors GetThemeColors ( )
{
if ( IsLightTheme ( ) ) {
// 浅色主题
return ThemeColors {
RGB ( 245 , 245 , 245 ) , // 背景
RGB ( 225 , 225 , 225 ) , // 悬停
RGB ( 32 , 32 , 32 ) , // 文字
RGB ( 210 , 210 , 210 ) // 边框
} ;
}
else {
// 深色主题
return ThemeColors {
RGB ( 30 , 30 , 30 ) , // 背景
RGB ( 50 , 50 , 50 ) , // 悬停
RGB ( 230 , 230 , 230 ) , // 文字
RGB ( 60 , 60 , 60 ) // 边框
} ;
}
}
static HFONT CreatePopupUIFont ( )
{
LOGFONTW lf { } ;
// 使用系统图标标题字体作为基准
SystemParametersInfoW ( SPI_GETICONTITLELOGFONT , sizeof ( lf ) , & lf , 0 ) ;
// 替换为 Segoe UI, 更现代
wcscpy_s ( lf . lfFaceName , L " Segoe UI " ) ;
lf . lfHeight = - 12 ; // 约 9pt @96DPI
lf . lfWeight = FW_NORMAL ;
lf . lfQuality = CLEARTYPE_NATURAL_QUALITY ; // 开启更自然的 ClearType
return CreateFontIndirectW ( & lf ) ;
}
// ---------- 前向声明 ----------
LRESULT CALLBACK TrayWndProc ( HWND hwnd , UINT msg , WPARAM wParam , LPARAM lParam ) ;
LRESULT CALLBACK PopupWndProc ( HWND hwnd , UINT msg , WPARAM wParam , LPARAM lParam ) ;
DWORD WINAPI TrayMessageThreadProc ( LPVOID lpParam ) ;
void TrayMessageThread ( TrayData * td ) ;
// 注册窗口类(线程安全)
void EnsureWindowClassesRegistered ( HINSTANCE hInstance ) {
static std : : atomic_bool registered { false } ;
bool expected = false ;
if ( ! registered . compare_exchange_strong ( expected , true ) ) return ;
WNDCLASSEXW wc = { 0 } ;
wc . cbSize = sizeof ( wc ) ;
wc . lpfnWndProc = TrayWndProc ;
wc . hInstance = hInstance ;
wc . lpszClassName = L " ModernTray_TrayWindowClass " ;
wc . hCursor = LoadCursor ( NULL , IDC_ARROW ) ;
WNDCLASSEXW popup = { 0 } ;
popup . cbSize = sizeof ( popup ) ;
popup . lpfnWndProc = PopupWndProc ;
popup . hInstance = hInstance ;
popup . lpszClassName = L " ModernTray_PopupWindowClass " ;
popup . hCursor = LoadCursor ( NULL , IDC_ARROW ) ;
popup . hbrBackground = NULL ; // 自绘背景
RegisterClassExW ( & wc ) ;
RegisterClassExW ( & popup ) ;
}
// 加载图标(文件或默认)
HICON LoadTrayIconSafe ( const std : : wstring & path ) {
if ( ! path . empty ( ) ) {
HICON h = ( HICON ) LoadImageW ( NULL , path . c_str ( ) , IMAGE_ICON ,
GetSystemMetrics ( SM_CXSMICON ) , GetSystemMetrics ( SM_CYSMICON ) ,
LR_LOADFROMFILE | LR_SHARED ) ;
if ( h ) return h ;
}
return LoadIconW ( NULL , IDI_APPLICATION ) ;
}
// 查找 TrayData
TrayData * FindTrayById ( UINT id ) {
//std::lock_guard<std::mutex> lk(gTrayListMutex);
for ( auto t : gTrayList ) if ( t - > trayId = = id ) return t ;
return nullptr ;
}
// 计算窗口尺寸(基于菜单文本)
SIZE CalcPopupSize ( HDC hdc , const std : : vector < MenuItemData > & items , HFONT hFont , int paddingX = 14 , int paddingY = 10 , int itemHeight = 28 ) {
SIZE sz = { 0 , 0 } ;
int maxw = 0 ;
HFONT old = ( HFONT ) SelectObject ( hdc , hFont ) ;
for ( const auto & it : items ) {
if ( it . title . empty ( ) ) continue ;
RECT rc = { 0 , 0 , 0 , 0 } ;
DrawTextW ( hdc , it . title . c_str ( ) , - 1 , & rc , DT_SINGLELINE | DT_LEFT | DT_CALCRECT ) ;
int w = rc . right - rc . left ;
if ( w > maxw ) maxw = w ;
}
SelectObject ( hdc , old ) ;
sz . cx = maxw + paddingX * 2 ;
if ( sz . cx < 140 ) sz . cx = 140 ; // 最小宽度
sz . cy = ( int ) items . size ( ) * itemHeight + paddingY ; // 顶部/底部各留 paddingY/2 的感觉
return sz ;
}
// 绘制圆角背景与文本( WM_PAINT 在 PopupWndProc 使用)
void PaintPopup ( HWND hwnd , const std : : vector < MenuItemData > & items , int hoverIndex ) {
PAINTSTRUCT ps ;
HDC hdc = BeginPaint ( hwnd , & ps ) ;
RECT rc ;
GetClientRect ( hwnd , & rc ) ;
ThemeColors c = GetThemeColors ( ) ;
// 背景(圆角 + 边框)
HBRUSH bg = CreateSolidBrush ( c . bg ) ;
HBRUSH hover = CreateSolidBrush ( c . hover ) ;
HPEN border = CreatePen ( PS_SOLID , 1 , c . border ) ;
HRGN rgn = CreateRoundRectRgn ( rc . left , rc . top , rc . right , rc . bottom , 12 , 12 ) ;
SelectClipRgn ( hdc , rgn ) ;
HGDIOBJ oldPen = SelectObject ( hdc , border ) ;
HGDIOBJ oldBrush = SelectObject ( hdc , bg ) ;
RoundRect ( hdc , rc . left , rc . top , rc . right , rc . bottom , 12 , 12 ) ;
// 文本 & 悬停绘制
SetBkMode ( hdc , TRANSPARENT ) ;
SetTextColor ( hdc , c . text ) ;
static HFONT sFont = nullptr ;
if ( ! sFont ) sFont = CreatePopupUIFont ( ) ;
HFONT oldFont = ( HFONT ) SelectObject ( hdc , sFont ) ;
int y = 6 ;
const int itemH = 28 ;
for ( size_t i = 0 ; i < items . size ( ) ; + + i ) {
RECT itrc = { 8 , y , rc . right - 8 , y + itemH - 2 } ;
if ( ( int ) i = = hoverIndex ) {
FillRect ( hdc , & itrc , hover ) ;
}
DrawTextW ( hdc , items [ i ] . title . c_str ( ) , - 1 , & itrc , DT_VCENTER | DT_SINGLELINE | DT_LEFT | DT_NOPREFIX ) ;
y + = itemH ;
}
SelectObject ( hdc , oldFont ) ;
SelectObject ( hdc , oldBrush ) ;
SelectObject ( hdc , oldPen ) ;
DeleteObject ( bg ) ;
DeleteObject ( hover ) ;
DeleteObject ( border ) ;
DeleteObject ( rgn ) ;
EndPaint ( hwnd , & ps ) ;
}
// ---------- Popup window helpers ----------
struct PopupContext {
TrayData * tray ;
std : : vector < MenuItemData > items ;
int hoverIndex ;
POINT origin ;
} ;
// 存储 PopupContext 到窗口属性(使用 GWLP_USERDATA)
static inline PopupContext * GetPopupContext ( HWND hwnd ) {
return ( PopupContext * ) GetWindowLongPtrW ( hwnd , GWLP_USERDATA ) ;
}
// PopupWndProc 实现:自绘菜单,鼠标移动、点击处理
LRESULT CALLBACK PopupWndProc ( HWND hwnd , UINT msg , WPARAM wParam , LPARAM lParam ) {
PopupContext * ctx = GetPopupContext ( hwnd ) ;
static const UINT kGuardTimerId = 1 ; // 初始防抖计时器
static const UINT kGuardMs = 120 ; // 防抖时长
switch ( msg ) {
case WM_USER + 1 :
switch ( lParam ) {
case WM_RBUTTONUP :
if ( pData & & pData - > hMenu ) {
case WM_CREATE :
SetTimer ( hwnd , kGuardTimerId , kGuardMs , nullptr ) ;
return 0 ;
case WM_TIMER :
if ( wParam = = kGuardTimerId ) {
KillTimer ( hwnd , kGuardTimerId ) ;
return 0 ;
}
break ;
case WM_ACTIVATE :
if ( LOWORD ( wParam ) = = WA_INACTIVE ) {
// 防抖期内忽略一次失焦
if ( ! KillTimer ( hwnd , kGuardTimerId ) ) {
DestroyWindow ( hwnd ) ;
return 0 ;
}
else {
SetTimer ( hwnd , kGuardTimerId , kGuardMs , nullptr ) ;
}
}
return 0 ;
case WM_MOUSEMOVE : {
if ( ! ctx ) break ;
int my = GET_Y_LPARAM ( lParam ) ;
const int itemH = 28 ;
int count = ( int ) ctx - > items . size ( ) ;
if ( count < = 0 ) break ;
int idx = std : : max ( 0 , std : : min ( count - 1 , ( my - 6 ) / itemH ) ) ;
if ( idx ! = ctx - > hoverIndex ) {
ctx - > hoverIndex = idx ;
InvalidateRect ( hwnd , NULL , TRUE ) ;
}
return 0 ;
}
case WM_LBUTTONDOWN : {
if ( ! ctx ) break ;
int my = GET_Y_LPARAM ( lParam ) ;
const int itemH = 28 ;
int count = ( int ) ctx - > items . size ( ) ;
if ( count > 0 ) {
int idx = std : : max ( 0 , std : : min ( count - 1 , ( my - 6 ) / itemH ) ) ;
if ( idx > = 0 & & idx < count ) {
MenuItemData sel = ctx - > items [ idx ] ;
TrayData * td = ctx - > tray ;
JavaVM * jvm = gJvm . load ( ) ;
if ( ! jvm ) {
JNI_GetCreatedJavaVMs ( & jvm , 1 , NULL ) ;
gJvm . store ( jvm ) ;
}
if ( jvm & & sel . eventObjGlobal & & sel . onClickMethod ) {
JNIEnv * env = nullptr ;
if ( jvm - > AttachCurrentThread ( ( void * * ) & env , NULL ) = = JNI_OK ) {
env - > CallVoidMethod ( sel . eventObjGlobal , sel . onClickMethod , ( jlong ) td - > trayId ) ;
jvm - > DetachCurrentThread ( ) ;
}
}
}
}
DestroyWindow ( hwnd ) ;
return 0 ;
}
case WM_PAINT :
if ( ctx ) PaintPopup ( hwnd , ctx - > items , ctx - > hoverIndex ) ;
return 0 ;
case WM_NCDESTROY :
if ( ctx ) {
delete ctx ;
SetWindowLongPtrW ( hwnd , GWLP_USERDATA , 0 ) ;
}
return DefWindowProcW ( hwnd , msg , wParam , lParam ) ;
default :
return DefWindowProcW ( hwnd , msg , wParam , lParam ) ;
}
}
// 创建并显示自定义弹出菜单(在托盘窗口线程上下文调用)
void ShowCustomPopup ( TrayData * td , int x , int y ) {
if ( ! td ) return ;
HINSTANCE hInst = GetModuleHandleW ( NULL ) ;
HWND popup = CreateWindowExW (
WS_EX_TOOLWINDOW | WS_EX_TOPMOST ,
L " ModernTray_PopupWindowClass " , L " " ,
WS_POPUP ,
x , y , 200 , 200 ,
td - > hwnd /* owner */ , NULL , hInst , NULL
) ;
if ( ! popup ) return ;
PopupContext * ctx = new PopupContext ( ) ;
ctx - > tray = td ;
ctx - > items = td - > menuItems ; // 保证是完整列表
ctx - > hoverIndex = - 1 ;
SetWindowLongPtrW ( popup , GWLP_USERDATA , ( LONG_PTR ) ctx ) ;
// 计算尺寸并防止出屏
HDC hdc = GetDC ( popup ) ;
static HFONT sFont = nullptr ;
if ( ! sFont ) sFont = CreatePopupUIFont ( ) ;
SIZE sz = CalcPopupSize ( hdc , ctx - > items , sFont ) ;
ReleaseDC ( popup , hdc ) ;
RECT work { } ;
SystemParametersInfoW ( SPI_GETWORKAREA , 0 , & work , 0 ) ;
LONG px = std : : min (
std : : max ( static_cast < LONG > ( x ) , work . left ) ,
std : : max ( work . right - static_cast < LONG > ( sz . cx ) , work . left )
) ;
LONG py = std : : min (
std : : max ( static_cast < LONG > ( y ) , work . top ) ,
std : : max ( work . bottom - static_cast < LONG > ( sz . cy ) , work . top )
) ;
SetWindowPos ( popup , HWND_TOPMOST , px , py , sz . cx , sz . cy , SWP_SHOWWINDOW | SWP_NOACTIVATE ) ;
// 圆角
HRGN r = CreateRoundRectRgn ( 0 , 0 , sz . cx + 1 , sz . cy + 1 , 12 , 12 ) ;
SetWindowRgn ( popup , r , TRUE ) ;
// 抢前台以降低“瞬间失焦”概率
if ( td & & td - > hwnd ) SetForegroundWindow ( td - > hwnd ) ;
ShowWindow ( popup , SW_SHOWNORMAL ) ;
SetForegroundWindow ( popup ) ;
SetFocus ( popup ) ;
// 仅用于悬停高亮(不再用离开即关闭)
TRACKMOUSEEVENT tme { sizeof ( TRACKMOUSEEVENT ) } ;
tme . dwFlags = TME_LEAVE ;
tme . hwndTrack = popup ;
TrackMouseEvent ( & tme ) ;
UpdateWindow ( popup ) ;
}
// ---------- Tray 窗口处理函数 ----------
LRESULT CALLBACK TrayWndProc ( HWND hwnd , UINT msg , WPARAM wParam , LPARAM lParam ) {
TrayData * td = ( TrayData * ) GetWindowLongPtrW ( hwnd , GWLP_USERDATA ) ;
if ( msg = = ( WM_USER + 1 ) ) {
if ( lParam = = WM_RBUTTONUP ) {
if ( td & & td - > hwnd ) {
SetForegroundWindow ( td - > hwnd ) ;
}
else {
SetForegroundWindow ( hwnd ) ;
}
POINT pt ;
GetCursorPos ( & pt ) ;
SetForegroundWindow ( hwnd ) ;
TrackPopupMenuEx ( pData - > hMenu ,
TPM_RIGHTALIGN | TPM_BOTTOMALIGN ,
pt . x , pt . y , hwnd , NULL ) ;
PostMessageW ( hwnd , WM_NULL , 0 , 0 ) ;
}
if ( td ) ShowCustomPopup ( td , pt . x , pt . y ) ;
return 0 ;
case WM_LBUTTONDOWN :
if ( pData & & pData - > onClickMethod ) {
JavaVM * jvm ;
JNIEnv * env ;
if ( JNI_GetCreatedJavaVMs ( & jvm , 1 , NULL ) = = JNI_OK & &
jvm - > AttachCurrentThread ( ( void * * ) & env , NULL ) = = JNI_OK ) {
env - > CallVoidMethod ( pData - > eventObj , pData - > onClickMethod , ( jlong ) pData - > trayId ) ;
}
else if ( lParam = = WM_LBUTTONDOWN ) {
if ( td & & td - > eventObjGlobal & & td - > onClickMethod ) {
JavaVM * jvm = gJvm . load ( ) ;
if ( ! jvm ) {
JNI_GetCreatedJavaVMs ( & jvm , 1 , NULL ) ;
gJvm . store ( jvm ) ;
}
if ( jvm ) {
JNIEnv * env = nullptr ;
if ( jvm - > AttachCurrentThread ( ( void * * ) & env , NULL ) = = JNI_OK ) {
env - > CallVoidMethod ( td - > eventObjGlobal , td - > onClickMethod , ( jlong ) td - > trayId ) ;
jvm - > DetachCurrentThread ( ) ;
}
}
return 0 ;
}
break ;
case WM_COMMAND : {
int menuId = LOWORD ( wParam ) ;
if ( pData ) {
for ( auto & item : pData - > menuItems ) {
if ( item . menuId = = menuId ) {
JavaVM * jvm ;
JNIEnv * env ;
if ( JNI_GetCreatedJavaVMs ( & jvm , 1 , NULL ) = = JNI_OK & &
jvm - > AttachCurrentThread ( ( void * * ) & env , NULL ) = = JNI_OK ) {
env - > CallVoidMethod ( item . eventObj , item . onClickMethod , ( jlong ) pData - > trayId ) ;
jvm - > DetachCurrentThread ( ) ;
}
break ;
}
}
}
return 0 ;
}
}
switch ( msg ) {
case WM_CREATE :
return 0 ;
case WM_DESTROY :
PostQuitMessage ( 0 ) ;
return 0 ;
}
default :
return DefWindowProcW ( hwnd , msg , wParam , lParam ) ;
}
JNIEXPORT jlong JNICALL Java_com_axis_innovators_box_tools_RegisterTray_register
( JNIEnv * env , jclass , jstring name , jobject menuItems , jstring icon , jstring , jobject event ) {
// 注册窗口类
WNDCLASSEXW wc = { sizeof ( WNDCLASSEXW ) } ;
wc . lpfnWndProc = WndProc ;
wc . hInstance = GetModuleHandleW ( NULL ) ;
wc . lpszClassName = L " TrayWindowClass " ;
if ( ! RegisterClassExW ( & wc ) ) return - 1 ;
// 创建消息窗口
HWND hwnd = CreateWindowExW ( 0 , L " TrayWindowClass " , L " " , 0 , 0 , 0 , 0 , 0 ,
HWND_MESSAGE , NULL , NULL , NULL ) ;
if ( ! hwnd ) return - 1 ;
TrayData * pData = new TrayData ( ) ;
pData - > hwnd = hwnd ;
pData - > trayId = GetTickCount ( ) ;
pData - > hMenu = CreatePopupMenu ( ) ;
// 解析菜单项
jclass listClass = env - > GetObjectClass ( menuItems ) ;
jint size = env - > CallIntMethod ( menuItems , env - > GetMethodID ( listClass , " size " , " ()I " ) ) ;
for ( int i = 0 ; i < size ; + + i ) {
jobject item = env - > CallObjectMethod ( menuItems ,
env - > GetMethodID ( listClass , " get " , " (I)Ljava/lang/Object; " ) , i ) ;
jstring name = ( jstring ) env - > GetObjectField ( item ,
env - > GetFieldID ( env - > GetObjectClass ( item ) , " name " , " Ljava/lang/String; " ) ) ;
jobject eventObj = env - > GetObjectField ( item ,
env - > GetFieldID ( env - > GetObjectClass ( item ) , " event " ,
" Lcom/axis/innovators/box/tools/RegisterTray$Event; " ) ) ;
const jchar * nameChars = env - > GetStringChars ( name , NULL ) ;
std : : wstring menuName ( nameChars , nameChars + env - > GetStringLength ( name ) ) ;
env - > ReleaseStringChars ( name , nameChars ) ;
MenuItemData menuItem ;
menuItem . menuId = 1000 + i ;
menuItem . eventObj = env - > NewGlobalRef ( eventObj ) ;
jclass eventClass = env - > GetObjectClass ( eventObj ) ;
menuItem . onClickMethod = env - > GetMethodID ( eventClass , " onClick " , " (J)V " ) ;
AppendMenuW ( pData - > hMenu , MF_STRING , menuItem . menuId , menuName . c_str ( ) ) ;
pData - > menuItems . push_back ( menuItem ) ;
}
// 事件回调
jclass eventClass = env - > GetObjectClass ( event ) ;
p Data- > onClickMethod = env - > GetMethodID ( eventClass , " onClick " , " (J)V " ) ;
pData - > eventObj = env - > NewGlobalRef ( event ) ;
// 线程入口( Win32 线程入口)
DWORD WINAPI TrayMessageThreadProc ( LPVOID lpParam ) {
Tray Data* td = ( TrayData * ) lpParam ;
if ( ! td ) return 0 ;
TrayMessageThread ( td ) ;
return 0 ;
}
// 配置托盘
NOTIFYICONDATAW nid = { sizeof ( NOTIFYICONDATAW ) } ;
nid . hWnd = hwnd ;
nid . uID = pData - > trayId ;
nid . uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP ;
nid . uCallbackMessage = WM_USER + 1 ;
// 线程主体:为单个托盘创建窗口、图标并运行消息循环
void TrayMessageThread ( TrayData * td ) {
if ( ! td ) return ;
td - > running . store ( true ) ;
HINSTANCE hInst = GetModuleHandleW ( NULL ) ;
EnsureWindowClassesRegistered ( hInst ) ;
// 创建消息窗口(消息窗口必须在本线程创建)
HWND hwnd = CreateWindowExW ( 0 , L " ModernTray_TrayWindowClass " , L " " , 0 ,
CW_USEDEFAULT , CW_USEDEFAULT , CW_USEDEFAULT , CW_USEDEFAULT ,
HWND_MESSAGE , NULL , hInst , NULL ) ;
if ( ! hwnd ) {
//DEBUG_LOG(L"CreateWindowExW 失败");
td - > running . store ( false ) ;
return ;
}
td - > hwnd = hwnd ;
SetWindowLongPtrW ( hwnd , GWLP_USERDATA , ( LONG_PTR ) td ) ;
// 加载图标
const wchar_t * iconPath = ( const wchar_t * ) env - > GetStringChars ( icon , NULL ) ;
pData - > hIcon = LoadTrayIcon ( iconPath ) ;
nid . hIcon = pData - > hIcon ;
env - > ReleaseStringChars ( icon , ( const jchar * ) iconPath ) ;
td - > hIcon = LoadTrayIconSafe ( td - > iconPath ) ;
// 设置提示
const wchar_t * tip = ( const wchar_t * ) env - > GetStringChars ( name , NULL ) ;
wcsncpy_s ( nid . szTip , _countof ( nid . szTip ) , tip , _TRUNCATE ) ;
env - > ReleaseStringChars ( name , ( const jchar * ) tip ) ;
if ( ! Shell_NotifyIconW ( NIM_ADD , & nid ) ) {
delete pData ;
return - 1 ;
// 添加到系统托盘
NOTIFYICONDATAW nid = { 0 } ;
nid . cbSize = sizeof ( nid ) ;
nid . hWnd = hwnd ;
nid . uID = td - > trayId ;
nid . uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP ;
nid . uCallbackMessage = WM_USER + 1 ;
nid . hIcon = td - > hIcon ;
// szTip
{
std : : wstring tip = td - > name . empty ( ) ? L " Tray " : td - > name ;
wcsncpy_s ( nid . szTip , tip . c_str ( ) , _TRUNCATE ) ;
}
SetWindowLongPtrW ( hwnd , GWLP_USERDATA , ( LONG_PTR ) pData ) ;
trayDataList . push_back ( pData ) ;
if ( ! Shell_NotifyIconW ( NIM_ADD , & nid ) ) {
//DEBUG_LOG(L"Shell_NotifyIconW(NIM_ADD) 失败");
}
// 启动 消息循环
// 消息循环
MSG msg ;
while ( GetMessageW ( & msg , NULL , 0 , 0 ) ) {
TranslateMessage ( & msg ) ;
DispatchMessageW ( & msg ) ;
}
return ( jlong ) pData - > trayId ;
// 在退出前删除托盘图标(再一次保险)
Shell_NotifyIconW ( NIM_DELETE , & nid ) ;
// 清理窗口句柄(如果尚未)
if ( td - > hwnd ) {
DestroyWindow ( td - > hwnd ) ;
td - > hwnd = NULL ;
}
TrayData * FindTrayData ( UINT trayId ) {
for ( auto data : trayDataList ) {
if ( data - > trayId = = trayId ) return data ;
}
return nullptr ;
td - > running . store ( false ) ;
}
// ---------- JNI 接口实现 ----------
// 辅助:将 jstring 转 wchar_t string
static std : : wstring JStringToWString ( JNIEnv * env , jstring js ) {
if ( ! js ) return L " " ;
const jchar * chars = env - > GetStringChars ( js , NULL ) ;
jsize len = env - > GetStringLength ( js ) ;
std : : wstring ws ( chars , chars + len ) ;
env - > ReleaseStringChars ( js , chars ) ;
return ws ;
}
/*
* public static native long register(String name, List<Item> value, String icon, Event event);
*/
JNIEXPORT jlong JNICALL Java_com_axis_innovators_box_tools_RegisterTray_register
( JNIEnv * env , jclass , jstring jname , jobject jlist , jstring jicon , jobject jevent ) {
// 转换输入
std : : wstring name = JStringToWString ( env , jname ) ;
std : : wstring iconPath = JStringToWString ( env , jicon ) ;
// 创建 TrayData
TrayData * td = new TrayData ( ) ;
td - > trayId = ( UINT ) GetTickCount ( ) ;
td - > name = name ;
td - > iconPath = iconPath ;
// 缓存 JVM
JavaVM * jvm = nullptr ;
if ( JNI_GetCreatedJavaVMs ( & jvm , 1 , NULL ) = = JNI_OK & & jvm ) gJvm . store ( jvm ) ;
// event 全局引用与方法
if ( jevent ) {
td - > eventObjGlobal = env - > NewGlobalRef ( jevent ) ;
jclass evc = env - > GetObjectClass ( jevent ) ;
td - > onClickMethod = env - > GetMethodID ( evc , " onClick " , " (J)V " ) ;
}
// 解析列表 items( 更鲁棒的遍历)
if ( jlist ) {
jclass listClass = env - > GetObjectClass ( jlist ) ;
jmethodID sizeMid = env - > GetMethodID ( listClass , " size " , " ()I " ) ;
jmethodID getMid = env - > GetMethodID ( listClass , " get " , " (I)Ljava/lang/Object; " ) ;
jint size = env - > CallIntMethod ( jlist , sizeMid ) ;
jint parsed = 0 ;
for ( jint i = 0 ; i < size ; + + i ) {
jobject item = env - > CallObjectMethod ( jlist , getMid , i ) ;
if ( ! item ) continue ;
jclass itemClass = env - > GetObjectClass ( item ) ;
jfieldID nameF = env - > GetFieldID ( itemClass , " name " , " Ljava/lang/String; " ) ;
jstring jtitle = ( jstring ) env - > GetObjectField ( item , nameF ) ;
std : : wstring title = JStringToWString ( env , jtitle ) ;
jfieldID eventF = env - > GetFieldID ( itemClass , " event " , " Lcom/axis/innovators/box/tools/RegisterTray$Event; " ) ;
jobject ievent = env - > GetObjectField ( item , eventF ) ;
jobject globalEvent = nullptr ;
jmethodID onClickMid = nullptr ;
if ( ievent ) {
globalEvent = env - > NewGlobalRef ( ievent ) ;
jclass evc = env - > GetObjectClass ( ievent ) ;
onClickMid = env - > GetMethodID ( evc , " onClick " , " (J)V " ) ;
}
MenuItemData mid ;
mid . menuId = 1000 + i ;
mid . eventObjGlobal = globalEvent ;
mid . onClickMethod = onClickMid ;
mid . title = title ;
td - > menuItems . push_back ( mid ) ;
+ + parsed ;
}
// 打印数量,便于确认 Java 侧是否传入了多项
wchar_t buf [ 128 ] ;
swprintf_s ( buf , L " [register] parsed menu items = %d " , ( int ) parsed ) ;
//DEBUG_LOG(buf);
}
// 放入全局列表
{
//std::lock_guard<std::mutex> lk(gTrayListMutex);
gTrayList . push_back ( td ) ;
}
// 启动窗口线程
td - > threadHandle = CreateThread ( NULL , 0 , TrayMessageThreadProc , td , 0 , & td - > threadId ) ;
if ( ! td - > threadHandle ) {
DEBUG_LOG ( L " CreateThread failed " ) ;
// 清理
if ( td - > eventObjGlobal ) {
env - > DeleteGlobalRef ( td - > eventObjGlobal ) ;
td - > eventObjGlobal = NULL ;
}
for ( auto & mi : td - > menuItems ) {
if ( mi . eventObjGlobal ) {
env - > DeleteGlobalRef ( mi . eventObjGlobal ) ;
mi . eventObjGlobal = NULL ;
}
}
td - > menuItems . clear ( ) ;
delete td ;
return ( jlong ) 0 ;
}
return ( jlong ) td - > trayId ;
}
/*
* public static native long registerEx(String name, List<Item> value, String icon, String description, Event event);
*/
JNIEXPORT jlong JNICALL Java_com_axis_innovators_box_tools_RegisterTray_registerEx
( JNIEnv * env , jclass , jstring jname , jobject jlist , jstring jicon , jstring jdesc , jobject jevent ) {
// 功能与 register 相同,只是多了 description
std : : wstring name = JStringToWString ( env , jname ) ;
std : : wstring iconPath = JStringToWString ( env , jicon ) ;
std : : wstring desc = JStringToWString ( env , jdesc ) ;
TrayData * td = new TrayData ( ) ;
td - > trayId = ( UINT ) GetTickCount ( ) ;
td - > name = name ;
td - > iconPath = iconPath ;
td - > description = desc ;
// 缓存 JVM
JavaVM * jvm = nullptr ;
if ( JNI_GetCreatedJavaVMs ( & jvm , 1 , NULL ) = = JNI_OK & & jvm ) gJvm . store ( jvm ) ;
// event
if ( jevent ) {
td - > eventObjGlobal = env - > NewGlobalRef ( jevent ) ;
jclass evc = env - > GetObjectClass ( jevent ) ;
td - > onClickMethod = env - > GetMethodID ( evc , " onClick " , " (J)V " ) ;
}
// 解析 list
if ( jlist ) {
jclass listClass = env - > GetObjectClass ( jlist ) ;
jmethodID sizeMid = env - > GetMethodID ( listClass , " size " , " ()I " ) ;
jmethodID getMid = env - > GetMethodID ( listClass , " get " , " (I)Ljava/lang/Object; " ) ;
jint size = env - > CallIntMethod ( jlist , sizeMid ) ;
jint parsed = 0 ;
for ( jint i = 0 ; i < size ; + + i ) {
jobject item = env - > CallObjectMethod ( jlist , getMid , i ) ;
if ( ! item ) continue ;
jclass itemClass = env - > GetObjectClass ( item ) ;
jfieldID nameF = env - > GetFieldID ( itemClass , " name " , " Ljava/lang/String; " ) ;
jstring jtitle = ( jstring ) env - > GetObjectField ( item , nameF ) ;
std : : wstring title = JStringToWString ( env , jtitle ) ;
jfieldID eventF = env - > GetFieldID ( itemClass , " event " , " Lcom/axis/innovators/box/tools/RegisterTray$Event; " ) ;
jobject ievent = env - > GetObjectField ( item , eventF ) ;
jobject globalEvent = nullptr ;
jmethodID onClickMid = nullptr ;
if ( ievent ) {
globalEvent = env - > NewGlobalRef ( ievent ) ;
jclass evc = env - > GetObjectClass ( ievent ) ;
onClickMid = env - > GetMethodID ( evc , " onClick " , " (J)V " ) ;
}
MenuItemData mid ;
mid . menuId = 2000 + i ;
mid . eventObjGlobal = globalEvent ;
mid . onClickMethod = onClickMid ;
mid . title = title ;
td - > menuItems . push_back ( mid ) ;
+ + parsed ;
}
wchar_t buf [ 128 ] ;
swprintf_s ( buf , L " [registerEx] parsed menu items = %d " , ( int ) parsed ) ;
//DEBUG_LOG(buf);
}
{
//std::lock_guard<std::mutex> lk(gTrayListMutex);
gTrayList . push_back ( td ) ;
}
td - > threadHandle = CreateThread ( NULL , 0 , TrayMessageThreadProc , td , 0 , & td - > threadId ) ;
if ( ! td - > threadHandle ) {
//DEBUG_LOG(L"CreateThread failed (registerEx)");
// 清理
if ( td - > eventObjGlobal ) {
env - > DeleteGlobalRef ( td - > eventObjGlobal ) ;
td - > eventObjGlobal = NULL ;
}
for ( auto & mi : td - > menuItems ) {
if ( mi . eventObjGlobal ) {
env - > DeleteGlobalRef ( mi . eventObjGlobal ) ;
mi . eventObjGlobal = NULL ;
}
}
td - > menuItems . clear ( ) ;
delete td ;
return ( jlong ) 0 ;
}
return ( jlong ) td - > trayId ;
}
/*
* public static native void unregister(long id);
*/
JNIEXPORT void JNICALL Java_com_axis_innovators_box_tools_RegisterTray_unregister
( JNIEnv * env , jclass clazz , jlong id ) {
const UINT trayId = static_cast < UINT > ( id ) ;
TrayData * pData = FindTrayData ( trayId ) ;
if ( ! pData ) {
OutputDebugStringW ( L " [unregister] 找不到对应的托盘数据 " ) ;
( JNIEnv * env , jclass , jlong id ) {
UINT trayId = ( UINT ) id ;
TrayData * td = nullptr ;
{
//std::lock_guard<std::mutex> lk(gTrayListMutex);
auto it = std : : find_if ( gTrayList . begin ( ) , gTrayList . end ( ) , [ trayId ] ( TrayData * t ) { return t - > trayId = = trayId ; } ) ;
if ( it ! = gTrayList . end ( ) ) {
td = * it ;
gTrayList . erase ( it ) ;
}
}
if ( ! td ) {
DEBUG_LOG ( L " [unregister] 找不到对应的托盘数据 " ) ;
return ;
}
// 1. 删除系统托盘图标
NOTIFYICONDATAW nid = { sizeof ( NOTIFYICONDATAW ) } ;
nid . hWnd = pData - > hwnd ;
nid . uID = trayId ;
if ( ! Shell_NotifyIconW ( NIM_DELETE , & nid ) ) {
DWORD err = GetLastError ( ) ;
wchar_t errMsg [ 256 ] ;
swprintf_s ( errMsg , _countof ( errMsg ) , L " [unregister] 销毁窗口失败 (错误码: 0x%08X) " , err ) ;
OutputDebugStringW ( errMsg ) ;
// 发布消息让线程结束(销毁窗口会退出消息循环)
if ( td - > hwnd ) {
PostMessageW ( td - > hwnd , WM_CLOSE , 0 , 0 ) ;
}
// 2. 释放图标资源
if ( pData - > hIcon ) {
if ( ! DestroyIcon ( pData - > hIcon ) ) {
OutputDebugStringW ( L " [unregister] 销毁图标失败 " ) ;
// 等待线程退出
if ( td - > threadHandle ) {
if ( GetCurrentThreadId ( ) ! = td - > threadId ) {
DWORD waitRes = WaitForSingleObject ( td - > threadHandle , 5000 ) ;
if ( waitRes = = WAIT_TIMEOUT ) {
DEBUG_LOG ( L " [unregister] WaitForSingleObject 超时 " ) ;
}
}
else {
OutputDebugStringW ( L " [unregister] 图标资源已释放 " ) ;
for ( int i = 0 ; i < 100 & & td - > running . load ( ) ; + + i ) Sleep ( 10 ) ;
}
pData - > hIcon = NULL ;
}
// 3. 销毁菜单
if ( pData - > hMenu ) {
if ( ! DestroyMenu ( pData - > hMenu ) ) {
OutputDebugStringW ( L " [unregister] 销毁菜单失败 " ) ;
CloseHandle ( td - > threadHandle ) ;
td - > threadHandle = NULL ;
td - > threadId = 0 ;
}
else {
OutputDebugStringW ( L " [unregister] 菜单已销毁 " ) ;
}
pData - > hMenu = NULL ;
for ( int i = 0 ; i < 50 & & td - > running . load ( ) ; + + i ) Sleep ( 20 ) ;
}
// 4. 销毁窗口
if ( pData - > hwnd ) {
if ( ! DestroyWindow ( pData - > hwn d ) ) {
DWORD err = GetLastError ( ) ;
wchar_t errMsg [ 256 ] ;
swprintf_s ( errMsg , _countof ( errMsg ) , // 修改点
L " [unregister] 销毁窗口失败 (错误码: 0x%08X) " ,
err ) ;
OutputDebugStringW ( errMsg ) ;
}
else {
OutputDebugStringW ( L " [unregister] 窗口已销毁 " ) ;
}
pData - > hwnd = NULL ;
// 删除通知区图标(保险)
NOTIFYICONDATAW nid = { 0 } ;
nid . cbSize = sizeof ( ni d) ;
nid . hWnd = td - > hwnd ;
nid . uID = td - > trayId ;
Shell_NotifyIconW ( NIM_DELETE , & nid ) ;
// 释放 icon
if ( td - > hIcon ) {
DestroyIcon ( td - > hIcon ) ;
td - > hIcon = NULL ;
}
// 5. 释放JNI 全局引用
if ( pData - > eventObj ) {
env - > DeleteGlobalRef ( pData - > eventObj ) ;
pData - > eventObj = NULL ;
OutputDebugStringW ( L " [unregister] 事件全局引用已释放 " ) ;
// 删除 全局引用
if ( td - > eventObjGlobal ) {
env - > DeleteGlobalRef ( td - > eventObjGlobal ) ;
td - > eventObjGlobal = NULL ;
}
// 6. 释放菜单项全局引用
for ( auto & item : pData - > menuItems ) {
if ( item . eventObj ) {
env - > DeleteGlobalRef ( item . eventObj ) ;
item . eventObj = NULL ;
for ( auto & mi : td - > menuItems ) {
if ( mi . eventObjGlobal ) {
env - > DeleteGlobalRef ( mi . eventObjGlobal ) ;
mi . eventObjGlobal = NULL ;
}
}
pData - > menuItems . clear ( ) ;
OutputDebugStringW ( L " [unregister] 菜单项资源已清理 " ) ;
td - > menuItems . clear ( ) ;
// 7. 从全局列表移除
auto it = std : : remove_if ( trayDataList . begin ( ) , trayDataList . e nd( ),
[ pData ] ( TrayData * data ) { return data = = pData ; } ) ;
if ( it ! = trayDataList . end ( ) ) {
trayDataList . erase ( it , trayDataList . end ( ) ) ;
OutputDebugStringW ( L " [unregister] 已从全局列表移除 " ) ;
if ( td - > hwnd ) {
DestroyWindow ( td - > hw nd ) ;
td - > hwnd = NULL ;
}
// 8. 释放内存
delete pData ;
OutputDebugStringW ( L " [unregister] 内存已释放 " ) ;
// 9. 强制重绘任务栏
HWND taskbar = FindWindowW ( L " Shell_TrayWnd " , NULL ) ;
if ( taskbar ) {
RedrawWindow ( taskbar , NULL , NULL ,
RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN ) ;
}
delete td ;
DEBUG_LOG ( L " [unregister] 已完成清理 " ) ;
}
BOOL APIENTRY DllMain ( HMODULE hModule , DWORD reason , LPVOID lpReserved ) {
// DllMain
BOOL APIENTRY DllMain ( HMODULE , DWORD , LPVOID ) {
return TRUE ;
}