Skip to content

Commit

Permalink
Add window tray example
Browse files Browse the repository at this point in the history
new Win32pai:
* Shell_NotifyIcon
* GetSubMenu
* DestroyMenu
* TrackPopupMenuEx

new struct:
* NOTIFYICONDATA
* TPMPARAMS

new constants:
* Shell_NotifyIcon uFlags
* Shell_NotifyIcon WndProc callback msg
* NOTIFYICONDATA::uFlags
* NOTIFYICONDATA::dwState
* NOTIFYICONDATA::dwInfoFlags
* TrackPopupMenuEx uFlag
* LoadImage type, fuLoad
  • Loading branch information
ilopX committed Jan 16, 2021
1 parent c7bc6ae commit 7bb739a
Show file tree
Hide file tree
Showing 12 changed files with 969 additions and 5 deletions.
69 changes: 69 additions & 0 deletions example/shell_notify_icon/_app.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'dart:ffi';
import 'dart:io';

import 'package:win32/win32.dart';

final hInst = GetModuleHandle(nullptr);

const EVENT_QUIT = WM_APP + 2;
const EVENT_TRAY_NOTIFY = WM_APP + 1;

typedef LocalWndProc = bool Function(int hWnd,
int uMsg, int wParam, int lParam);

final wndProc = Pointer.fromFunction<WindowProc>(_appWndProc, 0);

void exec() {
final msg = MSG.allocate().addressOf;
while (GetMessage(msg, NULL, 0, 0) != 0) {
TranslateMessage(msg);
DispatchMessage(msg);
}
}

int loadDartIcon() {
final dartIconPath = _thisPath('dart.ico');
return LoadImage(
0,
TEXT(dartIconPath),
IMAGE_ICON,
0,
0,
LR_LOADFROMFILE | LR_DEFAULTSIZE | LR_SHARED
);
}

final _localWndProcs = <LocalWndProc>[];

/// Use in iterateLocalWndProcs
void registryWdnProc(LocalWndProc proc) => _localWndProcs.add(proc);

void deregisterWndProc(LocalWndProc proc) {
_localWndProcs.remove(proc);
}
int _appWndProc(int hWnd, int uMsg, int wParam, int lParam) {
if (iterateLocalWndProcs(hWnd, uMsg, wParam, lParam)) {
return TRUE;
}

switch(uMsg) {
case WM_CLOSE:
ShowWindow(hWnd, SW_HIDE);
return TRUE;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

bool iterateLocalWndProcs(int hWnd, int uMsg, int wParam, int lParam) {
for(final proc in _localWndProcs) {
final isProcProcessed = proc(hWnd, uMsg, wParam, lParam);
if (isProcProcessed) {
return true;
}
}
return false;
}

String _thisPath(String fileName) => Platform.script
.toFilePath()
.replaceFirst(RegExp(r'[^\\]+$'), fileName);
63 changes: 63 additions & 0 deletions example/shell_notify_icon/_menu.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import 'dart:ffi';
import 'dart:math';

import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';

import '_app.dart' as app;
import '_tray.dart' as tray;

void show({required int hWndParent}) {
final mousePos = _currentMousePos();
final hMenu = _buildMenu();

SetForegroundWindow(hWndParent);
TrackPopupMenuEx(hMenu,
_contextMenuFlags,
mousePos.x, mousePos.y,
hWndParent, nullptr);

DestroyMenu(hMenu);
}

bool wndProc(int hWnd, int uMsg, int wParam, int lParam) {
switch (uMsg) {
case WM_COMMAND:
final param = LOWORD(wParam);
switch (param) {
case app.EVENT_QUIT:
tray.removeIcon();
PostQuitMessage(0);
return true;
}
}
return false;
}

int _buildMenu() {
final hMenu = CreateMenu();
AppendMenu(hMenu, MF_STRING, app.EVENT_QUIT, TEXT("&Quit"));

final hMenubar = CreateMenu();
AppendMenu(hMenubar, MF_POPUP, hMenu, TEXT("_Parent"));

return GetSubMenu(hMenubar, 0);
}

Point<int> _currentMousePos() {
final point = POINT.allocate();
GetCursorPos(point.addressOf);
final result = Point(point.x, point.y);
free(point.addressOf);
return result;
}

int get _contextMenuFlags {
var uFlags = TPM_RIGHTBUTTON;
if (GetSystemMetrics(SM_MENUDROPALIGNMENT) != 0) {
uFlags |= TPM_RIGHTALIGN;
} else {
uFlags |= TPM_LEFTALIGN;
}
return uFlags;
}
65 changes: 65 additions & 0 deletions example/shell_notify_icon/_tray.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';

import '_app.dart' as app;
import '_menu.dart' as menu;

NOTIFYICONDATA _nid = NOTIFYICONDATA.allocate();

bool _trayWndProc(int hWnd, int msg, int wParam, int lParam) {
switch (msg) {
case app.EVENT_TRAY_NOTIFY:
final trayMsg = _fixNotifyDataToVersion4(LOWORD(lParam));
switch (trayMsg) {
case NIN_SELECT:
ShowWindow(_nid.hWnd, IsWindowVisible(_nid.hWnd) == 1
? SW_HIDE
: SW_SHOW);
SetForegroundWindow(_nid.hWnd);
return true;

case WM_CONTEXTMENU:
menu.show(hWndParent: hWnd);
return true;
}
}
return false;
}

void addIcon({required int hWndParent}) {
_nid.hWnd = hWndParent;
_nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE | NIF_SHOWTIP | NIF_GUID;
_nid.szTip = 'Dart tray';
_nid.uCallbackMessage = app.EVENT_TRAY_NOTIFY;
_nid.hIcon = app.loadDartIcon();

Shell_NotifyIcon(NIM_ADD, _nid.addressOf);

// TODO: uVersion does not yet support. See NOTIFYICONDATA declaration
// nid.uVersion = 4;
// Shell_NotifyIcon(NIM_SETVERSION, nid.addressOf);

app.registryWdnProc(_trayWndProc);
}

void removeIcon() {
Shell_NotifyIcon(NIM_DELETE, _nid.addressOf);
free(_nid.addressOf);
app.deregisterWndProc(_trayWndProc);
}

int _fixNotifyDataToVersion4(int msg) {
switch(msg) {
case 521: return WM_MBUTTONDBLCLK;
case 520: return WM_MBUTTONUP;
case 519: return WM_MBUTTONDOWN;
case 517: return WM_CONTEXTMENU;
case 516: return WM_RBUTTONDOWN;
case 515: return WM_LBUTTONDBLCLK;
case 514: return NIN_SELECT;
case 513: return WM_LBUTTONDOWN;
case 512: return WM_MOUSEMOVE;
}
return msg;
}
60 changes: 60 additions & 0 deletions example/shell_notify_icon/_window.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import 'dart:ffi';
import 'dart:math' as math;

import 'package:win32/win32.dart';

import '_app.dart' as app;

bool _windowWndProc(int hWnd, int uMsg, int wParam, int lParam) {
switch(uMsg) {
case WM_CLOSE:
ShowWindow(hWnd, SW_HIDE);
return true;
}
return false;
}

int createHidden() {
final windowClassNme = _regWinClass();
final rect = _getWindowCenterRect();
final hWnd = CreateWindowEx(
0,
TEXT(windowClassNme),
TEXT('Tray Callback Window'),
WS_OVERLAPPEDWINDOW,
rect.left,
rect.top,
rect.width,
rect.height,
NULL,
NULL,
app.hInst,
nullptr);
app.registryWdnProc(_windowWndProc);
return hWnd;
}

String _regWinClass() {
const windowClass = 'Tray_Callback_Window';
final wc = WNDCLASS.allocate();
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = app.wndProc;
wc.hInstance = app.hInst;
wc.hIcon = app.loadDartIcon();
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = TEXT(windowClass);
RegisterClass(wc.addressOf);
return windowClass;
}

math.Rectangle<int> _getWindowCenterRect() {
const windowWidth = 500;
const windowHeight = 250;

final screenWidth = GetSystemMetrics(SM_CXFULLSCREEN);
final screenHeight = GetSystemMetrics(SM_CYFULLSCREEN);

final x = (screenWidth - windowWidth) ~/ 2;
final y = (screenHeight - windowHeight) ~/ 2;
return math.Rectangle(x, y, windowWidth, windowHeight);
}
Binary file added example/shell_notify_icon/dart.ico
Binary file not shown.
12 changes: 12 additions & 0 deletions example/shell_notify_icon/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import '_app.dart' as app;
import '_menu.dart' as menu;
import '_tray.dart' as tray;
import '_window.dart' as window;

void main() {
final hWnd = window.createHidden();
tray.addIcon(hWndParent: hWnd);
app.registryWdnProc(menu.wndProc);
app.exec();
}

Loading

0 comments on commit 7bb739a

Please sign in to comment.