TaskbarWindow.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. /*
  2. * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021, Spencer Dixon <spencercdixon@gmail.com>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include "TaskbarWindow.h"
  8. #include "ClockWidget.h"
  9. #include "QuickLaunchWidget.h"
  10. #include "TaskbarButton.h"
  11. #include <AK/Debug.h>
  12. #include <LibCore/StandardPaths.h>
  13. #include <LibGUI/BoxLayout.h>
  14. #include <LibGUI/Button.h>
  15. #include <LibGUI/ConnectionToWindowManagerServer.h>
  16. #include <LibGUI/ConnectionToWindowServer.h>
  17. #include <LibGUI/Desktop.h>
  18. #include <LibGUI/Frame.h>
  19. #include <LibGUI/Icon.h>
  20. #include <LibGUI/Menu.h>
  21. #include <LibGUI/Painter.h>
  22. #include <LibGUI/Window.h>
  23. #include <LibGfx/Font/FontDatabase.h>
  24. #include <LibGfx/Palette.h>
  25. #include <serenity.h>
  26. #include <stdio.h>
  27. class TaskbarWidget final : public GUI::Widget {
  28. C_OBJECT(TaskbarWidget);
  29. public:
  30. virtual ~TaskbarWidget() override = default;
  31. private:
  32. TaskbarWidget() = default;
  33. virtual void paint_event(GUI::PaintEvent& event) override
  34. {
  35. GUI::Painter painter(*this);
  36. painter.add_clip_rect(event.rect());
  37. painter.fill_rect(rect(), palette().button());
  38. painter.draw_line({ 0, 1 }, { width() - 1, 1 }, palette().threed_highlight());
  39. }
  40. virtual void did_layout() override
  41. {
  42. WindowList::the().for_each_window([&](auto& window) {
  43. if (auto* button = window.button())
  44. static_cast<TaskbarButton*>(button)->update_taskbar_rect();
  45. });
  46. }
  47. };
  48. TaskbarWindow::TaskbarWindow(NonnullRefPtr<GUI::Menu> start_menu)
  49. : m_start_menu(move(start_menu))
  50. {
  51. set_window_type(GUI::WindowType::Taskbar);
  52. set_title("Taskbar");
  53. on_screen_rects_change(GUI::Desktop::the().rects(), GUI::Desktop::the().main_screen_index());
  54. auto& main_widget = set_main_widget<TaskbarWidget>();
  55. main_widget.set_layout<GUI::HorizontalBoxLayout>();
  56. main_widget.layout()->set_margins({ 2, 3, 0, 3 });
  57. m_start_button = GUI::Button::construct("Serenity");
  58. set_start_button_font(Gfx::FontDatabase::default_font().bold_variant());
  59. m_start_button->set_icon_spacing(0);
  60. auto app_icon = GUI::Icon::default_icon("ladyball"sv);
  61. m_start_button->set_icon(app_icon.bitmap_for_size(16));
  62. m_start_button->set_menu(m_start_menu);
  63. main_widget.add_child(*m_start_button);
  64. main_widget.add<Taskbar::QuickLaunchWidget>();
  65. m_task_button_container = main_widget.add<GUI::Widget>();
  66. m_task_button_container->set_layout<GUI::HorizontalBoxLayout>();
  67. m_task_button_container->layout()->set_spacing(3);
  68. m_default_icon = Gfx::Bitmap::try_load_from_file("/res/icons/16x16/window.png"sv).release_value_but_fixme_should_propagate_errors();
  69. m_applet_area_container = main_widget.add<GUI::Frame>();
  70. m_applet_area_container->set_frame_thickness(1);
  71. m_applet_area_container->set_frame_shape(Gfx::FrameShape::Box);
  72. m_applet_area_container->set_frame_shadow(Gfx::FrameShadow::Sunken);
  73. m_clock_widget = main_widget.add<Taskbar::ClockWidget>();
  74. m_show_desktop_button = GUI::Button::construct();
  75. m_show_desktop_button->set_tooltip("Show Desktop");
  76. m_show_desktop_button->set_icon(GUI::Icon::default_icon("desktop"sv).bitmap_for_size(16));
  77. m_show_desktop_button->set_button_style(Gfx::ButtonStyle::Coolbar);
  78. m_show_desktop_button->set_fixed_size(24, 24);
  79. m_show_desktop_button->on_click = TaskbarWindow::show_desktop_button_clicked;
  80. main_widget.add_child(*m_show_desktop_button);
  81. auto af_path = String::formatted("{}/{}", Desktop::AppFile::APP_FILES_DIRECTORY, "Assistant.af");
  82. m_assistant_app_file = Desktop::AppFile::open(af_path);
  83. }
  84. void TaskbarWindow::config_string_did_change(String const& domain, String const& group, String const& key, String const& value)
  85. {
  86. if (domain == "Taskbar" && group == "Clock" && key == "TimeFormat") {
  87. m_clock_widget->update_format(value);
  88. update_applet_area();
  89. }
  90. }
  91. void TaskbarWindow::show_desktop_button_clicked(unsigned)
  92. {
  93. toggle_show_desktop();
  94. }
  95. void TaskbarWindow::toggle_show_desktop()
  96. {
  97. GUI::ConnectionToWindowManagerServer::the().async_toggle_show_desktop();
  98. }
  99. void TaskbarWindow::on_screen_rects_change(Vector<Gfx::IntRect, 4> const& rects, size_t main_screen_index)
  100. {
  101. auto const& rect = rects[main_screen_index];
  102. Gfx::IntRect new_rect { rect.x(), rect.bottom() - taskbar_height() + 1, rect.width(), taskbar_height() };
  103. set_rect(new_rect);
  104. update_applet_area();
  105. }
  106. void TaskbarWindow::update_applet_area()
  107. {
  108. // NOTE: Widget layout is normally lazy, but here we have to force it right away so we can tell
  109. // WindowServer where to place the applet area window.
  110. if (!main_widget())
  111. return;
  112. main_widget()->do_layout();
  113. auto new_rect = Gfx::IntRect({}, m_applet_area_size).centered_within(m_applet_area_container->screen_relative_rect());
  114. GUI::ConnectionToWindowManagerServer::the().async_set_applet_area_position(new_rect.location());
  115. }
  116. NonnullRefPtr<GUI::Button> TaskbarWindow::create_button(WindowIdentifier const& identifier)
  117. {
  118. auto& button = m_task_button_container->add<TaskbarButton>(identifier);
  119. button.set_min_size(20, 21);
  120. button.set_max_size(140, 21);
  121. button.set_text_alignment(Gfx::TextAlignment::CenterLeft);
  122. button.set_icon(*m_default_icon);
  123. return button;
  124. }
  125. void TaskbarWindow::add_window_button(::Window& window, WindowIdentifier const& identifier)
  126. {
  127. if (window.button())
  128. return;
  129. window.set_button(create_button(identifier));
  130. auto* button = window.button();
  131. button->on_click = [window = &window, identifier](auto) {
  132. if (window->is_minimized() || !window->is_active())
  133. GUI::ConnectionToWindowManagerServer::the().async_set_active_window(identifier.client_id(), identifier.window_id());
  134. else if (!window->is_blocked())
  135. GUI::ConnectionToWindowManagerServer::the().async_set_window_minimized(identifier.client_id(), identifier.window_id(), true);
  136. };
  137. }
  138. void TaskbarWindow::remove_window_button(::Window& window, bool was_removed)
  139. {
  140. auto* button = window.button();
  141. if (!button)
  142. return;
  143. if (!was_removed)
  144. static_cast<TaskbarButton*>(button)->clear_taskbar_rect();
  145. window.set_button(nullptr);
  146. button->remove_from_parent();
  147. }
  148. void TaskbarWindow::update_window_button(::Window& window, bool show_as_active)
  149. {
  150. auto* button = window.button();
  151. if (!button)
  152. return;
  153. button->set_text(window.title());
  154. button->set_tooltip(window.title());
  155. button->set_checked(show_as_active);
  156. button->set_visible(is_window_on_current_workspace(window));
  157. }
  158. void TaskbarWindow::event(Core::Event& event)
  159. {
  160. switch (event.type()) {
  161. case GUI::Event::MouseDown: {
  162. // If the cursor is at the edge/corner of the screen but technically not within the start button (or other taskbar buttons),
  163. // we adjust it so that the nearest button ends up being clicked anyways.
  164. auto& mouse_event = static_cast<GUI::MouseEvent&>(event);
  165. int const ADJUSTMENT = 4;
  166. auto adjusted_x = AK::clamp(mouse_event.x(), ADJUSTMENT, width() - ADJUSTMENT);
  167. auto adjusted_y = AK::min(mouse_event.y(), height() - ADJUSTMENT);
  168. Gfx::IntPoint adjusted_point = { adjusted_x, adjusted_y };
  169. if (adjusted_point != mouse_event.position()) {
  170. GUI::ConnectionToWindowServer::the().async_set_global_cursor_position(position() + adjusted_point);
  171. GUI::MouseEvent adjusted_event = { (GUI::Event::Type)mouse_event.type(), adjusted_point, mouse_event.buttons(), mouse_event.button(), mouse_event.modifiers(), mouse_event.wheel_delta_x(), mouse_event.wheel_delta_y(), mouse_event.wheel_raw_delta_x(), mouse_event.wheel_raw_delta_y() };
  172. Window::event(adjusted_event);
  173. return;
  174. }
  175. break;
  176. }
  177. case GUI::Event::FontsChange:
  178. set_start_button_font(Gfx::FontDatabase::default_font().bold_variant());
  179. break;
  180. }
  181. Window::event(event);
  182. }
  183. void TaskbarWindow::wm_event(GUI::WMEvent& event)
  184. {
  185. WindowIdentifier identifier { event.client_id(), event.window_id() };
  186. switch (event.type()) {
  187. case GUI::Event::WM_WindowRemoved: {
  188. if constexpr (EVENT_DEBUG) {
  189. auto& removed_event = static_cast<GUI::WMWindowRemovedEvent&>(event);
  190. dbgln("WM_WindowRemoved: client_id={}, window_id={}",
  191. removed_event.client_id(),
  192. removed_event.window_id());
  193. }
  194. if (auto* window = WindowList::the().window(identifier))
  195. remove_window_button(*window, true);
  196. WindowList::the().remove_window(identifier);
  197. update();
  198. break;
  199. }
  200. case GUI::Event::WM_WindowRectChanged: {
  201. if constexpr (EVENT_DEBUG) {
  202. auto& changed_event = static_cast<GUI::WMWindowRectChangedEvent&>(event);
  203. dbgln("WM_WindowRectChanged: client_id={}, window_id={}, rect={}",
  204. changed_event.client_id(),
  205. changed_event.window_id(),
  206. changed_event.rect());
  207. }
  208. break;
  209. }
  210. case GUI::Event::WM_WindowIconBitmapChanged: {
  211. auto& changed_event = static_cast<GUI::WMWindowIconBitmapChangedEvent&>(event);
  212. if (auto* window = WindowList::the().window(identifier)) {
  213. if (window->button()) {
  214. auto icon = changed_event.bitmap();
  215. if (icon->height() != taskbar_icon_size() || icon->width() != taskbar_icon_size()) {
  216. auto sw = taskbar_icon_size() / (float)icon->width();
  217. auto sh = taskbar_icon_size() / (float)icon->height();
  218. auto scaled_bitmap_or_error = icon->scaled(sw, sh);
  219. if (scaled_bitmap_or_error.is_error())
  220. window->button()->set_icon(nullptr);
  221. else
  222. window->button()->set_icon(scaled_bitmap_or_error.release_value());
  223. } else {
  224. window->button()->set_icon(icon);
  225. }
  226. }
  227. }
  228. break;
  229. }
  230. case GUI::Event::WM_WindowStateChanged: {
  231. auto& changed_event = static_cast<GUI::WMWindowStateChangedEvent&>(event);
  232. if constexpr (EVENT_DEBUG) {
  233. dbgln("WM_WindowStateChanged: client_id={}, window_id={}, title={}, rect={}, is_active={}, is_blocked={}, is_minimized={}",
  234. changed_event.client_id(),
  235. changed_event.window_id(),
  236. changed_event.title(),
  237. changed_event.rect(),
  238. changed_event.is_active(),
  239. changed_event.is_blocked(),
  240. changed_event.is_minimized());
  241. }
  242. if (changed_event.window_type() != GUI::WindowType::Normal || changed_event.is_frameless()) {
  243. if (auto* window = WindowList::the().window(identifier))
  244. remove_window_button(*window, false);
  245. break;
  246. }
  247. auto& window = WindowList::the().ensure_window(identifier);
  248. window.set_title(changed_event.title());
  249. window.set_rect(changed_event.rect());
  250. window.set_active(changed_event.is_active());
  251. window.set_blocked(changed_event.is_blocked());
  252. window.set_minimized(changed_event.is_minimized());
  253. window.set_progress(changed_event.progress());
  254. window.set_workspace(changed_event.workspace_row(), changed_event.workspace_column());
  255. add_window_button(window, identifier);
  256. update_window_button(window, window.is_active());
  257. break;
  258. }
  259. case GUI::Event::WM_AppletAreaSizeChanged: {
  260. auto& changed_event = static_cast<GUI::WMAppletAreaSizeChangedEvent&>(event);
  261. m_applet_area_size = changed_event.size();
  262. m_applet_area_container->set_fixed_size(changed_event.size().width() + 8, 21);
  263. update_applet_area();
  264. break;
  265. }
  266. case GUI::Event::WM_SuperKeyPressed: {
  267. if (m_start_menu->is_visible()) {
  268. m_start_menu->dismiss();
  269. } else {
  270. m_start_menu->popup(m_start_button->screen_relative_rect().top_left());
  271. }
  272. break;
  273. }
  274. case GUI::Event::WM_SuperSpaceKeyPressed: {
  275. if (!m_assistant_app_file->spawn())
  276. warnln("failed to spawn 'Assistant' when requested via Super+Space");
  277. break;
  278. }
  279. case GUI::Event::WM_SuperDKeyPressed: {
  280. toggle_show_desktop();
  281. break;
  282. }
  283. case GUI::Event::WM_SuperDigitKeyPressed: {
  284. auto& digit_event = static_cast<GUI::WMSuperDigitKeyPressedEvent&>(event);
  285. auto index = digit_event.digit() != 0 ? digit_event.digit() - 1 : 9;
  286. for (auto& widget : m_task_button_container->child_widgets()) {
  287. // NOTE: The button might be invisible depending on the current workspace
  288. if (!widget.is_visible())
  289. continue;
  290. if (index == 0) {
  291. static_cast<TaskbarButton&>(widget).click();
  292. break;
  293. }
  294. --index;
  295. }
  296. break;
  297. }
  298. case GUI::Event::WM_WorkspaceChanged: {
  299. auto& changed_event = static_cast<GUI::WMWorkspaceChangedEvent&>(event);
  300. workspace_change_event(changed_event.current_row(), changed_event.current_column());
  301. break;
  302. }
  303. default:
  304. break;
  305. }
  306. }
  307. void TaskbarWindow::screen_rects_change_event(GUI::ScreenRectsChangeEvent& event)
  308. {
  309. on_screen_rects_change(event.rects(), event.main_screen_index());
  310. }
  311. bool TaskbarWindow::is_window_on_current_workspace(::Window& window) const
  312. {
  313. return window.workspace_row() == m_current_workspace_row && window.workspace_column() == m_current_workspace_column;
  314. }
  315. void TaskbarWindow::workspace_change_event(unsigned current_row, unsigned current_column)
  316. {
  317. m_current_workspace_row = current_row;
  318. m_current_workspace_column = current_column;
  319. WindowList::the().for_each_window([&](auto& window) {
  320. if (auto* button = window.button())
  321. button->set_visible(is_window_on_current_workspace(window));
  322. });
  323. }
  324. void TaskbarWindow::set_start_button_font(Gfx::Font const& font)
  325. {
  326. m_start_button->set_font(font);
  327. m_start_button->set_fixed_size(font.width(m_start_button->text()) + 30, 21);
  328. }