flutter_window.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. #include "flutter_window.h"
  2. #include "utils.h"
  3. #include "file_info.h"
  4. #include "file_icon.h"
  5. #include <optional>
  6. #include "flutter/generated_plugin_registrant.h"
  7. void trayMessageHandler(TrayIcon *, UINT);
  8. HICON getIconHandle(std::string);
  9. const flutter::EncodableValue* ValueOrNull(const flutter::EncodableMap& map, const char* key);
  10. static std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>> appChannel;
  11. static std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>> trayChannel;
  12. FlutterWindow::FlutterWindow(const flutter::DartProject& project)
  13. : project_(project) {}
  14. FlutterWindow::~FlutterWindow() {}
  15. bool FlutterWindow::OnCreate() {
  16. if (!Win32Window::OnCreate()) {
  17. return false;
  18. }
  19. RECT frame = GetClientArea();
  20. // The size here must match the window dimensions to avoid unnecessary surface
  21. // creation / destruction in the startup path.
  22. flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
  23. frame.right - frame.left, frame.bottom - frame.top, project_);
  24. // Ensure that basic setup of the controller was successful.
  25. if (!flutter_controller_->engine() || !flutter_controller_->view()) {
  26. return false;
  27. }
  28. RegisterPlugins(flutter_controller_->engine());
  29. SetChildContent(flutter_controller_->view()->GetNativeWindow());
  30. // register channel
  31. registerMethodChannel(flutter_controller_->engine());
  32. // setup trayicon
  33. m_tray.setUserData(this);
  34. m_tray.setListener(trayMessageHandler);
  35. flutter_controller_->engine()->SetNextFrameCallback([&]() {
  36. this->Show();
  37. });
  38. // Flutter can complete the first frame before the "show window" callback is
  39. // registered. The following call ensures a frame is pending to ensure the
  40. // window is shown. It is a no-op if the first frame hasn't completed yet.
  41. flutter_controller_->ForceRedraw();
  42. return true;
  43. }
  44. void FlutterWindow::OnDestroy() {
  45. m_tray.setVisible(false);
  46. if (flutter_controller_) {
  47. flutter_controller_ = nullptr;
  48. }
  49. Win32Window::OnDestroy();
  50. }
  51. void FlutterWindow::SetMinimumSize(SIZE size) {
  52. min_window_size_ = size;
  53. }
  54. LRESULT
  55. FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
  56. WPARAM const wparam,
  57. LPARAM const lparam) noexcept {
  58. // my custom message handler
  59. std::optional<LRESULT> r = handleMessage(hwnd, message, wparam, lparam);
  60. if (r) {
  61. return *r;
  62. }
  63. // Give Flutter, including plugins, an opportunity to handle window messages.
  64. if (flutter_controller_) {
  65. std::optional<LRESULT> result =
  66. flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
  67. lparam);
  68. if (result) {
  69. return *result;
  70. }
  71. }
  72. switch (message) {
  73. case WM_FONTCHANGE:
  74. flutter_controller_->engine()->ReloadSystemFonts();
  75. break;
  76. case WM_GETMINMAXINFO:
  77. if (min_window_size_.cx > 0 && min_window_size_.cy > 0) {
  78. LPMINMAXINFO info = reinterpret_cast<LPMINMAXINFO>(lparam);
  79. info->ptMinTrackSize.x = min_window_size_.cx;
  80. info->ptMinTrackSize.y = min_window_size_.cy;
  81. }
  82. break;
  83. }
  84. return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
  85. }
  86. void FlutterWindow::registerMethodChannel(flutter::FlutterEngine* engine)
  87. {
  88. appChannel = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
  89. engine->messenger(), "app.nomo/app",
  90. &flutter::StandardMethodCodec::GetInstance());
  91. appChannel->SetMethodCallHandler([this](const auto& call, auto result) {
  92. handleAppMethodCall(call, std::move(result));
  93. });
  94. trayChannel = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
  95. engine->messenger(), "app.nomo/tray",
  96. &flutter::StandardMethodCodec::GetInstance());
  97. trayChannel->SetMethodCallHandler([this](const auto& call, auto result) {
  98. handleTrayMethodCall(call, std::move(result));
  99. });
  100. }
  101. void FlutterWindow::handleAppMethodCall(
  102. const flutter::MethodCall<flutter::EncodableValue>& method_call,
  103. std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
  104. {
  105. if (method_call.method_name().compare("activeWindow") == 0) {
  106. onActiveWindow();
  107. result->Success(flutter::EncodableValue(true));
  108. //
  109. } else if (method_call.method_name().compare("hideWindow") == 0) {
  110. onHideWindow();
  111. result->Success(flutter::EncodableValue(true));
  112. //
  113. } else if (method_call.method_name().compare("toggleWindow") == 0) {
  114. onToggleWindow();
  115. result->Success(flutter::EncodableValue(true));
  116. //
  117. } else if (method_call.method_name().compare("destroyWindow") == 0) {
  118. Destroy();
  119. result->Success(flutter::EncodableValue(true));
  120. //
  121. } else if (method_call.method_name().compare("extractFileIcon") == 0) {
  122. extractFileIcon(method_call, std::move(result));
  123. //
  124. } else if (method_call.method_name().compare("getFileDescription") == 0) {
  125. getFileDescription(method_call, std::move(result));
  126. //
  127. } else {
  128. result->NotImplemented();
  129. }
  130. }
  131. void FlutterWindow::handleTrayMethodCall(
  132. const flutter::MethodCall<flutter::EncodableValue>& method_call,
  133. std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
  134. {
  135. if (method_call.method_name().compare("setSystemTray") == 0) {
  136. setSystemTray(method_call, std::move(result));
  137. //
  138. } else if (method_call.method_name().compare("resetSystemTray") == 0) {
  139. resetSystemTray(method_call, std::move(result));
  140. //
  141. } else if (method_call.method_name().compare("setContextMenu") == 0) {
  142. setContextMenu(method_call, std::move(result));
  143. //
  144. } else if (method_call.method_name().compare("popupContextMenu") == 0) {
  145. popUpContextMenu(method_call, std::move(result));
  146. //
  147. } else {
  148. result->NotImplemented();
  149. }
  150. }
  151. std::optional<LRESULT> FlutterWindow::handleMessage(HWND window, UINT const message,
  152. WPARAM const wparam, LPARAM const lparam)
  153. {
  154. if (message == WM_CLOSE) {
  155. if (m_tray.isVisible()) {
  156. onHideWindow();
  157. } else {
  158. Destroy();
  159. }
  160. return std::make_optional<LRESULT>(0);
  161. }
  162. if (message == WM_COMMAND) {
  163. flutter::EncodableMap eventData = flutter::EncodableMap();
  164. eventData[flutter::EncodableValue("id")] = flutter::EncodableValue((int)wparam);
  165. trayChannel->InvokeMethod("onTrayMenuItemClick",
  166. std::make_unique<flutter::EncodableValue>(eventData));
  167. }
  168. return std::nullopt;
  169. }
  170. void FlutterWindow::onActiveWindow()
  171. {
  172. bool isVisible = (::GetWindowLong(GetHandle(), GWL_STYLE) & WS_VISIBLE) != 0;
  173. if (isVisible) {
  174. WINDOWPLACEMENT windowPlacement;
  175. GetWindowPlacement(GetHandle(), &windowPlacement);
  176. if (windowPlacement.showCmd == SW_SHOWMINIMIZED) {
  177. PostMessage(GetHandle(), WM_SYSCOMMAND, SC_RESTORE, 0);
  178. }
  179. } else {
  180. onShowWindow();
  181. }
  182. }
  183. void FlutterWindow::onToggleWindow()
  184. {
  185. bool isVisible = (::GetWindowLong(GetHandle(), GWL_STYLE) & WS_VISIBLE) != 0;
  186. if (isVisible) {
  187. WINDOWPLACEMENT windowPlacement;
  188. GetWindowPlacement(GetHandle(), &windowPlacement);
  189. if (windowPlacement.showCmd == SW_SHOWMINIMIZED) {
  190. PostMessage(GetHandle(), WM_SYSCOMMAND, SC_RESTORE, 0);
  191. } else {
  192. onHideWindow();
  193. }
  194. } else {
  195. onShowWindow();
  196. }
  197. }
  198. void FlutterWindow::onShowWindow()
  199. {
  200. ShowWindow(GetHandle(), SW_SHOW);
  201. SetForegroundWindow(GetHandle());
  202. auto args = std::make_unique<flutter::EncodableValue>(nullptr);
  203. appChannel->InvokeMethod("onWindowShow", std::move(args));
  204. }
  205. void FlutterWindow::onHideWindow()
  206. {
  207. ShowWindow(GetHandle(), SW_HIDE);
  208. auto args = std::make_unique<flutter::EncodableValue>(nullptr);
  209. appChannel->InvokeMethod("onWindowHide", std::move(args));
  210. }
  211. void FlutterWindow::onTrayIconLButtonDown()
  212. {
  213. auto args = std::make_unique<flutter::EncodableValue>(nullptr);
  214. trayChannel->InvokeMethod("onTrayIconLButtonDown", std::move(args));
  215. }
  216. void FlutterWindow::onTrayIconLButtonUp()
  217. {
  218. auto args = std::make_unique<flutter::EncodableValue>(nullptr);
  219. trayChannel->InvokeMethod("onTrayIconLButtonUp", std::move(args));
  220. }
  221. void FlutterWindow::onTrayIconRButtonDown()
  222. {
  223. auto args = std::make_unique<flutter::EncodableValue>(nullptr);
  224. trayChannel->InvokeMethod("onTrayIconRButtonDown", std::move(args));
  225. }
  226. void FlutterWindow::onTrayIconRButtonUp()
  227. {
  228. auto args = std::make_unique<flutter::EncodableValue>(nullptr);
  229. trayChannel->InvokeMethod("onTrayIconRButtonUp", std::move(args));
  230. }
  231. void FlutterWindow::setSystemTray(
  232. const flutter::MethodCall<flutter::EncodableValue>& method_call,
  233. std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
  234. {
  235. const flutter::EncodableMap& args =
  236. std::get<flutter::EncodableMap>(*method_call.arguments());
  237. std::string tooltip = std::get<std::string>(args.at(flutter::EncodableValue("tooltip")));
  238. std::string icon = std::get<std::string>(args.at(flutter::EncodableValue("icon")));
  239. m_tray.setName((LPCWSTR)(Utf16FromUtf8(tooltip.c_str()).c_str()));
  240. m_tray.setIcon(getIconHandle(icon));
  241. m_tray.setVisible(true);
  242. result->Success(flutter::EncodableValue(true));
  243. }
  244. void FlutterWindow::resetSystemTray(
  245. const flutter::MethodCall<flutter::EncodableValue>& method_call,
  246. std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
  247. {
  248. m_tray.setVisible(false);
  249. result->Success(flutter::EncodableValue(true));
  250. }
  251. void FlutterWindow::setContextMenu(
  252. const flutter::MethodCall<flutter::EncodableValue>& method_call,
  253. std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
  254. {
  255. const flutter::EncodableMap& args =
  256. std::get<flutter::EncodableMap>(*method_call.arguments());
  257. m_menuHandle = CreatePopupMenu();
  258. createMenu(m_menuHandle, std::get<flutter::EncodableMap>(
  259. args.at(flutter::EncodableValue("menu"))));
  260. result->Success(flutter::EncodableValue(true));
  261. }
  262. void FlutterWindow::popUpContextMenu(
  263. const flutter::MethodCall<flutter::EncodableValue>& method_call,
  264. std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
  265. {
  266. POINT pt;
  267. if (GetCursorPos(&pt)) {
  268. ::SetForegroundWindow(GetHandle());
  269. TrackPopupMenu(m_menuHandle, TPM_BOTTOMALIGN | TPM_LEFTALIGN,
  270. pt.x, pt.y, 0, GetHandle(), NULL);
  271. }
  272. result->Success(flutter::EncodableValue(true));
  273. }
  274. void FlutterWindow::extractFileIcon(
  275. const flutter::MethodCall<flutter::EncodableValue>& method_call,
  276. std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
  277. {
  278. const flutter::EncodableMap& args =
  279. std::get<flutter::EncodableMap>(*method_call.arguments());
  280. std::string fileName = std::get<std::string>(args.at(flutter::EncodableValue("fileName")));
  281. std::string iconName = std::get<std::string>(args.at(flutter::EncodableValue("iconName")));
  282. auto ret = SaveIcon(fileName, iconName, 32, 0);
  283. result->Success(flutter::EncodableValue(ret));
  284. }
  285. void FlutterWindow::getFileDescription(
  286. const flutter::MethodCall<flutter::EncodableValue>& method_call,
  287. std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
  288. {
  289. const flutter::EncodableMap& args =
  290. std::get<flutter::EncodableMap>(*method_call.arguments());
  291. std::wstring fileName = Utf16FromUtf8(std::get<std::string>(args.at(flutter::EncodableValue("fileName"))).c_str());
  292. std::wstring description;
  293. FileInfoUtils::GetFileDescription(fileName, description);
  294. std::wcout << fileName << " " << description << std::endl;
  295. result->Success(flutter::EncodableValue(utf8_encode(description)));
  296. }
  297. void FlutterWindow::createMenu(HMENU menu, flutter::EncodableMap args) {
  298. flutter::EncodableList items = std::get<flutter::EncodableList>(
  299. args.at(flutter::EncodableValue("items")));
  300. int count = GetMenuItemCount(menu);
  301. for (int i = 0; i < count; i++) {
  302. // always remove at 0 because they shift every time
  303. RemoveMenu(menu, 0, MF_BYPOSITION);
  304. }
  305. for (flutter::EncodableValue item_value : items) {
  306. flutter::EncodableMap item_map =
  307. std::get<flutter::EncodableMap>(item_value);
  308. int id = std::get<int>(item_map.at(flutter::EncodableValue("id")));
  309. std::string type =
  310. std::get<std::string>(item_map.at(flutter::EncodableValue("type")));
  311. std::string label =
  312. std::get<std::string>(item_map.at(flutter::EncodableValue("label")));
  313. auto* checked = std::get_if<bool>(ValueOrNull(item_map, "checked"));
  314. bool disabled =
  315. std::get<bool>(item_map.at(flutter::EncodableValue("disabled")));
  316. UINT_PTR item_id = id;
  317. UINT uFlags = MF_STRING;
  318. if (disabled) {
  319. uFlags |= MF_GRAYED;
  320. }
  321. if (type.compare("separator") == 0) {
  322. AppendMenuW(menu, MF_SEPARATOR, item_id, NULL);
  323. } else {
  324. if (type.compare("checkbox") == 0) {
  325. if (checked == nullptr) {
  326. // skip
  327. } else {
  328. uFlags |= (*checked == true ? MF_CHECKED : MF_UNCHECKED);
  329. }
  330. } else if (type.compare("submenu") == 0) {
  331. uFlags |= MF_POPUP;
  332. HMENU sub_menu = ::CreatePopupMenu();
  333. createMenu(sub_menu, std::get<flutter::EncodableMap>(item_map.at(
  334. flutter::EncodableValue("submenu"))));
  335. item_id = reinterpret_cast<UINT_PTR>(sub_menu);
  336. }
  337. AppendMenuW(menu, uFlags, item_id, (LPCWSTR)(Utf16FromUtf8(label.c_str()).c_str()));
  338. }
  339. }
  340. }
  341. void trayMessageHandler(TrayIcon *pTrayIcon, UINT uMsg)
  342. {
  343. switch (uMsg) {
  344. case WM_LBUTTONDOWN:
  345. ((FlutterWindow *)pTrayIcon->getUserData())->onTrayIconLButtonDown();
  346. break;
  347. case WM_LBUTTONUP:
  348. ((FlutterWindow *)pTrayIcon->getUserData())->onTrayIconLButtonUp();
  349. break;
  350. case WM_RBUTTONDOWN:
  351. ((FlutterWindow *)pTrayIcon->getUserData())->onTrayIconRButtonDown();
  352. break;
  353. case WM_RBUTTONUP:
  354. ((FlutterWindow *)pTrayIcon->getUserData())->onTrayIconRButtonUp();
  355. break;
  356. }
  357. }
  358. HICON getIconHandle(std::string iconPath)
  359. {
  360. return static_cast<HICON>(LoadImage(NULL, (LPCWSTR)(Utf16FromUtf8(iconPath.c_str()).c_str()),
  361. IMAGE_ICON, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE | LR_SHARED));
  362. }
  363. const flutter::EncodableValue* ValueOrNull(const flutter::EncodableMap& map, const char* key)
  364. {
  365. auto it = map.find(flutter::EncodableValue(key));
  366. if (it == map.end()) {
  367. return nullptr;
  368. }
  369. return &(it->second);
  370. }