Skip to content

Commit

Permalink
Merge pull request #139 from ilopX/shell_notify_icon_example
Browse files Browse the repository at this point in the history
Add windows tray example
  • Loading branch information
timsneath authored Jan 19, 2021
2 parents 9538d9a + 543312a commit 3746f90
Show file tree
Hide file tree
Showing 15 changed files with 965 additions and 2 deletions.
65 changes: 65 additions & 0 deletions example/shell_notify_icon/_app.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'dart:ffi';
import 'dart:io';

import 'package:ffi/ffi.dart';
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 = calloc<MSG>();
while (GetMessage(msg, NULL, 0, 0) != 0) {
TranslateMessage(msg);
DispatchMessage(msg);
}
free(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);
61 changes: 61 additions & 0 deletions example/shell_notify_icon/_menu.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
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 = calloc<POINT>();
GetCursorPos(point);
final result = Point(point.ref.x, point.ref.y);
free(point);
return result;
}

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

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

final _nid = calloc<NOTIFYICONDATA>()..ref.cbSize = sizeOf<NOTIFYICONDATA>();

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

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

void addIcon({required int hWndParent}) {
final nid = _nid.ref;
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);

nid.uVersion = 4;
Shell_NotifyIcon(NIM_SETVERSION, _nid);

app.registryWdnProc(_trayWndProc);
}

void removeIcon() {
Shell_NotifyIcon(NIM_DELETE, _nid);
free(_nid);
app.deregisterWndProc(_trayWndProc);
}
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 pWndClass = calloc<WNDCLASS>()
..ref.style = CS_HREDRAW | CS_VREDRAW
..ref.lpfnWndProc = app.wndProc
..ref.hInstance = app.hInst
..ref.hIcon = app.loadDartIcon()
..ref.hCursor = LoadCursor(NULL, IDC_ARROW)
..ref.lpszClassName = TEXT(windowClass);
RegisterClass(pWndClass);
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.
11 changes: 11 additions & 0 deletions example/shell_notify_icon/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
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 3746f90

Please sign in to comment.