TaskbarWindow.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. /*
  2. * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "TaskbarWindow.h"
  7. #include "ClockWidget.h"
  8. #include "TaskbarButton.h"
  9. #include <AK/Debug.h>
  10. #include <LibCore/ConfigFile.h>
  11. #include <LibCore/StandardPaths.h>
  12. #include <LibDesktop/AppFile.h>
  13. #include <LibGUI/BoxLayout.h>
  14. #include <LibGUI/Button.h>
  15. #include <LibGUI/Desktop.h>
  16. #include <LibGUI/Frame.h>
  17. #include <LibGUI/Icon.h>
  18. #include <LibGUI/Menu.h>
  19. #include <LibGUI/Painter.h>
  20. #include <LibGUI/Window.h>
  21. #include <LibGUI/WindowManagerServerConnection.h>
  22. #include <LibGUI/WindowServerConnection.h>
  23. #include <LibGfx/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 { }
  31. private:
  32. TaskbarWidget() { }
  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_rect_change(GUI::Desktop::the().rect());
  54. auto& main_widget = set_main_widget<TaskbarWidget>();
  55. main_widget.set_layout<GUI::HorizontalBoxLayout>();
  56. main_widget.layout()->set_margins({ 3, 3, 3, 1 });
  57. m_start_button = GUI::Button::construct("Serenity");
  58. m_start_button->set_font(Gfx::FontDatabase::default_bold_font());
  59. m_start_button->set_icon_spacing(0);
  60. m_start_button->set_fixed_size(80, 22);
  61. auto app_icon = GUI::Icon::default_icon("ladyball");
  62. m_start_button->set_icon(app_icon.bitmap_for_size(16));
  63. m_start_button->set_menu(m_start_menu);
  64. main_widget.add_child(*m_start_button);
  65. create_quick_launch_bar();
  66. m_task_button_container = main_widget.add<GUI::Widget>();
  67. m_task_button_container->set_layout<GUI::HorizontalBoxLayout>();
  68. m_task_button_container->layout()->set_spacing(3);
  69. m_default_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window.png");
  70. m_applet_area_container = main_widget.add<GUI::Frame>();
  71. m_applet_area_container->set_frame_thickness(1);
  72. m_applet_area_container->set_frame_shape(Gfx::FrameShape::Box);
  73. m_applet_area_container->set_frame_shadow(Gfx::FrameShadow::Sunken);
  74. main_widget.add<Taskbar::ClockWidget>();
  75. }
  76. TaskbarWindow::~TaskbarWindow()
  77. {
  78. }
  79. void TaskbarWindow::create_quick_launch_bar()
  80. {
  81. auto& quick_launch_bar = main_widget()->add<GUI::Frame>();
  82. quick_launch_bar.set_layout<GUI::HorizontalBoxLayout>();
  83. quick_launch_bar.layout()->set_spacing(0);
  84. quick_launch_bar.layout()->set_margins({ 3, 0, 3, 0 });
  85. quick_launch_bar.set_frame_thickness(0);
  86. int total_width = 6;
  87. bool first = true;
  88. auto config = Core::ConfigFile::get_for_app("Taskbar");
  89. constexpr const char* quick_launch = "QuickLaunch";
  90. // FIXME: Core::ConfigFile does not keep the order of the entries.
  91. for (auto& name : config->keys(quick_launch)) {
  92. auto af_name = config->read_entry(quick_launch, name);
  93. auto af_path = String::formatted("{}/{}", Desktop::AppFile::APP_FILES_DIRECTORY, af_name);
  94. auto af = Desktop::AppFile::open(af_path);
  95. if (!af->is_valid())
  96. continue;
  97. auto app_executable = af->executable();
  98. const int button_size = 24;
  99. auto& button = quick_launch_bar.add<GUI::Button>();
  100. button.set_fixed_size(button_size, button_size);
  101. button.set_button_style(Gfx::ButtonStyle::Coolbar);
  102. button.set_icon(af->icon().bitmap_for_size(16));
  103. button.set_tooltip(af->name());
  104. button.on_click = [app_executable](auto) {
  105. pid_t pid = fork();
  106. if (pid < 0) {
  107. perror("fork");
  108. } else if (pid == 0) {
  109. if (chdir(Core::StandardPaths::home_directory().characters()) < 0) {
  110. perror("chdir");
  111. exit(1);
  112. }
  113. execl(app_executable.characters(), app_executable.characters(), nullptr);
  114. perror("execl");
  115. VERIFY_NOT_REACHED();
  116. } else {
  117. if (disown(pid) < 0)
  118. perror("disown");
  119. }
  120. };
  121. if (!first)
  122. total_width += quick_launch_bar.layout()->spacing();
  123. first = false;
  124. total_width += button_size;
  125. }
  126. quick_launch_bar.set_fixed_size(total_width, 24);
  127. }
  128. void TaskbarWindow::on_screen_rect_change(const Gfx::IntRect& rect)
  129. {
  130. Gfx::IntRect new_rect { rect.x(), rect.bottom() - taskbar_height() + 1, rect.width(), taskbar_height() };
  131. set_rect(new_rect);
  132. update_applet_area();
  133. }
  134. void TaskbarWindow::update_applet_area()
  135. {
  136. // NOTE: Widget layout is normally lazy, but here we have to force it right away so we can tell
  137. // WindowServer where to place the applet area window.
  138. if (!main_widget())
  139. return;
  140. main_widget()->do_layout();
  141. Gfx::IntRect new_rect { {}, m_applet_area_size };
  142. new_rect.center_within(m_applet_area_container->screen_relative_rect());
  143. GUI::WindowManagerServerConnection::the().send_sync<Messages::WindowManagerServer::SetAppletAreaPosition>(new_rect.location());
  144. }
  145. NonnullRefPtr<GUI::Button> TaskbarWindow::create_button(const WindowIdentifier& identifier)
  146. {
  147. auto& button = m_task_button_container->add<TaskbarButton>(identifier);
  148. button.set_min_size(20, 22);
  149. button.set_max_size(140, 22);
  150. button.set_text_alignment(Gfx::TextAlignment::CenterLeft);
  151. button.set_icon(*m_default_icon);
  152. return button;
  153. }
  154. void TaskbarWindow::add_window_button(::Window& window, const WindowIdentifier& identifier)
  155. {
  156. if (window.button())
  157. return;
  158. window.set_button(create_button(identifier));
  159. auto* button = window.button();
  160. button->on_click = [window = &window, identifier, button](auto) {
  161. // We need to look at the button's checked state here to figure
  162. // out if the application is active or not. That's because this
  163. // button's window may not actually be active when a modal window
  164. // is displayed, in which case window->is_active() would return
  165. // false because window is the modal window's owner (which is not
  166. // active)
  167. if (window->is_minimized() || !button->is_checked()) {
  168. GUI::WindowManagerServerConnection::the().post_message(Messages::WindowManagerServer::SetActiveWindow(identifier.client_id(), identifier.window_id()));
  169. } else {
  170. GUI::WindowManagerServerConnection::the().post_message(Messages::WindowManagerServer::SetWindowMinimized(identifier.client_id(), identifier.window_id(), true));
  171. }
  172. };
  173. }
  174. void TaskbarWindow::remove_window_button(::Window& window, bool was_removed)
  175. {
  176. auto* button = window.button();
  177. if (!button)
  178. return;
  179. if (!was_removed)
  180. static_cast<TaskbarButton*>(button)->clear_taskbar_rect();
  181. window.set_button(nullptr);
  182. button->remove_from_parent();
  183. }
  184. void TaskbarWindow::update_window_button(::Window& window, bool show_as_active)
  185. {
  186. auto* button = window.button();
  187. if (!button)
  188. return;
  189. button->set_text(window.title());
  190. button->set_tooltip(window.title());
  191. button->set_checked(show_as_active);
  192. }
  193. ::Window* TaskbarWindow::find_window_owner(::Window& window) const
  194. {
  195. if (!window.is_modal())
  196. return &window;
  197. ::Window* parent = nullptr;
  198. auto* current_window = &window;
  199. while (current_window) {
  200. parent = WindowList::the().find_parent(*current_window);
  201. if (!parent || !parent->is_modal())
  202. break;
  203. current_window = parent;
  204. }
  205. return parent;
  206. }
  207. void TaskbarWindow::wm_event(GUI::WMEvent& event)
  208. {
  209. WindowIdentifier identifier { event.client_id(), event.window_id() };
  210. switch (event.type()) {
  211. case GUI::Event::WM_WindowRemoved: {
  212. if constexpr (EVENT_DEBUG) {
  213. auto& removed_event = static_cast<GUI::WMWindowRemovedEvent&>(event);
  214. dbgln("WM_WindowRemoved: client_id={}, window_id={}",
  215. removed_event.client_id(),
  216. removed_event.window_id());
  217. }
  218. if (auto* window = WindowList::the().window(identifier))
  219. remove_window_button(*window, true);
  220. WindowList::the().remove_window(identifier);
  221. update();
  222. break;
  223. }
  224. case GUI::Event::WM_WindowRectChanged: {
  225. if constexpr (EVENT_DEBUG) {
  226. auto& changed_event = static_cast<GUI::WMWindowRectChangedEvent&>(event);
  227. dbgln("WM_WindowRectChanged: client_id={}, window_id={}, rect={}",
  228. changed_event.client_id(),
  229. changed_event.window_id(),
  230. changed_event.rect());
  231. }
  232. break;
  233. }
  234. case GUI::Event::WM_WindowIconBitmapChanged: {
  235. auto& changed_event = static_cast<GUI::WMWindowIconBitmapChangedEvent&>(event);
  236. if (auto* window = WindowList::the().window(identifier)) {
  237. if (window->button())
  238. window->button()->set_icon(changed_event.bitmap());
  239. }
  240. break;
  241. }
  242. case GUI::Event::WM_WindowStateChanged: {
  243. auto& changed_event = static_cast<GUI::WMWindowStateChangedEvent&>(event);
  244. if constexpr (EVENT_DEBUG) {
  245. dbgln("WM_WindowStateChanged: client_id={}, window_id={}, title={}, rect={}, is_active={}, is_minimized={}",
  246. changed_event.client_id(),
  247. changed_event.window_id(),
  248. changed_event.title(),
  249. changed_event.rect(),
  250. changed_event.is_active(),
  251. changed_event.is_minimized());
  252. }
  253. if (changed_event.window_type() != GUI::WindowType::Normal || changed_event.is_frameless()) {
  254. if (auto* window = WindowList::the().window(identifier))
  255. remove_window_button(*window, false);
  256. break;
  257. }
  258. auto& window = WindowList::the().ensure_window(identifier);
  259. window.set_parent_identifier({ changed_event.parent_client_id(), changed_event.parent_window_id() });
  260. if (!window.is_modal())
  261. add_window_button(window, identifier);
  262. else
  263. remove_window_button(window, false);
  264. window.set_title(changed_event.title());
  265. window.set_rect(changed_event.rect());
  266. window.set_modal(changed_event.is_modal());
  267. window.set_active(changed_event.is_active());
  268. window.set_minimized(changed_event.is_minimized());
  269. window.set_progress(changed_event.progress());
  270. auto* window_owner = find_window_owner(window);
  271. if (window_owner == &window) {
  272. update_window_button(window, window.is_active());
  273. } else if (window_owner) {
  274. // check the window owner's button if the modal's window button
  275. // would have been checked
  276. VERIFY(window.is_modal());
  277. update_window_button(*window_owner, window.is_active());
  278. }
  279. break;
  280. }
  281. case GUI::Event::WM_AppletAreaSizeChanged: {
  282. auto& changed_event = static_cast<GUI::WMAppletAreaSizeChangedEvent&>(event);
  283. m_applet_area_size = changed_event.size();
  284. m_applet_area_container->set_fixed_size(changed_event.size().width() + 8, 22);
  285. update_applet_area();
  286. break;
  287. }
  288. case GUI::Event::WM_SuperKeyPressed: {
  289. if (m_start_menu->is_visible()) {
  290. m_start_menu->dismiss();
  291. } else {
  292. m_start_menu->popup(m_start_button->screen_relative_rect().top_left());
  293. }
  294. break;
  295. }
  296. default:
  297. break;
  298. }
  299. }
  300. void TaskbarWindow::screen_rect_change_event(GUI::ScreenRectChangeEvent& event)
  301. {
  302. on_screen_rect_change(event.rect());
  303. }