WSWindowManager.cpp 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101
  1. #include "WSWindowManager.h"
  2. #include "WSCompositor.h"
  3. #include "WSEventLoop.h"
  4. #include "WSMenu.h"
  5. #include "WSMenuBar.h"
  6. #include "WSMenuItem.h"
  7. #include "WSScreen.h"
  8. #include "WSWindow.h"
  9. #include <AK/LogStream.h>
  10. #include <AK/StdLibExtras.h>
  11. #include <AK/Vector.h>
  12. #include <LibCore/CTimer.h>
  13. #include <LibDraw/CharacterBitmap.h>
  14. #include <LibDraw/Font.h>
  15. #include <LibDraw/PNGLoader.h>
  16. #include <LibDraw/Painter.h>
  17. #include <LibDraw/StylePainter.h>
  18. #include <WindowServer/WSAPITypes.h>
  19. #include <WindowServer/WSButton.h>
  20. #include <WindowServer/WSClientConnection.h>
  21. #include <WindowServer/WSCursor.h>
  22. #include <errno.h>
  23. #include <stdio.h>
  24. #include <time.h>
  25. #include <unistd.h>
  26. //#define DEBUG_COUNTERS
  27. //#define DEBUG_MENUS
  28. //#define RESIZE_DEBUG
  29. //#define DRAG_DEBUG
  30. //#define DOUBLECLICK_DEBUG
  31. static WSWindowManager* s_the;
  32. WSWindowManager& WSWindowManager::the()
  33. {
  34. ASSERT(s_the);
  35. return *s_the;
  36. }
  37. WSWindowManager::WSWindowManager()
  38. {
  39. s_the = this;
  40. reload_config(false);
  41. struct AppMenuItem {
  42. const char* binary_name;
  43. const char* description;
  44. const char* icon_path;
  45. };
  46. Vector<AppMenuItem> apps = {
  47. { "/bin/Terminal", "Open Terminal...", "/res/icons/16x16/app-terminal.png" },
  48. { "/bin/FileManager", "Open FileManager...", "/res/icons/16x16/filetype-folder.png" },
  49. { "/bin/SystemMonitor", "Open SystemMonitor...", "/res/icons/16x16/app-system-monitor.png" }
  50. };
  51. u8 system_menu_name[] = { 0xc3, 0xb8, 0 };
  52. m_system_menu = WSMenu::construct(nullptr, -1, String((const char*)system_menu_name));
  53. int appIndex = 1;
  54. for (const auto& app : apps) {
  55. m_system_menu->add_item(make<WSMenuItem>(*m_system_menu, appIndex++, app.description, String(), true, false, false, load_png(app.icon_path)));
  56. }
  57. m_system_menu->add_item(make<WSMenuItem>(*m_system_menu, WSMenuItem::Separator));
  58. m_system_menu->add_item(make<WSMenuItem>(*m_system_menu, 100, "Reload WM Config File"));
  59. m_system_menu->add_item(make<WSMenuItem>(*m_system_menu, WSMenuItem::Separator));
  60. m_system_menu->add_item(make<WSMenuItem>(*m_system_menu, 200, "About...", String(), true, false, false, load_png("/res/icons/16x16/ladybug.png")));
  61. m_system_menu->add_item(make<WSMenuItem>(*m_system_menu, WSMenuItem::Separator));
  62. m_system_menu->add_item(make<WSMenuItem>(*m_system_menu, 300, "Shutdown..."));
  63. m_system_menu->on_item_activation = [this, apps](WSMenuItem& item) {
  64. if (item.identifier() >= 1 && item.identifier() <= 1u + apps.size() - 1) {
  65. if (fork() == 0) {
  66. const auto& bin = apps[item.identifier() - 1].binary_name;
  67. execl(bin, bin, nullptr);
  68. ASSERT_NOT_REACHED();
  69. }
  70. }
  71. switch (item.identifier()) {
  72. case 100:
  73. reload_config(true);
  74. break;
  75. case 200:
  76. if (fork() == 0) {
  77. execl("/bin/About", "/bin/About", nullptr);
  78. ASSERT_NOT_REACHED();
  79. }
  80. return;
  81. case 300:
  82. if (fork() == 0) {
  83. execl("/bin/SystemDialog", "/bin/SystemDialog", "--shutdown", nullptr);
  84. ASSERT_NOT_REACHED();
  85. }
  86. return;
  87. }
  88. #ifdef DEBUG_MENUS
  89. dbg() << "WSMenu 1 item activated: " << item.text();
  90. #endif
  91. };
  92. // NOTE: This ensures that the system menu has the correct dimensions.
  93. set_current_menubar(nullptr);
  94. m_menu_manager.setup();
  95. invalidate();
  96. WSCompositor::the().compose();
  97. }
  98. WSWindowManager::~WSWindowManager()
  99. {
  100. }
  101. NonnullRefPtr<WSCursor> WSWindowManager::get_cursor(const String& name, const Point& hotspot)
  102. {
  103. auto path = m_wm_config->read_entry("Cursor", name, "/res/cursors/arrow.png");
  104. auto gb = GraphicsBitmap::load_from_file(path);
  105. if (gb)
  106. return WSCursor::create(*gb, hotspot);
  107. return WSCursor::create(*GraphicsBitmap::load_from_file("/res/cursors/arrow.png"));
  108. }
  109. NonnullRefPtr<WSCursor> WSWindowManager::get_cursor(const String& name)
  110. {
  111. auto path = m_wm_config->read_entry("Cursor", name, "/res/cursors/arrow.png");
  112. auto gb = GraphicsBitmap::load_from_file(path);
  113. if (gb)
  114. return WSCursor::create(*gb);
  115. return WSCursor::create(*GraphicsBitmap::load_from_file("/res/cursors/arrow.png"));
  116. }
  117. void WSWindowManager::reload_config(bool set_screen)
  118. {
  119. m_wm_config = CConfigFile::get_for_app("WindowManager");
  120. m_double_click_speed = m_wm_config->read_num_entry("Input", "DoubleClickSpeed", 250);
  121. if (set_screen)
  122. set_resolution(m_wm_config->read_num_entry("Screen", "Width", 1920),
  123. m_wm_config->read_num_entry("Screen", "Height", 1080));
  124. m_arrow_cursor = get_cursor("Arrow", { 2, 2 });
  125. m_hand_cursor = get_cursor("Hand", { 8, 4 });
  126. m_resize_horizontally_cursor = get_cursor("ResizeH");
  127. m_resize_vertically_cursor = get_cursor("ResizeV");
  128. m_resize_diagonally_tlbr_cursor = get_cursor("ResizeDTLBR");
  129. m_resize_diagonally_bltr_cursor = get_cursor("ResizeDBLTR");
  130. m_i_beam_cursor = get_cursor("IBeam");
  131. m_disallowed_cursor = get_cursor("Disallowed");
  132. m_move_cursor = get_cursor("Move");
  133. m_background_color = m_wm_config->read_color_entry("Colors", "Background", Color::Red);
  134. m_active_window_border_color = m_wm_config->read_color_entry("Colors", "ActiveWindowBorder", Color::Red);
  135. m_active_window_border_color2 = m_wm_config->read_color_entry("Colors", "ActiveWindowBorder2", Color::Red);
  136. m_active_window_title_color = m_wm_config->read_color_entry("Colors", "ActiveWindowTitle", Color::Red);
  137. m_inactive_window_border_color = m_wm_config->read_color_entry("Colors", "InactiveWindowBorder", Color::Red);
  138. m_inactive_window_border_color2 = m_wm_config->read_color_entry("Colors", "InactiveWindowBorder2", Color::Red);
  139. m_inactive_window_title_color = m_wm_config->read_color_entry("Colors", "InactiveWindowTitle", Color::Red);
  140. m_dragging_window_border_color = m_wm_config->read_color_entry("Colors", "DraggingWindowBorder", Color::Red);
  141. m_dragging_window_border_color2 = m_wm_config->read_color_entry("Colors", "DraggingWindowBorder2", Color::Red);
  142. m_dragging_window_title_color = m_wm_config->read_color_entry("Colors", "DraggingWindowTitle", Color::Red);
  143. m_highlight_window_border_color = m_wm_config->read_color_entry("Colors", "HighlightWindowBorder", Color::Red);
  144. m_highlight_window_border_color2 = m_wm_config->read_color_entry("Colors", "HighlightWindowBorder2", Color::Red);
  145. m_highlight_window_title_color = m_wm_config->read_color_entry("Colors", "HighlightWindowTitle", Color::Red);
  146. m_menu_selection_color = m_wm_config->read_color_entry("Colors", "MenuSelectionColor", Color::Red);
  147. }
  148. const Font& WSWindowManager::font() const
  149. {
  150. return Font::default_font();
  151. }
  152. const Font& WSWindowManager::window_title_font() const
  153. {
  154. return Font::default_bold_font();
  155. }
  156. const Font& WSWindowManager::menu_font() const
  157. {
  158. return Font::default_font();
  159. }
  160. const Font& WSWindowManager::app_menu_font() const
  161. {
  162. return Font::default_bold_font();
  163. }
  164. void WSWindowManager::set_resolution(int width, int height)
  165. {
  166. WSCompositor::the().set_resolution(width, height);
  167. m_menu_manager.set_needs_window_resize();
  168. WSClientConnection::for_each_client([&](WSClientConnection& client) {
  169. client.notify_about_new_screen_rect(WSScreen::the().rect());
  170. });
  171. if (m_wm_config) {
  172. dbg() << "Saving resolution: " << Size(width, height) << " to config file at " << m_wm_config->file_name();
  173. m_wm_config->write_num_entry("Screen", "Width", width);
  174. m_wm_config->write_num_entry("Screen", "Height", height);
  175. m_wm_config->sync();
  176. }
  177. }
  178. int WSWindowManager::menubar_menu_margin() const
  179. {
  180. return 16;
  181. }
  182. void WSWindowManager::set_current_menu(WSMenu* menu, bool is_submenu)
  183. {
  184. if (m_current_menu == menu)
  185. return;
  186. if (!is_submenu && m_current_menu)
  187. m_current_menu->close();
  188. if (menu)
  189. m_current_menu = menu->make_weak_ptr();
  190. if (!is_submenu) {
  191. m_menu_manager.open_menu_stack().clear();
  192. if (menu)
  193. m_menu_manager.open_menu_stack().append(menu->make_weak_ptr());
  194. } else {
  195. m_menu_manager.open_menu_stack().append(menu->make_weak_ptr());
  196. }
  197. }
  198. void WSWindowManager::set_current_menubar(WSMenuBar* menubar)
  199. {
  200. if (menubar)
  201. m_current_menubar = menubar->make_weak_ptr();
  202. else
  203. m_current_menubar = nullptr;
  204. #ifdef DEBUG_MENUS
  205. dbg() << "[WM] Current menubar is now " << menubar;
  206. #endif
  207. Point next_menu_location { menubar_menu_margin() / 2, 0 };
  208. int index = 0;
  209. for_each_active_menubar_menu([&](WSMenu& menu) {
  210. int text_width = index == 1 ? Font::default_bold_font().width(menu.name()) : font().width(menu.name());
  211. menu.set_rect_in_menubar({ next_menu_location.x() - menubar_menu_margin() / 2, 0, text_width + menubar_menu_margin(), menubar_rect().height() - 1 });
  212. menu.set_text_rect_in_menubar({ next_menu_location, { text_width, menubar_rect().height() } });
  213. next_menu_location.move_by(menu.rect_in_menubar().width(), 0);
  214. ++index;
  215. return true;
  216. });
  217. m_menu_manager.refresh();
  218. }
  219. void WSWindowManager::add_window(WSWindow& window)
  220. {
  221. m_windows_in_order.append(&window);
  222. if (window.is_fullscreen()) {
  223. CEventLoop::current().post_event(window, make<WSResizeEvent>(window.rect(), WSScreen::the().rect()));
  224. window.set_rect(WSScreen::the().rect());
  225. }
  226. set_active_window(&window);
  227. if (m_switcher.is_visible() && window.type() != WSWindowType::WindowSwitcher)
  228. m_switcher.refresh();
  229. if (window.listens_to_wm_events()) {
  230. for_each_window([&](WSWindow& other_window) {
  231. if (&window != &other_window) {
  232. tell_wm_listener_about_window(window, other_window);
  233. tell_wm_listener_about_window_icon(window, other_window);
  234. }
  235. return IterationDecision::Continue;
  236. });
  237. }
  238. tell_wm_listeners_window_state_changed(window);
  239. }
  240. void WSWindowManager::move_to_front_and_make_active(WSWindow& window)
  241. {
  242. if (window.is_blocked_by_modal_window())
  243. return;
  244. if (m_windows_in_order.tail() != &window)
  245. invalidate(window);
  246. m_windows_in_order.remove(&window);
  247. m_windows_in_order.append(&window);
  248. set_active_window(&window);
  249. }
  250. void WSWindowManager::remove_window(WSWindow& window)
  251. {
  252. invalidate(window);
  253. m_windows_in_order.remove(&window);
  254. if (window.is_active())
  255. pick_new_active_window();
  256. if (m_switcher.is_visible() && window.type() != WSWindowType::WindowSwitcher)
  257. m_switcher.refresh();
  258. for_each_window_listening_to_wm_events([&window](WSWindow& listener) {
  259. if (!(listener.wm_event_mask() & WSAPI_WMEventMask::WindowRemovals))
  260. return IterationDecision::Continue;
  261. if (window.client())
  262. CEventLoop::current().post_event(listener, make<WSWMWindowRemovedEvent>(window.client()->client_id(), window.window_id()));
  263. return IterationDecision::Continue;
  264. });
  265. }
  266. void WSWindowManager::tell_wm_listener_about_window(WSWindow& listener, WSWindow& window)
  267. {
  268. if (!(listener.wm_event_mask() & WSAPI_WMEventMask::WindowStateChanges))
  269. return;
  270. if (window.client())
  271. CEventLoop::current().post_event(listener, make<WSWMWindowStateChangedEvent>(window.client()->client_id(), window.window_id(), window.title(), window.rect(), window.is_active(), window.type(), window.is_minimized()));
  272. }
  273. void WSWindowManager::tell_wm_listener_about_window_rect(WSWindow& listener, WSWindow& window)
  274. {
  275. if (!(listener.wm_event_mask() & WSAPI_WMEventMask::WindowRectChanges))
  276. return;
  277. if (window.client())
  278. CEventLoop::current().post_event(listener, make<WSWMWindowRectChangedEvent>(window.client()->client_id(), window.window_id(), window.rect()));
  279. }
  280. void WSWindowManager::tell_wm_listener_about_window_icon(WSWindow& listener, WSWindow& window)
  281. {
  282. if (!(listener.wm_event_mask() & WSAPI_WMEventMask::WindowIconChanges))
  283. return;
  284. if (window.client() && window.icon().shared_buffer_id() != -1)
  285. CEventLoop::current().post_event(listener, make<WSWMWindowIconBitmapChangedEvent>(window.client()->client_id(), window.window_id(), window.icon().shared_buffer_id(), window.icon().size()));
  286. }
  287. void WSWindowManager::tell_wm_listeners_window_state_changed(WSWindow& window)
  288. {
  289. for_each_window_listening_to_wm_events([&](WSWindow& listener) {
  290. tell_wm_listener_about_window(listener, window);
  291. return IterationDecision::Continue;
  292. });
  293. }
  294. void WSWindowManager::tell_wm_listeners_window_icon_changed(WSWindow& window)
  295. {
  296. for_each_window_listening_to_wm_events([&](WSWindow& listener) {
  297. tell_wm_listener_about_window_icon(listener, window);
  298. return IterationDecision::Continue;
  299. });
  300. }
  301. void WSWindowManager::tell_wm_listeners_window_rect_changed(WSWindow& window)
  302. {
  303. for_each_window_listening_to_wm_events([&](WSWindow& listener) {
  304. tell_wm_listener_about_window_rect(listener, window);
  305. return IterationDecision::Continue;
  306. });
  307. }
  308. void WSWindowManager::notify_title_changed(WSWindow& window)
  309. {
  310. if (window.type() != WSWindowType::Normal)
  311. return;
  312. dbg() << "[WM] WSWindow{" << &window << "} title set to \"" << window.title() << '"';
  313. invalidate(window.frame().rect());
  314. if (m_switcher.is_visible())
  315. m_switcher.refresh();
  316. tell_wm_listeners_window_state_changed(window);
  317. }
  318. void WSWindowManager::notify_rect_changed(WSWindow& window, const Rect& old_rect, const Rect& new_rect)
  319. {
  320. UNUSED_PARAM(old_rect);
  321. UNUSED_PARAM(new_rect);
  322. #ifdef RESIZE_DEBUG
  323. dbg() << "[WM] WSWindow " << &window << " rect changed " << old_rect << " -> " << new_rect;
  324. #endif
  325. if (m_switcher.is_visible() && window.type() != WSWindowType::WindowSwitcher)
  326. m_switcher.refresh();
  327. tell_wm_listeners_window_rect_changed(window);
  328. }
  329. void WSWindowManager::notify_minimization_state_changed(WSWindow& window)
  330. {
  331. tell_wm_listeners_window_state_changed(window);
  332. if (window.is_active() && window.is_minimized())
  333. pick_new_active_window();
  334. }
  335. void WSWindowManager::pick_new_active_window()
  336. {
  337. for_each_visible_window_of_type_from_front_to_back(WSWindowType::Normal, [&](WSWindow& candidate) {
  338. set_active_window(&candidate);
  339. return IterationDecision::Break;
  340. });
  341. }
  342. void WSWindowManager::close_current_menu()
  343. {
  344. if (m_current_menu && m_current_menu->menu_window())
  345. m_current_menu->menu_window()->set_visible(false);
  346. m_current_menu = nullptr;
  347. for (auto& menu : m_menu_manager.open_menu_stack()) {
  348. if (menu && menu->menu_window())
  349. menu->menu_window()->set_visible(false);
  350. }
  351. m_menu_manager.open_menu_stack().clear();
  352. m_menu_manager.refresh();
  353. }
  354. void WSWindowManager::start_window_drag(WSWindow& window, const WSMouseEvent& event)
  355. {
  356. #ifdef DRAG_DEBUG
  357. dbg() << "[WM] Begin dragging WSWindow{" << &window << "}";
  358. #endif
  359. move_to_front_and_make_active(window);
  360. m_drag_window = window.make_weak_ptr();
  361. m_drag_origin = event.position();
  362. if (window.is_maximized()) {
  363. auto width_before_resize = window.width();
  364. window.set_maximized(false);
  365. window.move_to(m_drag_origin.x() - (window.width() * ((float)m_drag_origin.x() / width_before_resize)), m_drag_origin.y());
  366. }
  367. m_drag_window_origin = window.position();
  368. invalidate(window);
  369. }
  370. void WSWindowManager::start_window_resize(WSWindow& window, const Point& position, MouseButton button)
  371. {
  372. move_to_front_and_make_active(window);
  373. constexpr ResizeDirection direction_for_hot_area[3][3] = {
  374. { ResizeDirection::UpLeft, ResizeDirection::Up, ResizeDirection::UpRight },
  375. { ResizeDirection::Left, ResizeDirection::None, ResizeDirection::Right },
  376. { ResizeDirection::DownLeft, ResizeDirection::Down, ResizeDirection::DownRight },
  377. };
  378. Rect outer_rect = window.frame().rect();
  379. ASSERT(outer_rect.contains(position));
  380. int window_relative_x = position.x() - outer_rect.x();
  381. int window_relative_y = position.y() - outer_rect.y();
  382. int hot_area_row = min(2, window_relative_y / (outer_rect.height() / 3));
  383. int hot_area_column = min(2, window_relative_x / (outer_rect.width() / 3));
  384. m_resize_direction = direction_for_hot_area[hot_area_row][hot_area_column];
  385. if (m_resize_direction == ResizeDirection::None) {
  386. ASSERT(!m_resize_window);
  387. return;
  388. }
  389. #ifdef RESIZE_DEBUG
  390. dbg() << "[WM] Begin resizing WSWindow{" << &window << "}";
  391. #endif
  392. m_resizing_mouse_button = button;
  393. m_resize_window = window.make_weak_ptr();
  394. ;
  395. m_resize_origin = position;
  396. m_resize_window_original_rect = window.rect();
  397. invalidate(window);
  398. }
  399. void WSWindowManager::start_window_resize(WSWindow& window, const WSMouseEvent& event)
  400. {
  401. start_window_resize(window, event.position(), event.button());
  402. }
  403. bool WSWindowManager::process_ongoing_window_drag(WSMouseEvent& event, WSWindow*& hovered_window)
  404. {
  405. if (!m_drag_window)
  406. return false;
  407. if (event.type() == WSEvent::MouseUp && event.button() == MouseButton::Left) {
  408. #ifdef DRAG_DEBUG
  409. dbg() << "[WM] Finish dragging WSWindow{" << m_drag_window << "}";
  410. #endif
  411. invalidate(*m_drag_window);
  412. if (m_drag_window->rect().contains(event.position()))
  413. hovered_window = m_drag_window;
  414. if (m_drag_window->is_resizable()) {
  415. process_event_for_doubleclick(*m_drag_window, event);
  416. if (event.type() == WSEvent::MouseDoubleClick) {
  417. #if defined(DOUBLECLICK_DEBUG)
  418. dbg() << "[WM] Click up became doubleclick!";
  419. #endif
  420. m_drag_window->set_maximized(!m_drag_window->is_maximized());
  421. }
  422. }
  423. m_drag_window = nullptr;
  424. return true;
  425. }
  426. if (event.type() == WSEvent::MouseMove) {
  427. #ifdef DRAG_DEBUG
  428. dbg() << "[WM] Dragging, origin: " << m_drag_origin << ", now: " << event.position();
  429. #endif
  430. Point pos = m_drag_window_origin.translated(event.position() - m_drag_origin);
  431. m_drag_window->set_position_without_repaint(pos);
  432. if (m_drag_window->rect().contains(event.position()))
  433. hovered_window = m_drag_window;
  434. return true;
  435. }
  436. return false;
  437. }
  438. bool WSWindowManager::process_ongoing_window_resize(const WSMouseEvent& event, WSWindow*& hovered_window)
  439. {
  440. if (!m_resize_window)
  441. return false;
  442. if (event.type() == WSEvent::MouseUp && event.button() == m_resizing_mouse_button) {
  443. #ifdef RESIZE_DEBUG
  444. dbg() << "[WM] Finish resizing WSWindow{" << m_resize_window << "}";
  445. #endif
  446. CEventLoop::current().post_event(*m_resize_window, make<WSResizeEvent>(m_resize_window->rect(), m_resize_window->rect()));
  447. invalidate(*m_resize_window);
  448. if (m_resize_window->rect().contains(event.position()))
  449. hovered_window = m_resize_window;
  450. m_resize_window = nullptr;
  451. m_resizing_mouse_button = MouseButton::None;
  452. return true;
  453. }
  454. if (event.type() != WSEvent::MouseMove)
  455. return false;
  456. auto old_rect = m_resize_window->rect();
  457. int diff_x = event.x() - m_resize_origin.x();
  458. int diff_y = event.y() - m_resize_origin.y();
  459. int change_x = 0;
  460. int change_y = 0;
  461. int change_w = 0;
  462. int change_h = 0;
  463. switch (m_resize_direction) {
  464. case ResizeDirection::DownRight:
  465. change_w = diff_x;
  466. change_h = diff_y;
  467. break;
  468. case ResizeDirection::Right:
  469. change_w = diff_x;
  470. break;
  471. case ResizeDirection::UpRight:
  472. change_w = diff_x;
  473. change_y = diff_y;
  474. change_h = -diff_y;
  475. break;
  476. case ResizeDirection::Up:
  477. change_y = diff_y;
  478. change_h = -diff_y;
  479. break;
  480. case ResizeDirection::UpLeft:
  481. change_x = diff_x;
  482. change_w = -diff_x;
  483. change_y = diff_y;
  484. change_h = -diff_y;
  485. break;
  486. case ResizeDirection::Left:
  487. change_x = diff_x;
  488. change_w = -diff_x;
  489. break;
  490. case ResizeDirection::DownLeft:
  491. change_x = diff_x;
  492. change_w = -diff_x;
  493. change_h = diff_y;
  494. break;
  495. case ResizeDirection::Down:
  496. change_h = diff_y;
  497. break;
  498. default:
  499. ASSERT_NOT_REACHED();
  500. }
  501. auto new_rect = m_resize_window_original_rect;
  502. Size minimum_size { 50, 50 };
  503. new_rect.set_x(new_rect.x() + change_x);
  504. new_rect.set_y(new_rect.y() + change_y);
  505. new_rect.set_width(max(minimum_size.width(), new_rect.width() + change_w));
  506. new_rect.set_height(max(minimum_size.height(), new_rect.height() + change_h));
  507. if (!m_resize_window->size_increment().is_null()) {
  508. int horizontal_incs = (new_rect.width() - m_resize_window->base_size().width()) / m_resize_window->size_increment().width();
  509. new_rect.set_width(m_resize_window->base_size().width() + horizontal_incs * m_resize_window->size_increment().width());
  510. int vertical_incs = (new_rect.height() - m_resize_window->base_size().height()) / m_resize_window->size_increment().height();
  511. new_rect.set_height(m_resize_window->base_size().height() + vertical_incs * m_resize_window->size_increment().height());
  512. }
  513. if (new_rect.contains(event.position()))
  514. hovered_window = m_resize_window;
  515. if (m_resize_window->rect() == new_rect)
  516. return true;
  517. #ifdef RESIZE_DEBUG
  518. dbg() << "[WM] Resizing, original: " << m_resize_window_original_rect << ", now: " << new_rect;
  519. #endif
  520. m_resize_window->set_rect(new_rect);
  521. CEventLoop::current().post_event(*m_resize_window, make<WSResizeEvent>(old_rect, new_rect));
  522. return true;
  523. }
  524. void WSWindowManager::set_cursor_tracking_button(WSButton* button)
  525. {
  526. m_cursor_tracking_button = button ? button->make_weak_ptr() : nullptr;
  527. }
  528. auto WSWindowManager::DoubleClickInfo::metadata_for_button(MouseButton button) -> ClickMetadata&
  529. {
  530. switch (button) {
  531. case MouseButton::Left:
  532. return m_left;
  533. case MouseButton::Right:
  534. return m_right;
  535. case MouseButton::Middle:
  536. return m_middle;
  537. default:
  538. ASSERT_NOT_REACHED();
  539. }
  540. }
  541. // #define DOUBLECLICK_DEBUG
  542. void WSWindowManager::process_event_for_doubleclick(WSWindow& window, WSMouseEvent& event)
  543. {
  544. // We only care about button presses (because otherwise it's not a doubleclick, duh!)
  545. ASSERT(event.type() == WSEvent::MouseUp);
  546. if (&window != m_double_click_info.m_clicked_window) {
  547. // we either haven't clicked anywhere, or we haven't clicked on this
  548. // window. set the current click window, and reset the timers.
  549. #if defined(DOUBLECLICK_DEBUG)
  550. dbg() << "Initial mouseup on window " << &window << " (previous was " << m_double_click_info.m_clicked_window << ')';
  551. #endif
  552. m_double_click_info.m_clicked_window = window.make_weak_ptr();
  553. m_double_click_info.reset();
  554. }
  555. auto& metadata = m_double_click_info.metadata_for_button(event.button());
  556. // if the clock is invalid, we haven't clicked with this button on this
  557. // window yet, so there's nothing to do.
  558. if (!metadata.clock.is_valid()) {
  559. metadata.clock.start();
  560. } else {
  561. int elapsed_since_last_click = metadata.clock.elapsed();
  562. metadata.clock.start();
  563. if (elapsed_since_last_click < m_double_click_speed) {
  564. auto diff = event.position() - metadata.last_position;
  565. auto distance_travelled_squared = diff.x() * diff.x() + diff.y() * diff.y();
  566. if (distance_travelled_squared > (m_max_distance_for_double_click * m_max_distance_for_double_click)) {
  567. // too far; try again
  568. metadata.clock.start();
  569. } else {
  570. #if defined(DOUBLECLICK_DEBUG)
  571. dbg() << "Transforming MouseUp to MouseDoubleClick (" << elapsed_since_last_click << " < " << m_double_click_speed << ")!";
  572. #endif
  573. event = WSMouseEvent(WSEvent::MouseDoubleClick, event.position(), event.buttons(), event.button(), event.modifiers(), event.wheel_delta());
  574. // invalidate this now we've delivered a doubleclick, otherwise
  575. // tripleclick will deliver two doubleclick events (incorrectly).
  576. metadata.clock = {};
  577. }
  578. } else {
  579. // too slow; try again
  580. metadata.clock.start();
  581. }
  582. }
  583. metadata.last_position = event.position();
  584. }
  585. void WSWindowManager::deliver_mouse_event(WSWindow& window, WSMouseEvent& event)
  586. {
  587. window.dispatch_event(event);
  588. if (event.type() == WSEvent::MouseUp) {
  589. process_event_for_doubleclick(window, event);
  590. if (event.type() == WSEvent::MouseDoubleClick)
  591. window.dispatch_event(event);
  592. }
  593. }
  594. void WSWindowManager::process_mouse_event(WSMouseEvent& event, WSWindow*& hovered_window)
  595. {
  596. hovered_window = nullptr;
  597. if (process_ongoing_window_drag(event, hovered_window))
  598. return;
  599. if (process_ongoing_window_resize(event, hovered_window))
  600. return;
  601. if (m_cursor_tracking_button)
  602. return m_cursor_tracking_button->on_mouse_event(event.translated(-m_cursor_tracking_button->screen_rect().location()));
  603. // This is quite hackish, but it's how the WSButton hover effect is implemented.
  604. if (m_hovered_button && event.type() == WSEvent::MouseMove)
  605. m_hovered_button->on_mouse_event(event.translated(-m_hovered_button->screen_rect().location()));
  606. HashTable<WSWindow*> windows_who_received_mouse_event_due_to_cursor_tracking;
  607. for (auto* window = m_windows_in_order.tail(); window; window = window->prev()) {
  608. if (!window->global_cursor_tracking())
  609. continue;
  610. ASSERT(window->is_visible()); // Maybe this should be supported? Idk. Let's catch it and think about it later.
  611. ASSERT(!window->is_minimized()); // Maybe this should also be supported? Idk.
  612. windows_who_received_mouse_event_due_to_cursor_tracking.set(window);
  613. auto translated_event = event.translated(-window->position());
  614. deliver_mouse_event(*window, translated_event);
  615. }
  616. // FIXME: Now that the menubar has a dedicated window, is this special-casing really necessary?
  617. if (!active_window_is_modal() && menubar_rect().contains(event.position())) {
  618. m_menu_manager.dispatch_event(event);
  619. return;
  620. }
  621. if (m_current_menu && m_current_menu->menu_window()) {
  622. auto& window = *m_current_menu->menu_window();
  623. bool event_is_inside_current_menu = window.rect().contains(event.position());
  624. if (!event_is_inside_current_menu) {
  625. if (m_current_menu->hovered_item())
  626. m_current_menu->clear_hovered_item();
  627. if (event.type() == WSEvent::MouseDown || event.type() == WSEvent::MouseUp)
  628. close_current_menu();
  629. if (event.type() == WSEvent::MouseMove) {
  630. for (auto& menu : m_menu_manager.open_menu_stack()) {
  631. if (!menu)
  632. continue;
  633. if (!menu->menu_window()->rect().contains(event.position()))
  634. continue;
  635. hovered_window = menu->menu_window();
  636. auto translated_event = event.translated(-menu->menu_window()->position());
  637. deliver_mouse_event(*menu->menu_window(), translated_event);
  638. break;
  639. }
  640. }
  641. } else {
  642. hovered_window = &window;
  643. auto translated_event = event.translated(-window.position());
  644. deliver_mouse_event(window, translated_event);
  645. }
  646. return;
  647. }
  648. WSWindow* event_window_with_frame = nullptr;
  649. if (m_active_input_window) {
  650. // At this point, we have delivered the start of an input sequence to a
  651. // client application. We must keep delivering to that client
  652. // application until the input sequence is done.
  653. //
  654. // This prevents e.g. dragging on one window out of the bounds starting
  655. // a drag in that other unrelated window, and other silly shennanigans.
  656. if (!windows_who_received_mouse_event_due_to_cursor_tracking.contains(m_active_input_window)) {
  657. auto translated_event = event.translated(-m_active_input_window->position());
  658. deliver_mouse_event(*m_active_input_window, translated_event);
  659. windows_who_received_mouse_event_due_to_cursor_tracking.set(m_active_input_window.ptr());
  660. }
  661. if (event.type() == WSEvent::MouseUp && event.buttons() == 0) {
  662. m_active_input_window = nullptr;
  663. }
  664. for_each_visible_window_from_front_to_back([&](auto& window) {
  665. if (window.frame().rect().contains(event.position())) {
  666. hovered_window = &window;
  667. return IterationDecision::Break;
  668. }
  669. return IterationDecision::Continue;
  670. });
  671. } else {
  672. for_each_visible_window_from_front_to_back([&](WSWindow& window) {
  673. auto window_frame_rect = window.frame().rect();
  674. if (!window_frame_rect.contains(event.position()))
  675. return IterationDecision::Continue;
  676. if (&window != m_resize_candidate.ptr())
  677. clear_resize_candidate();
  678. // First check if we should initiate a drag or resize (Logo+LMB or Logo+RMB).
  679. // In those cases, the event is swallowed by the window manager.
  680. if (window.is_movable()) {
  681. if (!window.is_fullscreen() && m_keyboard_modifiers == Mod_Logo && event.type() == WSEvent::MouseDown && event.button() == MouseButton::Left) {
  682. hovered_window = &window;
  683. start_window_drag(window, event);
  684. return IterationDecision::Break;
  685. }
  686. if (window.is_resizable() && m_keyboard_modifiers == Mod_Logo && event.type() == WSEvent::MouseDown && event.button() == MouseButton::Right && !window.is_blocked_by_modal_window()) {
  687. hovered_window = &window;
  688. start_window_resize(window, event);
  689. return IterationDecision::Break;
  690. }
  691. }
  692. if (m_keyboard_modifiers == Mod_Logo && event.type() == WSEvent::MouseWheel) {
  693. float opacity_change = -event.wheel_delta() * 0.05f;
  694. float new_opacity = window.opacity() + opacity_change;
  695. if (new_opacity < 0.05f)
  696. new_opacity = 0.05f;
  697. if (new_opacity > 1.0f)
  698. new_opacity = 1.0f;
  699. window.set_opacity(new_opacity);
  700. window.invalidate();
  701. return IterationDecision::Break;
  702. }
  703. // Well okay, let's see if we're hitting the frame or the window inside the frame.
  704. if (window.rect().contains(event.position())) {
  705. if (window.type() == WSWindowType::Normal && event.type() == WSEvent::MouseDown)
  706. move_to_front_and_make_active(window);
  707. hovered_window = &window;
  708. if (!window.global_cursor_tracking() && !windows_who_received_mouse_event_due_to_cursor_tracking.contains(&window)) {
  709. auto translated_event = event.translated(-window.position());
  710. deliver_mouse_event(window, translated_event);
  711. if (event.type() == WSEvent::MouseDown) {
  712. m_active_input_window = window.make_weak_ptr();
  713. }
  714. }
  715. return IterationDecision::Break;
  716. }
  717. // We are hitting the frame, pass the event along to WSWindowFrame.
  718. window.frame().on_mouse_event(event.translated(-window_frame_rect.location()));
  719. event_window_with_frame = &window;
  720. return IterationDecision::Break;
  721. });
  722. }
  723. if (event_window_with_frame != m_resize_candidate.ptr())
  724. clear_resize_candidate();
  725. }
  726. void WSWindowManager::clear_resize_candidate()
  727. {
  728. if (m_resize_candidate)
  729. WSCompositor::the().invalidate_cursor();
  730. m_resize_candidate = nullptr;
  731. }
  732. bool WSWindowManager::any_opaque_window_contains_rect(const Rect& rect)
  733. {
  734. bool found_containing_window = false;
  735. for_each_window([&](WSWindow& window) {
  736. if (!window.is_visible())
  737. return IterationDecision::Continue;
  738. if (window.is_minimized())
  739. return IterationDecision::Continue;
  740. if (window.opacity() < 1.0f)
  741. return IterationDecision::Continue;
  742. if (window.has_alpha_channel()) {
  743. // FIXME: Just because the window has an alpha channel doesn't mean it's not opaque.
  744. // Maybe there's some way we could know this?
  745. return IterationDecision::Continue;
  746. }
  747. if (window.frame().rect().contains(rect)) {
  748. found_containing_window = true;
  749. return IterationDecision::Break;
  750. }
  751. return IterationDecision::Continue;
  752. });
  753. return found_containing_window;
  754. };
  755. bool WSWindowManager::any_opaque_window_above_this_one_contains_rect(const WSWindow& a_window, const Rect& rect)
  756. {
  757. bool found_containing_window = false;
  758. bool checking = false;
  759. for_each_visible_window_from_back_to_front([&](WSWindow& window) {
  760. if (&window == &a_window) {
  761. checking = true;
  762. return IterationDecision::Continue;
  763. }
  764. if (!checking)
  765. return IterationDecision::Continue;
  766. if (!window.is_visible())
  767. return IterationDecision::Continue;
  768. if (window.is_minimized())
  769. return IterationDecision::Continue;
  770. if (window.opacity() < 1.0f)
  771. return IterationDecision::Continue;
  772. if (window.has_alpha_channel())
  773. return IterationDecision::Continue;
  774. if (window.frame().rect().contains(rect)) {
  775. found_containing_window = true;
  776. return IterationDecision::Break;
  777. }
  778. return IterationDecision::Continue;
  779. });
  780. return found_containing_window;
  781. };
  782. Rect WSWindowManager::menubar_rect() const
  783. {
  784. if (active_fullscreen_window())
  785. return {};
  786. return { 0, 0, WSScreen::the().rect().width(), 18 };
  787. }
  788. void WSWindowManager::draw_window_switcher()
  789. {
  790. if (m_switcher.is_visible())
  791. m_switcher.draw();
  792. }
  793. void WSWindowManager::event(CEvent& event)
  794. {
  795. if (static_cast<WSEvent&>(event).is_mouse_event()) {
  796. WSWindow* hovered_window = nullptr;
  797. process_mouse_event(static_cast<WSMouseEvent&>(event), hovered_window);
  798. set_hovered_window(hovered_window);
  799. return;
  800. }
  801. if (static_cast<WSEvent&>(event).is_key_event()) {
  802. auto& key_event = static_cast<const WSKeyEvent&>(event);
  803. m_keyboard_modifiers = key_event.modifiers();
  804. if (key_event.type() == WSEvent::KeyDown && key_event.modifiers() == Mod_Logo && key_event.key() == Key_Tab)
  805. m_switcher.show();
  806. if (m_switcher.is_visible()) {
  807. m_switcher.on_key_event(key_event);
  808. return;
  809. }
  810. if (m_active_window)
  811. return m_active_window->dispatch_event(event);
  812. return;
  813. }
  814. CObject::event(event);
  815. }
  816. void WSWindowManager::set_highlight_window(WSWindow* window)
  817. {
  818. if (window == m_highlight_window)
  819. return;
  820. if (auto* previous_highlight_window = m_highlight_window.ptr())
  821. invalidate(*previous_highlight_window);
  822. m_highlight_window = window ? window->make_weak_ptr() : nullptr;
  823. if (m_highlight_window)
  824. invalidate(*m_highlight_window);
  825. }
  826. void WSWindowManager::set_active_window(WSWindow* window)
  827. {
  828. if (window && window->is_blocked_by_modal_window())
  829. return;
  830. if (window->type() != WSWindowType::Normal)
  831. return;
  832. if (window == m_active_window)
  833. return;
  834. auto* previously_active_window = m_active_window.ptr();
  835. if (previously_active_window) {
  836. CEventLoop::current().post_event(*previously_active_window, make<WSEvent>(WSEvent::WindowDeactivated));
  837. invalidate(*previously_active_window);
  838. }
  839. m_active_window = window->make_weak_ptr();
  840. if (m_active_window) {
  841. CEventLoop::current().post_event(*m_active_window, make<WSEvent>(WSEvent::WindowActivated));
  842. invalidate(*m_active_window);
  843. auto* client = window->client();
  844. ASSERT(client);
  845. set_current_menubar(client->app_menubar());
  846. if (previously_active_window)
  847. tell_wm_listeners_window_state_changed(*previously_active_window);
  848. tell_wm_listeners_window_state_changed(*m_active_window);
  849. }
  850. }
  851. void WSWindowManager::set_hovered_window(WSWindow* window)
  852. {
  853. if (m_hovered_window == window)
  854. return;
  855. if (m_hovered_window)
  856. CEventLoop::current().post_event(*m_hovered_window, make<WSEvent>(WSEvent::WindowLeft));
  857. m_hovered_window = window ? window->make_weak_ptr() : nullptr;
  858. if (m_hovered_window)
  859. CEventLoop::current().post_event(*m_hovered_window, make<WSEvent>(WSEvent::WindowEntered));
  860. }
  861. void WSWindowManager::invalidate()
  862. {
  863. WSCompositor::the().invalidate();
  864. }
  865. void WSWindowManager::invalidate(const Rect& rect)
  866. {
  867. WSCompositor::the().invalidate(rect);
  868. }
  869. void WSWindowManager::invalidate(const WSWindow& window)
  870. {
  871. invalidate(window.frame().rect());
  872. }
  873. void WSWindowManager::invalidate(const WSWindow& window, const Rect& rect)
  874. {
  875. if (rect.is_empty()) {
  876. invalidate(window);
  877. return;
  878. }
  879. auto outer_rect = window.frame().rect();
  880. auto inner_rect = rect;
  881. inner_rect.move_by(window.position());
  882. // FIXME: This seems slightly wrong; the inner rect shouldn't intersect the border part of the outer rect.
  883. inner_rect.intersect(outer_rect);
  884. invalidate(inner_rect);
  885. }
  886. void WSWindowManager::close_menu(WSMenu& menu)
  887. {
  888. if (current_menu() == &menu)
  889. close_current_menu();
  890. }
  891. void WSWindowManager::close_menubar(WSMenuBar& menubar)
  892. {
  893. if (current_menubar() == &menubar)
  894. set_current_menubar(nullptr);
  895. }
  896. const WSClientConnection* WSWindowManager::active_client() const
  897. {
  898. if (m_active_window)
  899. return m_active_window->client();
  900. return nullptr;
  901. }
  902. void WSWindowManager::notify_client_changed_app_menubar(WSClientConnection& client)
  903. {
  904. if (active_client() == &client)
  905. set_current_menubar(client.app_menubar());
  906. m_menu_manager.refresh();
  907. }
  908. const WSCursor& WSWindowManager::active_cursor() const
  909. {
  910. if (m_drag_window)
  911. return *m_move_cursor;
  912. if (m_resize_window || m_resize_candidate) {
  913. switch (m_resize_direction) {
  914. case ResizeDirection::Up:
  915. case ResizeDirection::Down:
  916. return *m_resize_vertically_cursor;
  917. case ResizeDirection::Left:
  918. case ResizeDirection::Right:
  919. return *m_resize_horizontally_cursor;
  920. case ResizeDirection::UpLeft:
  921. case ResizeDirection::DownRight:
  922. return *m_resize_diagonally_tlbr_cursor;
  923. case ResizeDirection::UpRight:
  924. case ResizeDirection::DownLeft:
  925. return *m_resize_diagonally_bltr_cursor;
  926. case ResizeDirection::None:
  927. break;
  928. }
  929. }
  930. if (m_hovered_window && m_hovered_window->override_cursor())
  931. return *m_hovered_window->override_cursor();
  932. return *m_arrow_cursor;
  933. }
  934. void WSWindowManager::set_hovered_button(WSButton* button)
  935. {
  936. m_hovered_button = button ? button->make_weak_ptr() : nullptr;
  937. }
  938. void WSWindowManager::set_resize_candidate(WSWindow& window, ResizeDirection direction)
  939. {
  940. m_resize_candidate = window.make_weak_ptr();
  941. m_resize_direction = direction;
  942. }
  943. Rect WSWindowManager::maximized_window_rect(const WSWindow& window) const
  944. {
  945. Rect rect = WSScreen::the().rect();
  946. // Subtract window title bar (leaving the border)
  947. rect.set_y(rect.y() + window.frame().title_bar_rect().height());
  948. rect.set_height(rect.height() - window.frame().title_bar_rect().height());
  949. // Subtract menu bar
  950. rect.set_y(rect.y() + menubar_rect().height());
  951. rect.set_height(rect.height() - menubar_rect().height());
  952. // Subtract taskbar window height if present
  953. const_cast<WSWindowManager*>(this)->for_each_visible_window_of_type_from_back_to_front(WSWindowType::Taskbar, [&rect](WSWindow& taskbar_window) {
  954. rect.set_height(rect.height() - taskbar_window.height());
  955. return IterationDecision::Break;
  956. });
  957. return rect;
  958. }