main.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /*
  2. * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "ShutdownDialog.h"
  7. #include "TaskbarWindow.h"
  8. #include <AK/Debug.h>
  9. #include <AK/LexicalPath.h>
  10. #include <AK/QuickSort.h>
  11. #include <LibCore/ConfigFile.h>
  12. #include <LibCore/DirIterator.h>
  13. #include <LibCore/EventLoop.h>
  14. #include <LibDesktop/AppFile.h>
  15. #include <LibGUI/ActionGroup.h>
  16. #include <LibGUI/Application.h>
  17. #include <LibGUI/Menu.h>
  18. #include <LibGUI/WindowManagerServerConnection.h>
  19. #include <LibGUI/WindowServerConnection.h>
  20. #include <WindowServer/Window.h>
  21. #include <serenity.h>
  22. #include <signal.h>
  23. #include <spawn.h>
  24. #include <stdio.h>
  25. #include <sys/wait.h>
  26. #include <unistd.h>
  27. static Vector<String> discover_apps_and_categories();
  28. static NonnullRefPtr<GUI::Menu> build_system_menu();
  29. int main(int argc, char** argv)
  30. {
  31. if (pledge("stdio recvfd sendfd proc exec rpath unix sigaction", nullptr) < 0) {
  32. perror("pledge");
  33. return 1;
  34. }
  35. auto app = GUI::Application::construct(argc, argv);
  36. app->event_loop().register_signal(SIGCHLD, [](int) {
  37. // Wait all available children
  38. while (waitpid(-1, nullptr, WNOHANG) > 0)
  39. ;
  40. });
  41. // We need to obtain the WM connection here as well before the pledge shortening.
  42. GUI::WindowManagerServerConnection::the();
  43. if (pledge("stdio recvfd sendfd proc exec rpath", nullptr) < 0) {
  44. perror("pledge");
  45. return 1;
  46. }
  47. auto menu = build_system_menu();
  48. menu->realize_menu_if_needed();
  49. auto window = TaskbarWindow::construct(move(menu));
  50. window->show();
  51. window->make_window_manager(
  52. WindowServer::WMEventMask::WindowStateChanges
  53. | WindowServer::WMEventMask::WindowRemovals
  54. | WindowServer::WMEventMask::WindowIconChanges
  55. | WindowServer::WMEventMask::VirtualDesktopChanges);
  56. return app->exec();
  57. }
  58. struct AppMetadata {
  59. String executable;
  60. String name;
  61. String category;
  62. };
  63. Vector<AppMetadata> g_apps;
  64. struct ThemeMetadata {
  65. String name;
  66. String path;
  67. };
  68. Color g_menu_selection_color;
  69. Vector<ThemeMetadata> g_themes;
  70. RefPtr<GUI::Menu> g_themes_menu;
  71. GUI::ActionGroup g_themes_group;
  72. Vector<String> discover_apps_and_categories()
  73. {
  74. HashTable<String> seen_app_categories;
  75. Desktop::AppFile::for_each([&](auto af) {
  76. if (access(af->executable().characters(), X_OK) == 0) {
  77. g_apps.append({ af->executable(), af->name(), af->category() });
  78. seen_app_categories.set(af->category());
  79. }
  80. });
  81. quick_sort(g_apps, [](auto& a, auto& b) { return a.name < b.name; });
  82. Vector<String> sorted_app_categories;
  83. for (auto& category : seen_app_categories) {
  84. sorted_app_categories.append(category);
  85. }
  86. quick_sort(sorted_app_categories);
  87. return sorted_app_categories;
  88. }
  89. NonnullRefPtr<GUI::Menu> build_system_menu()
  90. {
  91. const Vector<String> sorted_app_categories = discover_apps_and_categories();
  92. auto system_menu = GUI::Menu::construct("\xE2\x9A\xA1"); // HIGH VOLTAGE SIGN
  93. system_menu->add_action(GUI::Action::create("About SerenityOS", Gfx::Bitmap::load_from_file("/res/icons/16x16/ladyball.png"), [](auto&) {
  94. pid_t child_pid;
  95. const char* argv[] = { "/bin/About", nullptr };
  96. if ((errno = posix_spawn(&child_pid, "/bin/About", nullptr, nullptr, const_cast<char**>(argv), environ))) {
  97. perror("posix_spawn");
  98. } else {
  99. if (disown(child_pid) < 0)
  100. perror("disown");
  101. }
  102. }));
  103. system_menu->add_separator();
  104. // First we construct all the necessary app category submenus.
  105. auto category_icons = Core::ConfigFile::open("/res/icons/SystemMenu.ini");
  106. HashMap<String, NonnullRefPtr<GUI::Menu>> app_category_menus;
  107. Function<void(String const&)> create_category_menu;
  108. create_category_menu = [&](String const& category) {
  109. if (app_category_menus.contains(category))
  110. return;
  111. String parent_category, child_category = category;
  112. for (ssize_t i = category.length() - 1; i >= 0; i--) {
  113. if (category[i] == '/') {
  114. parent_category = category.substring(0, i);
  115. child_category = category.substring(i + 1);
  116. }
  117. }
  118. GUI::Menu* parent_menu;
  119. if (parent_category.is_empty()) {
  120. parent_menu = system_menu;
  121. } else {
  122. parent_menu = app_category_menus.get(parent_category).value();
  123. if (!parent_menu) {
  124. create_category_menu(parent_category);
  125. parent_menu = app_category_menus.get(parent_category).value();
  126. VERIFY(parent_menu);
  127. }
  128. }
  129. auto& category_menu = parent_menu->add_submenu(child_category);
  130. auto category_icon_path = category_icons->read_entry("16x16", category);
  131. if (!category_icon_path.is_empty()) {
  132. auto icon = Gfx::Bitmap::load_from_file(category_icon_path);
  133. category_menu.set_icon(icon);
  134. }
  135. app_category_menus.set(category, category_menu);
  136. };
  137. for (const auto& category : sorted_app_categories)
  138. create_category_menu(category);
  139. // Then we create and insert all the app menu items into the right place.
  140. int app_identifier = 0;
  141. for (const auto& app : g_apps) {
  142. auto icon = GUI::FileIconProvider::icon_for_executable(app.executable).bitmap_for_size(16);
  143. if constexpr (SYSTEM_MENU_DEBUG) {
  144. if (icon)
  145. dbgln("App {} has icon with size {}", app.name, icon->size());
  146. }
  147. auto parent_menu = app_category_menus.get(app.category).value_or(system_menu.ptr());
  148. parent_menu->add_action(GUI::Action::create(app.name, icon, [app_identifier](auto&) {
  149. dbgln("Activated app with ID {}", app_identifier);
  150. const auto& bin = g_apps[app_identifier].executable;
  151. pid_t child_pid;
  152. const char* argv[] = { bin.characters(), nullptr };
  153. if ((errno = posix_spawn(&child_pid, bin.characters(), nullptr, nullptr, const_cast<char**>(argv), environ))) {
  154. perror("posix_spawn");
  155. } else {
  156. if (disown(child_pid) < 0)
  157. perror("disown");
  158. }
  159. }));
  160. ++app_identifier;
  161. }
  162. system_menu->add_separator();
  163. g_themes_group.set_exclusive(true);
  164. g_themes_group.set_unchecking_allowed(false);
  165. g_themes_menu = &system_menu->add_submenu("Themes");
  166. g_themes_menu->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/themes.png"));
  167. {
  168. Core::DirIterator dt("/res/themes", Core::DirIterator::SkipDots);
  169. while (dt.has_next()) {
  170. auto theme_name = dt.next_path();
  171. auto theme_path = String::formatted("/res/themes/{}", theme_name);
  172. g_themes.append({ LexicalPath::title(theme_name), theme_path });
  173. }
  174. quick_sort(g_themes, [](auto& a, auto& b) { return a.name < b.name; });
  175. }
  176. auto current_theme_name = GUI::WindowServerConnection::the().get_system_theme();
  177. {
  178. int theme_identifier = 0;
  179. for (auto& theme : g_themes) {
  180. auto action = GUI::Action::create_checkable(theme.name, [theme_identifier](auto&) {
  181. auto& theme = g_themes[theme_identifier];
  182. dbgln("Theme switched to {} at path {}", theme.name, theme.path);
  183. auto success = GUI::WindowServerConnection::the().set_system_theme(theme.path, theme.name);
  184. VERIFY(success);
  185. });
  186. if (theme.name == current_theme_name)
  187. action->set_checked(true);
  188. g_themes_group.add_action(action);
  189. g_themes_menu->add_action(action);
  190. ++theme_identifier;
  191. }
  192. }
  193. system_menu->add_separator();
  194. system_menu->add_action(GUI::Action::create("Help", Gfx::Bitmap::load_from_file("/res/icons/16x16/app-help.png"), [](auto&) {
  195. pid_t child_pid;
  196. const char* argv[] = { "/bin/Help", nullptr };
  197. if ((errno = posix_spawn(&child_pid, "/bin/Help", nullptr, nullptr, const_cast<char**>(argv), environ))) {
  198. perror("posix_spawn");
  199. } else {
  200. if (disown(child_pid) < 0)
  201. perror("disown");
  202. }
  203. }));
  204. system_menu->add_action(GUI::Action::create("Run...", Gfx::Bitmap::load_from_file("/res/icons/16x16/app-run.png"), [](auto&) {
  205. pid_t child_pid;
  206. const char* argv[] = { "/bin/Run", nullptr };
  207. if ((errno = posix_spawn(&child_pid, "/bin/Run", nullptr, nullptr, const_cast<char**>(argv), environ))) {
  208. perror("posix_spawn");
  209. } else {
  210. if (disown(child_pid) < 0)
  211. perror("disown");
  212. }
  213. }));
  214. system_menu->add_separator();
  215. system_menu->add_action(GUI::Action::create("Exit...", Gfx::Bitmap::load_from_file("/res/icons/16x16/power.png"), [](auto&) {
  216. auto command = ShutdownDialog::show();
  217. if (command.size() == 0)
  218. return;
  219. pid_t child_pid;
  220. if ((errno = posix_spawn(&child_pid, command[0], nullptr, nullptr, const_cast<char**>(command.data()), environ))) {
  221. perror("posix_spawn");
  222. } else {
  223. if (disown(child_pid) < 0)
  224. perror("disown");
  225. }
  226. }));
  227. return system_menu;
  228. }