feat(RegisterTray): 重构并添加新功能
- 重构了 RegisterTray.dll 的核心逻辑,使用更现代的 Windows API - 添加了自定义弹出菜单功能,支持鼠标悬停和点击事件 - 优化了托盘图标的创建和销毁流程 -改进了错误处理和资源管理- 新增 registerEx 方法,支持描述信息
This commit is contained in:
Binary file not shown.
@@ -7,12 +7,21 @@
|
|||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Class: com_axis_innovators_box_tools_RegisterTray
|
* Class: com_axis_innovators_box_tools_RegisterTray
|
||||||
* Method: register
|
* Method: register
|
||||||
* Signature: (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Lcom/axis/innovators/box/tools/RegisterTray/Event;)J
|
* Signature: (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lcom/axis/innovators/box/tools/RegisterTray/Event;)J
|
||||||
*/
|
*/
|
||||||
JNIEXPORT jlong JNICALL Java_com_axis_innovators_box_tools_RegisterTray_register
|
JNIEXPORT jlong JNICALL Java_com_axis_innovators_box_tools_RegisterTray_register
|
||||||
|
(JNIEnv*, jclass, jstring, jobject, jstring, jobject);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: com_axis_innovators_box_tools_RegisterTray
|
||||||
|
* Method: registerEx
|
||||||
|
* Signature: (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Lcom/axis/innovators/box/tools/RegisterTray/Event;)J
|
||||||
|
*/
|
||||||
|
JNIEXPORT jlong JNICALL Java_com_axis_innovators_box_tools_RegisterTray_registerEx
|
||||||
(JNIEnv*, jclass, jstring, jobject, jstring, jstring, jobject);
|
(JNIEnv*, jclass, jstring, jobject, jstring, jstring, jobject);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -4,288 +4,790 @@
|
|||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#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"
|
#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 {
|
struct MenuItemData {
|
||||||
|
jint menuId;
|
||||||
|
jobject eventObjGlobal; // global ref to the Event object for this item
|
||||||
jmethodID onClickMethod;
|
jmethodID onClickMethod;
|
||||||
jobject eventObj;
|
std::wstring title;
|
||||||
int menuId;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TrayData {
|
struct TrayData {
|
||||||
HMENU hMenu = NULL;
|
|
||||||
std::vector<MenuItemData> menuItems;
|
|
||||||
jobject eventObj = NULL;
|
|
||||||
jmethodID onClickMethod = NULL;
|
|
||||||
HWND hwnd = NULL;
|
HWND hwnd = NULL;
|
||||||
UINT trayId = 0;
|
UINT trayId = 0;
|
||||||
HICON hIcon = NULL;
|
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*> trayDataList;
|
static std::vector<TrayData*> gTrayList;
|
||||||
|
static std::mutex gTrayListMutex;
|
||||||
|
|
||||||
HICON LoadTrayIcon(const wchar_t* path) {
|
// ---------- 主题/字体 ----------
|
||||||
HICON hIcon = (HICON)LoadImageW(
|
|
||||||
NULL, path, IMAGE_ICON,
|
struct ThemeColors {
|
||||||
GetSystemMetrics(SM_CXSMICON),
|
COLORREF bg;
|
||||||
GetSystemMetrics(SM_CYSMICON),
|
COLORREF hover;
|
||||||
LR_LOADFROMFILE | LR_SHARED
|
COLORREF text;
|
||||||
);
|
COLORREF border;
|
||||||
return hIcon ? hIcon : LoadIconW(NULL, IDI_APPLICATION);
|
};
|
||||||
|
|
||||||
|
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) {
|
static ThemeColors GetThemeColors()
|
||||||
TrayData* pData = (TrayData*)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
|
{
|
||||||
|
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) {
|
switch (msg) {
|
||||||
case WM_USER + 1:
|
case WM_CREATE:
|
||||||
switch (lParam) {
|
SetTimer(hwnd, kGuardTimerId, kGuardMs, nullptr);
|
||||||
case WM_RBUTTONUP:
|
return 0;
|
||||||
if (pData && pData->hMenu) {
|
|
||||||
|
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;
|
POINT pt;
|
||||||
GetCursorPos(&pt);
|
GetCursorPos(&pt);
|
||||||
SetForegroundWindow(hwnd);
|
if (td) ShowCustomPopup(td, pt.x, pt.y);
|
||||||
TrackPopupMenuEx(pData->hMenu,
|
|
||||||
TPM_RIGHTALIGN | TPM_BOTTOMALIGN,
|
|
||||||
pt.x, pt.y, hwnd, NULL);
|
|
||||||
PostMessageW(hwnd, WM_NULL, 0, 0);
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
case WM_LBUTTONDOWN:
|
}
|
||||||
if (pData && pData->onClickMethod) {
|
else if (lParam == WM_LBUTTONDOWN) {
|
||||||
JavaVM* jvm;
|
if (td && td->eventObjGlobal && td->onClickMethod) {
|
||||||
JNIEnv* env;
|
JavaVM* jvm = gJvm.load();
|
||||||
if (JNI_GetCreatedJavaVMs(&jvm, 1, NULL) == JNI_OK &&
|
if (!jvm) {
|
||||||
jvm->AttachCurrentThread((void**)&env, NULL) == JNI_OK) {
|
JNI_GetCreatedJavaVMs(&jvm, 1, NULL);
|
||||||
env->CallVoidMethod(pData->eventObj, pData->onClickMethod, (jlong)pData->trayId);
|
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();
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (msg) {
|
||||||
|
case WM_CREATE:
|
||||||
|
return 0;
|
||||||
case WM_DESTROY:
|
case WM_DESTROY:
|
||||||
PostQuitMessage(0);
|
PostQuitMessage(0);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
default:
|
||||||
return DefWindowProcW(hwnd, msg, wParam, lParam);
|
return DefWindowProcW(hwnd, msg, wParam, lParam);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jlong JNICALL Java_com_axis_innovators_box_tools_RegisterTray_register
|
// 线程入口(Win32 线程入口)
|
||||||
(JNIEnv* env, jclass, jstring name, jobject menuItems, jstring icon, jstring, jobject event) {
|
DWORD WINAPI TrayMessageThreadProc(LPVOID lpParam) {
|
||||||
// 注册窗口类
|
TrayData* td = (TrayData*)lpParam;
|
||||||
WNDCLASSEXW wc = { sizeof(WNDCLASSEXW) };
|
if (!td) return 0;
|
||||||
wc.lpfnWndProc = WndProc;
|
TrayMessageThread(td);
|
||||||
wc.hInstance = GetModuleHandleW(NULL);
|
return 0;
|
||||||
wc.lpszClassName = L"TrayWindowClass";
|
}
|
||||||
if (!RegisterClassExW(&wc)) return -1;
|
|
||||||
|
|
||||||
// 创建消息窗口
|
// 线程主体:为单个托盘创建窗口、图标并运行消息循环
|
||||||
HWND hwnd = CreateWindowExW(0, L"TrayWindowClass", L"", 0, 0, 0, 0, 0,
|
void TrayMessageThread(TrayData* td) {
|
||||||
HWND_MESSAGE, NULL, NULL, NULL);
|
if (!td) return;
|
||||||
if (!hwnd) return -1;
|
td->running.store(true);
|
||||||
|
|
||||||
TrayData* pData = new TrayData();
|
HINSTANCE hInst = GetModuleHandleW(NULL);
|
||||||
pData->hwnd = hwnd;
|
EnsureWindowClassesRegistered(hInst);
|
||||||
pData->trayId = GetTickCount();
|
|
||||||
pData->hMenu = CreatePopupMenu();
|
|
||||||
|
|
||||||
// 解析菜单项
|
// 创建消息窗口(消息窗口必须在本线程创建)
|
||||||
jclass listClass = env->GetObjectClass(menuItems);
|
HWND hwnd = CreateWindowExW(0, L"ModernTray_TrayWindowClass", L"", 0,
|
||||||
jint size = env->CallIntMethod(menuItems, env->GetMethodID(listClass, "size", "()I"));
|
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
|
||||||
|
HWND_MESSAGE, NULL, hInst, NULL);
|
||||||
for (int i = 0; i < size; ++i) {
|
if (!hwnd) {
|
||||||
jobject item = env->CallObjectMethod(menuItems,
|
//DEBUG_LOG(L"CreateWindowExW 失败");
|
||||||
env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;"), i);
|
td->running.store(false);
|
||||||
|
return;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
td->hwnd = hwnd;
|
||||||
// 事件回调
|
SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)td);
|
||||||
jclass eventClass = env->GetObjectClass(event);
|
|
||||||
pData->onClickMethod = env->GetMethodID(eventClass, "onClick", "(J)V");
|
|
||||||
pData->eventObj = env->NewGlobalRef(event);
|
|
||||||
|
|
||||||
// 配置托盘
|
|
||||||
NOTIFYICONDATAW nid = { sizeof(NOTIFYICONDATAW) };
|
|
||||||
nid.hWnd = hwnd;
|
|
||||||
nid.uID = pData->trayId;
|
|
||||||
nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
|
|
||||||
nid.uCallbackMessage = WM_USER + 1;
|
|
||||||
|
|
||||||
// 加载图标
|
// 加载图标
|
||||||
const wchar_t* iconPath = (const wchar_t*)env->GetStringChars(icon, NULL);
|
td->hIcon = LoadTrayIconSafe(td->iconPath);
|
||||||
pData->hIcon = LoadTrayIcon(iconPath);
|
|
||||||
nid.hIcon = pData->hIcon;
|
|
||||||
env->ReleaseStringChars(icon, (const jchar*)iconPath);
|
|
||||||
|
|
||||||
// 设置提示
|
// 添加到系统托盘
|
||||||
const wchar_t* tip = (const wchar_t*)env->GetStringChars(name, NULL);
|
NOTIFYICONDATAW nid = { 0 };
|
||||||
wcsncpy_s(nid.szTip, _countof(nid.szTip), tip, _TRUNCATE);
|
nid.cbSize = sizeof(nid);
|
||||||
env->ReleaseStringChars(name, (const jchar*)tip);
|
nid.hWnd = hwnd;
|
||||||
|
nid.uID = td->trayId;
|
||||||
if (!Shell_NotifyIconW(NIM_ADD, &nid)) {
|
nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
|
||||||
delete pData;
|
nid.uCallbackMessage = WM_USER + 1;
|
||||||
return -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);
|
if (!Shell_NotifyIconW(NIM_ADD, &nid)) {
|
||||||
trayDataList.push_back(pData);
|
//DEBUG_LOG(L"Shell_NotifyIconW(NIM_ADD) 失败");
|
||||||
|
}
|
||||||
|
|
||||||
// 启动消息循环
|
// 消息循环
|
||||||
MSG msg;
|
MSG msg;
|
||||||
while (GetMessageW(&msg, NULL, 0, 0)) {
|
while (GetMessageW(&msg, NULL, 0, 0)) {
|
||||||
TranslateMessage(&msg);
|
TranslateMessage(&msg);
|
||||||
DispatchMessageW(&msg);
|
DispatchMessageW(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (jlong)pData->trayId;
|
// 在退出前删除托盘图标(再一次保险)
|
||||||
}
|
Shell_NotifyIconW(NIM_DELETE, &nid);
|
||||||
|
|
||||||
TrayData* FindTrayData(UINT trayId) {
|
// 清理窗口句柄(如果尚未)
|
||||||
for (auto data : trayDataList) {
|
if (td->hwnd) {
|
||||||
if (data->trayId == trayId) return data;
|
DestroyWindow(td->hwnd);
|
||||||
|
td->hwnd = NULL;
|
||||||
}
|
}
|
||||||
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
|
JNIEXPORT void JNICALL Java_com_axis_innovators_box_tools_RegisterTray_unregister
|
||||||
(JNIEnv* env, jclass clazz, jlong id) {
|
(JNIEnv* env, jclass, jlong id) {
|
||||||
const UINT trayId = static_cast<UINT>(id);
|
UINT trayId = (UINT)id;
|
||||||
TrayData* pData = FindTrayData(trayId);
|
TrayData* td = nullptr;
|
||||||
|
{
|
||||||
if (!pData) {
|
//std::lock_guard<std::mutex> lk(gTrayListMutex);
|
||||||
OutputDebugStringW(L"[unregister] 找不到对应的托盘数据");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 删除系统托盘图标
|
// 发布消息让线程结束(销毁窗口会退出消息循环)
|
||||||
NOTIFYICONDATAW nid = { sizeof(NOTIFYICONDATAW) };
|
if (td->hwnd) {
|
||||||
nid.hWnd = pData->hwnd;
|
PostMessageW(td->hwnd, WM_CLOSE, 0, 0);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 释放图标资源
|
// 等待线程退出
|
||||||
if (pData->hIcon) {
|
if (td->threadHandle) {
|
||||||
if (!DestroyIcon(pData->hIcon)) {
|
if (GetCurrentThreadId() != td->threadId) {
|
||||||
OutputDebugStringW(L"[unregister] 销毁图标失败");
|
DWORD waitRes = WaitForSingleObject(td->threadHandle, 5000);
|
||||||
|
if (waitRes == WAIT_TIMEOUT) {
|
||||||
|
DEBUG_LOG(L"[unregister] WaitForSingleObject 超时");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
OutputDebugStringW(L"[unregister] 图标资源已释放");
|
for (int i = 0; i < 100 && td->running.load(); ++i) Sleep(10);
|
||||||
}
|
}
|
||||||
pData->hIcon = NULL;
|
CloseHandle(td->threadHandle);
|
||||||
}
|
td->threadHandle = NULL;
|
||||||
|
td->threadId = 0;
|
||||||
// 3. 销毁菜单
|
|
||||||
if (pData->hMenu) {
|
|
||||||
if (!DestroyMenu(pData->hMenu)) {
|
|
||||||
OutputDebugStringW(L"[unregister] 销毁菜单失败");
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
OutputDebugStringW(L"[unregister] 菜单已销毁");
|
for (int i = 0; i < 50 && td->running.load(); ++i) Sleep(20);
|
||||||
}
|
|
||||||
pData->hMenu = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 销毁窗口
|
// 删除通知区图标(保险)
|
||||||
if (pData->hwnd) {
|
NOTIFYICONDATAW nid = { 0 };
|
||||||
if (!DestroyWindow(pData->hwnd)) {
|
nid.cbSize = sizeof(nid);
|
||||||
DWORD err = GetLastError();
|
nid.hWnd = td->hwnd;
|
||||||
wchar_t errMsg[256];
|
nid.uID = td->trayId;
|
||||||
swprintf_s(errMsg, _countof(errMsg), // 修改点
|
Shell_NotifyIconW(NIM_DELETE, &nid);
|
||||||
L"[unregister] 销毁窗口失败 (错误码: 0x%08X)",
|
|
||||||
err);
|
// 释放 icon
|
||||||
OutputDebugStringW(errMsg);
|
if (td->hIcon) {
|
||||||
}
|
DestroyIcon(td->hIcon);
|
||||||
else {
|
td->hIcon = NULL;
|
||||||
OutputDebugStringW(L"[unregister] 窗口已销毁");
|
|
||||||
}
|
|
||||||
pData->hwnd = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 释放JNI全局引用
|
// 删除全局引用
|
||||||
if (pData->eventObj) {
|
if (td->eventObjGlobal) {
|
||||||
env->DeleteGlobalRef(pData->eventObj);
|
env->DeleteGlobalRef(td->eventObjGlobal);
|
||||||
pData->eventObj = NULL;
|
td->eventObjGlobal = NULL;
|
||||||
OutputDebugStringW(L"[unregister] 事件全局引用已释放");
|
|
||||||
}
|
}
|
||||||
|
for (auto& mi : td->menuItems) {
|
||||||
// 6. 释放菜单项全局引用
|
if (mi.eventObjGlobal) {
|
||||||
for (auto& item : pData->menuItems) {
|
env->DeleteGlobalRef(mi.eventObjGlobal);
|
||||||
if (item.eventObj) {
|
mi.eventObjGlobal = NULL;
|
||||||
env->DeleteGlobalRef(item.eventObj);
|
|
||||||
item.eventObj = NULL;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pData->menuItems.clear();
|
td->menuItems.clear();
|
||||||
OutputDebugStringW(L"[unregister] 菜单项资源已清理");
|
|
||||||
|
|
||||||
// 7. 从全局列表移除
|
if (td->hwnd) {
|
||||||
auto it = std::remove_if(trayDataList.begin(), trayDataList.end(),
|
DestroyWindow(td->hwnd);
|
||||||
[pData](TrayData* data) { return data == pData; });
|
td->hwnd = NULL;
|
||||||
|
|
||||||
if (it != trayDataList.end()) {
|
|
||||||
trayDataList.erase(it, trayDataList.end());
|
|
||||||
OutputDebugStringW(L"[unregister] 已从全局列表移除");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8. 释放内存
|
delete td;
|
||||||
delete pData;
|
DEBUG_LOG(L"[unregister] 已完成清理");
|
||||||
OutputDebugStringW(L"[unregister] 内存已释放");
|
|
||||||
|
|
||||||
// 9. 强制重绘任务栏
|
|
||||||
HWND taskbar = FindWindowW(L"Shell_TrayWnd", NULL);
|
|
||||||
if (taskbar) {
|
|
||||||
RedrawWindow(taskbar, NULL, NULL,
|
|
||||||
RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) {
|
// DllMain
|
||||||
|
BOOL APIENTRY DllMain(HMODULE, DWORD, LPVOID) {
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ public class Main {
|
|||||||
if (!acquireLock()) {
|
if (!acquireLock()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AxisInnovatorsBox.run(args, true);
|
AxisInnovatorsBox.run(args, debugWindowEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ public class RegisterTray {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
LibraryLoad.loadLibrary("RegisterTray");
|
LibraryLoad.loadLibrary("RegisterTray");
|
||||||
|
//System.load("C:\\Users\\Administrator\\source\\repos\\RegisterTray\\x64\\Release\\RegisterTray.dll");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,39 +27,28 @@ public class RegisterTray {
|
|||||||
* 添加菜单项
|
* 添加菜单项
|
||||||
* @param id 菜单项唯一标识符(需大于0)
|
* @param id 菜单项唯一标识符(需大于0)
|
||||||
* @param label 菜单显示文本
|
* @param label 菜单显示文本
|
||||||
* @param onClick 点击事件处理器
|
* @param onClick 点击事件处理器(接收 itemId)
|
||||||
* @return 当前构建器实例
|
* @return 当前构建器实例
|
||||||
*/
|
*/
|
||||||
public MenuBuilder addItem(int id, String label, MenuItemClickListener onClick) {
|
public MenuBuilder addItem(int id, String label, MenuItemClickListener onClick) {
|
||||||
items.add(new Item(
|
// 为每一项创建一个 Event 回调对象(native 端会在点击时回调此 Event.onClick(long))
|
||||||
id,
|
// 这里我们忽略 native 传回的 trayId,只把 itemId 传给上层的 MenuItemClickListener
|
||||||
label,
|
Event ev = new Event() {
|
||||||
"", "", "",
|
@Override
|
||||||
(Event) combinedId -> {
|
public void onClick(long trayId) {
|
||||||
int itemId = (int)(combinedId >> 32);
|
try {
|
||||||
onClick.onClick(itemId);
|
onClick.onClick(id);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
t.printStackTrace();
|
||||||
}
|
}
|
||||||
));
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加菜单项
|
|
||||||
* @param id 菜单项唯一标识符(需大于0)
|
|
||||||
* @param label 菜单显示文本
|
|
||||||
* @param onClick 点击事件处理器
|
|
||||||
* @return 当前构建器实例
|
|
||||||
*/
|
|
||||||
public MenuBuilder addItem(MenuBuilder builder,int id, String label, MenuItemClickListener onClick) {
|
|
||||||
this.items = builder.items;
|
|
||||||
items.add(new Item(
|
items.add(new Item(
|
||||||
id,
|
id,
|
||||||
label,
|
label,
|
||||||
"", "", "",
|
"", "", "",
|
||||||
(Event) combinedId -> {
|
ev
|
||||||
int itemId = (int)(combinedId >> 32);
|
|
||||||
onClick.onClick(itemId);
|
|
||||||
}
|
|
||||||
));
|
));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -72,6 +62,7 @@ public class RegisterTray {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 托盘配置器(流畅接口)
|
* 托盘配置器(流畅接口)
|
||||||
*/
|
*/
|
||||||
@@ -143,7 +134,6 @@ public class RegisterTray {
|
|||||||
title,
|
title,
|
||||||
menuItems,
|
menuItems,
|
||||||
iconPath,
|
iconPath,
|
||||||
tooltip,
|
|
||||||
clickListener::onClick
|
clickListener::onClick
|
||||||
);
|
);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -185,8 +175,13 @@ public class RegisterTray {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static native long register(String name, List<Item> value,
|
public static native long register(String name, List<Item> items, String icon, Event event);
|
||||||
String icon, String description, Event event);
|
|
||||||
|
/**
|
||||||
|
* 更强的变体:允许提供 description(可以用于 tooltip 或弹出顶部信息)。
|
||||||
|
* 推荐使用 registerEx 来获取现代化圆角弹出菜单。
|
||||||
|
*/
|
||||||
|
public static native long registerEx(String name, List<Item> items, String icon, String description, Event event);
|
||||||
public static native void unregister(long id);
|
public static native void unregister(long id);
|
||||||
|
|
||||||
public interface Event {
|
public interface Event {
|
||||||
|
|||||||
@@ -60,22 +60,17 @@ public class Tray {
|
|||||||
* @throws RegisterTray.TrayException 抛出错误
|
* @throws RegisterTray.TrayException 抛出错误
|
||||||
*/
|
*/
|
||||||
public static void load(TrayLabels trayLabels) throws RegisterTray.TrayException {
|
public static void load(TrayLabels trayLabels) throws RegisterTray.TrayException {
|
||||||
if (trayLabels == null || trayLabelsList.contains(trayLabels)){
|
if (trayLabels == null || trayLabelsList.contains(trayLabels)) {
|
||||||
System.err.println("trayLabels is null or trayLabelsList contains trayLabels");
|
System.err.println("trayLabels is null or trayLabelsList contains trayLabels");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
trayLabelsList.add(trayLabels);
|
trayLabelsList.add(trayLabels);
|
||||||
|
|
||||||
if (menuBuilders == null) {
|
if (menuBuilders == null) {
|
||||||
RegisterTray.MenuBuilder menuBuilder = new RegisterTray.MenuBuilder()
|
menuBuilders = new RegisterTray.MenuBuilder();
|
||||||
.addItem(trayLabels.id, trayLabels.name, itemId -> trayLabels.action.run());
|
|
||||||
menuBuilders = menuBuilder;
|
|
||||||
menuItems = menuBuilder.build();
|
|
||||||
} else {
|
|
||||||
menuBuilders = new RegisterTray.MenuBuilder()
|
|
||||||
.addItem(menuBuilders, trayLabels.id, trayLabels.name, itemId -> trayLabels.action.run());
|
|
||||||
menuItems = menuBuilders.build();
|
|
||||||
}
|
}
|
||||||
|
menuBuilders.addItem(trayLabels.id, trayLabels.name, itemId -> trayLabels.action.run());
|
||||||
|
menuItems = menuBuilders.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void addAction(Runnable action){
|
public static void addAction(Runnable action){
|
||||||
|
|||||||
Reference in New Issue
Block a user