main.cpp 10.0 KB

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