|
|
@@ -1,9 +1,19 @@
|
|
|
#include "flutter_window.h"
|
|
|
+#include "utils.h"
|
|
|
+#include "file_info.h"
|
|
|
+#include "file_icon.h"
|
|
|
|
|
|
#include <optional>
|
|
|
|
|
|
#include "flutter/generated_plugin_registrant.h"
|
|
|
|
|
|
+void trayMessageHandler(TrayIcon *, UINT);
|
|
|
+HICON getIconHandle(std::string);
|
|
|
+const flutter::EncodableValue* ValueOrNull(const flutter::EncodableMap& map, const char* key);
|
|
|
+
|
|
|
+static std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>> appChannel;
|
|
|
+static std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>> trayChannel;
|
|
|
+
|
|
|
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
|
|
|
: project_(project) {}
|
|
|
|
|
|
@@ -27,6 +37,13 @@ bool FlutterWindow::OnCreate() {
|
|
|
RegisterPlugins(flutter_controller_->engine());
|
|
|
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
|
|
|
|
|
+ // register channel
|
|
|
+ registerMethodChannel(flutter_controller_->engine());
|
|
|
+
|
|
|
+ // setup trayicon
|
|
|
+ m_tray.setUserData(this);
|
|
|
+ m_tray.setListener(trayMessageHandler);
|
|
|
+
|
|
|
flutter_controller_->engine()->SetNextFrameCallback([&]() {
|
|
|
this->Show();
|
|
|
});
|
|
|
@@ -40,6 +57,8 @@ bool FlutterWindow::OnCreate() {
|
|
|
}
|
|
|
|
|
|
void FlutterWindow::OnDestroy() {
|
|
|
+ m_tray.setVisible(false);
|
|
|
+
|
|
|
if (flutter_controller_) {
|
|
|
flutter_controller_ = nullptr;
|
|
|
}
|
|
|
@@ -47,10 +66,20 @@ void FlutterWindow::OnDestroy() {
|
|
|
Win32Window::OnDestroy();
|
|
|
}
|
|
|
|
|
|
+void FlutterWindow::SetMinimumSize(SIZE size) {
|
|
|
+ min_window_size_ = size;
|
|
|
+}
|
|
|
+
|
|
|
LRESULT
|
|
|
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
|
|
|
WPARAM const wparam,
|
|
|
LPARAM const lparam) noexcept {
|
|
|
+ // my custom message handler
|
|
|
+ std::optional<LRESULT> r = handleMessage(hwnd, message, wparam, lparam);
|
|
|
+ if (r) {
|
|
|
+ return *r;
|
|
|
+ }
|
|
|
+
|
|
|
// Give Flutter, including plugins, an opportunity to handle window messages.
|
|
|
if (flutter_controller_) {
|
|
|
std::optional<LRESULT> result =
|
|
|
@@ -65,7 +94,336 @@ FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
|
|
|
case WM_FONTCHANGE:
|
|
|
flutter_controller_->engine()->ReloadSystemFonts();
|
|
|
break;
|
|
|
+ case WM_GETMINMAXINFO:
|
|
|
+ if (min_window_size_.cx > 0 && min_window_size_.cy > 0) {
|
|
|
+ LPMINMAXINFO info = reinterpret_cast<LPMINMAXINFO>(lparam);
|
|
|
+ info->ptMinTrackSize.x = min_window_size_.cx;
|
|
|
+ info->ptMinTrackSize.y = min_window_size_.cy;
|
|
|
+ }
|
|
|
+ break;
|
|
|
}
|
|
|
|
|
|
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
|
|
|
}
|
|
|
+
|
|
|
+void FlutterWindow::registerMethodChannel(flutter::FlutterEngine* engine)
|
|
|
+{
|
|
|
+ appChannel = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
|
|
|
+ engine->messenger(), "app.nomo/app",
|
|
|
+ &flutter::StandardMethodCodec::GetInstance());
|
|
|
+ appChannel->SetMethodCallHandler([this](const auto& call, auto result) {
|
|
|
+ handleAppMethodCall(call, std::move(result));
|
|
|
+ });
|
|
|
+
|
|
|
+ trayChannel = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
|
|
|
+ engine->messenger(), "app.nomo/tray",
|
|
|
+ &flutter::StandardMethodCodec::GetInstance());
|
|
|
+ trayChannel->SetMethodCallHandler([this](const auto& call, auto result) {
|
|
|
+ handleTrayMethodCall(call, std::move(result));
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+void FlutterWindow::handleAppMethodCall(
|
|
|
+ const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
|
|
+ std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
|
|
|
+{
|
|
|
+ if (method_call.method_name().compare("activeWindow") == 0) {
|
|
|
+ onActiveWindow();
|
|
|
+ result->Success(flutter::EncodableValue(true));
|
|
|
+ //
|
|
|
+ } else if (method_call.method_name().compare("hideWindow") == 0) {
|
|
|
+ onHideWindow();
|
|
|
+ result->Success(flutter::EncodableValue(true));
|
|
|
+ //
|
|
|
+ } else if (method_call.method_name().compare("toggleWindow") == 0) {
|
|
|
+ onToggleWindow();
|
|
|
+ result->Success(flutter::EncodableValue(true));
|
|
|
+ //
|
|
|
+ } else if (method_call.method_name().compare("destroyWindow") == 0) {
|
|
|
+ Destroy();
|
|
|
+ result->Success(flutter::EncodableValue(true));
|
|
|
+ //
|
|
|
+ } else if (method_call.method_name().compare("extractFileIcon") == 0) {
|
|
|
+ extractFileIcon(method_call, std::move(result));
|
|
|
+ //
|
|
|
+ } else if (method_call.method_name().compare("getFileDescription") == 0) {
|
|
|
+ getFileDescription(method_call, std::move(result));
|
|
|
+ //
|
|
|
+ } else {
|
|
|
+ result->NotImplemented();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void FlutterWindow::handleTrayMethodCall(
|
|
|
+ const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
|
|
+ std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
|
|
|
+{
|
|
|
+ if (method_call.method_name().compare("setSystemTray") == 0) {
|
|
|
+ setSystemTray(method_call, std::move(result));
|
|
|
+ //
|
|
|
+ } else if (method_call.method_name().compare("resetSystemTray") == 0) {
|
|
|
+ resetSystemTray(method_call, std::move(result));
|
|
|
+ //
|
|
|
+ } else if (method_call.method_name().compare("setContextMenu") == 0) {
|
|
|
+ setContextMenu(method_call, std::move(result));
|
|
|
+ //
|
|
|
+ } else if (method_call.method_name().compare("popupContextMenu") == 0) {
|
|
|
+ popUpContextMenu(method_call, std::move(result));
|
|
|
+ //
|
|
|
+ } else {
|
|
|
+ result->NotImplemented();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+std::optional<LRESULT> FlutterWindow::handleMessage(HWND window, UINT const message,
|
|
|
+ WPARAM const wparam, LPARAM const lparam)
|
|
|
+{
|
|
|
+ if (message == WM_CLOSE) {
|
|
|
+ if (m_tray.isVisible()) {
|
|
|
+ onHideWindow();
|
|
|
+ } else {
|
|
|
+ Destroy();
|
|
|
+ }
|
|
|
+ return std::make_optional<LRESULT>(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (message == WM_COMMAND) {
|
|
|
+ flutter::EncodableMap eventData = flutter::EncodableMap();
|
|
|
+ eventData[flutter::EncodableValue("id")] = flutter::EncodableValue((int)wparam);
|
|
|
+ trayChannel->InvokeMethod("onTrayMenuItemClick",
|
|
|
+ std::make_unique<flutter::EncodableValue>(eventData));
|
|
|
+ }
|
|
|
+
|
|
|
+ return std::nullopt;
|
|
|
+}
|
|
|
+
|
|
|
+void FlutterWindow::onActiveWindow()
|
|
|
+{
|
|
|
+ bool isVisible = (::GetWindowLong(GetHandle(), GWL_STYLE) & WS_VISIBLE) != 0;
|
|
|
+ if (isVisible) {
|
|
|
+ WINDOWPLACEMENT windowPlacement;
|
|
|
+ GetWindowPlacement(GetHandle(), &windowPlacement);
|
|
|
+ if (windowPlacement.showCmd == SW_SHOWMINIMIZED) {
|
|
|
+ PostMessage(GetHandle(), WM_SYSCOMMAND, SC_RESTORE, 0);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ onShowWindow();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void FlutterWindow::onToggleWindow()
|
|
|
+{
|
|
|
+ bool isVisible = (::GetWindowLong(GetHandle(), GWL_STYLE) & WS_VISIBLE) != 0;
|
|
|
+ if (isVisible) {
|
|
|
+ WINDOWPLACEMENT windowPlacement;
|
|
|
+ GetWindowPlacement(GetHandle(), &windowPlacement);
|
|
|
+ if (windowPlacement.showCmd == SW_SHOWMINIMIZED) {
|
|
|
+ PostMessage(GetHandle(), WM_SYSCOMMAND, SC_RESTORE, 0);
|
|
|
+ } else {
|
|
|
+ onHideWindow();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ onShowWindow();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void FlutterWindow::onShowWindow()
|
|
|
+{
|
|
|
+ ShowWindow(GetHandle(), SW_SHOW);
|
|
|
+ SetForegroundWindow(GetHandle());
|
|
|
+ auto args = std::make_unique<flutter::EncodableValue>(nullptr);
|
|
|
+ appChannel->InvokeMethod("onWindowShow", std::move(args));
|
|
|
+}
|
|
|
+
|
|
|
+void FlutterWindow::onHideWindow()
|
|
|
+{
|
|
|
+ ShowWindow(GetHandle(), SW_HIDE);
|
|
|
+ auto args = std::make_unique<flutter::EncodableValue>(nullptr);
|
|
|
+ appChannel->InvokeMethod("onWindowHide", std::move(args));
|
|
|
+}
|
|
|
+
|
|
|
+void FlutterWindow::onTrayIconLButtonDown()
|
|
|
+{
|
|
|
+ auto args = std::make_unique<flutter::EncodableValue>(nullptr);
|
|
|
+ trayChannel->InvokeMethod("onTrayIconLButtonDown", std::move(args));
|
|
|
+}
|
|
|
+
|
|
|
+void FlutterWindow::onTrayIconLButtonUp()
|
|
|
+{
|
|
|
+ auto args = std::make_unique<flutter::EncodableValue>(nullptr);
|
|
|
+ trayChannel->InvokeMethod("onTrayIconLButtonUp", std::move(args));
|
|
|
+}
|
|
|
+
|
|
|
+void FlutterWindow::onTrayIconRButtonDown()
|
|
|
+{
|
|
|
+ auto args = std::make_unique<flutter::EncodableValue>(nullptr);
|
|
|
+ trayChannel->InvokeMethod("onTrayIconRButtonDown", std::move(args));
|
|
|
+}
|
|
|
+
|
|
|
+void FlutterWindow::onTrayIconRButtonUp()
|
|
|
+{
|
|
|
+ auto args = std::make_unique<flutter::EncodableValue>(nullptr);
|
|
|
+ trayChannel->InvokeMethod("onTrayIconRButtonUp", std::move(args));
|
|
|
+}
|
|
|
+
|
|
|
+void FlutterWindow::setSystemTray(
|
|
|
+ const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
|
|
+ std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
|
|
|
+{
|
|
|
+ const flutter::EncodableMap& args =
|
|
|
+ std::get<flutter::EncodableMap>(*method_call.arguments());
|
|
|
+ std::string tooltip = std::get<std::string>(args.at(flutter::EncodableValue("tooltip")));
|
|
|
+ std::string icon = std::get<std::string>(args.at(flutter::EncodableValue("icon")));
|
|
|
+
|
|
|
+ m_tray.setName((LPCWSTR)(Utf16FromUtf8(tooltip.c_str()).c_str()));
|
|
|
+ m_tray.setIcon(getIconHandle(icon));
|
|
|
+ m_tray.setVisible(true);
|
|
|
+ result->Success(flutter::EncodableValue(true));
|
|
|
+}
|
|
|
+
|
|
|
+void FlutterWindow::resetSystemTray(
|
|
|
+ const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
|
|
+ std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
|
|
|
+{
|
|
|
+ m_tray.setVisible(false);
|
|
|
+ result->Success(flutter::EncodableValue(true));
|
|
|
+}
|
|
|
+
|
|
|
+void FlutterWindow::setContextMenu(
|
|
|
+ const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
|
|
+ std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
|
|
|
+{
|
|
|
+ const flutter::EncodableMap& args =
|
|
|
+ std::get<flutter::EncodableMap>(*method_call.arguments());
|
|
|
+
|
|
|
+ m_menuHandle = CreatePopupMenu();
|
|
|
+
|
|
|
+ createMenu(m_menuHandle, std::get<flutter::EncodableMap>(
|
|
|
+ args.at(flutter::EncodableValue("menu"))));
|
|
|
+
|
|
|
+ result->Success(flutter::EncodableValue(true));
|
|
|
+}
|
|
|
+
|
|
|
+void FlutterWindow::popUpContextMenu(
|
|
|
+ const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
|
|
+ std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
|
|
|
+{
|
|
|
+ POINT pt;
|
|
|
+ if (GetCursorPos(&pt)) {
|
|
|
+ ::SetForegroundWindow(GetHandle());
|
|
|
+ TrackPopupMenu(m_menuHandle, TPM_BOTTOMALIGN | TPM_LEFTALIGN,
|
|
|
+ pt.x, pt.y, 0, GetHandle(), NULL);
|
|
|
+ }
|
|
|
+ result->Success(flutter::EncodableValue(true));
|
|
|
+}
|
|
|
+
|
|
|
+void FlutterWindow::extractFileIcon(
|
|
|
+ const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
|
|
+ std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
|
|
|
+{
|
|
|
+ const flutter::EncodableMap& args =
|
|
|
+ std::get<flutter::EncodableMap>(*method_call.arguments());
|
|
|
+ std::string fileName = std::get<std::string>(args.at(flutter::EncodableValue("fileName")));
|
|
|
+ std::string iconName = std::get<std::string>(args.at(flutter::EncodableValue("iconName")));
|
|
|
+
|
|
|
+ auto ret = SaveIcon(fileName, iconName, 32, 0);
|
|
|
+ result->Success(flutter::EncodableValue(ret));
|
|
|
+}
|
|
|
+
|
|
|
+void FlutterWindow::getFileDescription(
|
|
|
+ const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
|
|
+ std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
|
|
|
+{
|
|
|
+ const flutter::EncodableMap& args =
|
|
|
+ std::get<flutter::EncodableMap>(*method_call.arguments());
|
|
|
+ std::wstring fileName = Utf16FromUtf8(std::get<std::string>(args.at(flutter::EncodableValue("fileName"))).c_str());
|
|
|
+ std::wstring description;
|
|
|
+ FileInfoUtils::GetFileDescription(fileName, description);
|
|
|
+
|
|
|
+ std::wcout << fileName << " " << description << std::endl;
|
|
|
+
|
|
|
+ result->Success(flutter::EncodableValue(utf8_encode(description)));
|
|
|
+}
|
|
|
+
|
|
|
+void FlutterWindow::createMenu(HMENU menu, flutter::EncodableMap args) {
|
|
|
+ flutter::EncodableList items = std::get<flutter::EncodableList>(
|
|
|
+ args.at(flutter::EncodableValue("items")));
|
|
|
+
|
|
|
+ int count = GetMenuItemCount(menu);
|
|
|
+ for (int i = 0; i < count; i++) {
|
|
|
+ // always remove at 0 because they shift every time
|
|
|
+ RemoveMenu(menu, 0, MF_BYPOSITION);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (flutter::EncodableValue item_value : items) {
|
|
|
+ flutter::EncodableMap item_map =
|
|
|
+ std::get<flutter::EncodableMap>(item_value);
|
|
|
+ int id = std::get<int>(item_map.at(flutter::EncodableValue("id")));
|
|
|
+ std::string type =
|
|
|
+ std::get<std::string>(item_map.at(flutter::EncodableValue("type")));
|
|
|
+ std::string label =
|
|
|
+ std::get<std::string>(item_map.at(flutter::EncodableValue("label")));
|
|
|
+ auto* checked = std::get_if<bool>(ValueOrNull(item_map, "checked"));
|
|
|
+ bool disabled =
|
|
|
+ std::get<bool>(item_map.at(flutter::EncodableValue("disabled")));
|
|
|
+
|
|
|
+ UINT_PTR item_id = id;
|
|
|
+ UINT uFlags = MF_STRING;
|
|
|
+
|
|
|
+ if (disabled) {
|
|
|
+ uFlags |= MF_GRAYED;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (type.compare("separator") == 0) {
|
|
|
+ AppendMenuW(menu, MF_SEPARATOR, item_id, NULL);
|
|
|
+ } else {
|
|
|
+ if (type.compare("checkbox") == 0) {
|
|
|
+ if (checked == nullptr) {
|
|
|
+ // skip
|
|
|
+ } else {
|
|
|
+ uFlags |= (*checked == true ? MF_CHECKED : MF_UNCHECKED);
|
|
|
+ }
|
|
|
+ } else if (type.compare("submenu") == 0) {
|
|
|
+ uFlags |= MF_POPUP;
|
|
|
+ HMENU sub_menu = ::CreatePopupMenu();
|
|
|
+ createMenu(sub_menu, std::get<flutter::EncodableMap>(item_map.at(
|
|
|
+ flutter::EncodableValue("submenu"))));
|
|
|
+ item_id = reinterpret_cast<UINT_PTR>(sub_menu);
|
|
|
+ }
|
|
|
+ AppendMenuW(menu, uFlags, item_id, (LPCWSTR)(Utf16FromUtf8(label.c_str()).c_str()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void trayMessageHandler(TrayIcon *pTrayIcon, UINT uMsg)
|
|
|
+{
|
|
|
+ switch (uMsg) {
|
|
|
+ case WM_LBUTTONDOWN:
|
|
|
+ ((FlutterWindow *)pTrayIcon->getUserData())->onTrayIconLButtonDown();
|
|
|
+ break;
|
|
|
+ case WM_LBUTTONUP:
|
|
|
+ ((FlutterWindow *)pTrayIcon->getUserData())->onTrayIconLButtonUp();
|
|
|
+ break;
|
|
|
+ case WM_RBUTTONDOWN:
|
|
|
+ ((FlutterWindow *)pTrayIcon->getUserData())->onTrayIconRButtonDown();
|
|
|
+ break;
|
|
|
+ case WM_RBUTTONUP:
|
|
|
+ ((FlutterWindow *)pTrayIcon->getUserData())->onTrayIconRButtonUp();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+HICON getIconHandle(std::string iconPath)
|
|
|
+{
|
|
|
+ return static_cast<HICON>(LoadImage(NULL, (LPCWSTR)(Utf16FromUtf8(iconPath.c_str()).c_str()),
|
|
|
+ IMAGE_ICON, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE | LR_SHARED));
|
|
|
+}
|
|
|
+
|
|
|
+const flutter::EncodableValue* ValueOrNull(const flutter::EncodableMap& map, const char* key)
|
|
|
+{
|
|
|
+ auto it = map.find(flutter::EncodableValue(key));
|
|
|
+ if (it == map.end()) {
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+ return &(it->second);
|
|
|
+}
|