main.cpp 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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. return app->exec();
  56. }
  57. struct AppMetadata {
  58. String executable;
  59. String name;
  60. String category;
  61. };
  62. Vector<AppMetadata> g_apps;
  63. struct ThemeMetadata {
  64. String name;
  65. String path;
  66. };
  67. Color g_menu_selection_color;
  68. Vector<ThemeMetadata> g_themes;
  69. RefPtr<GUI::Menu> g_themes_menu;
  70. GUI::ActionGroup g_themes_group;
  71. Vector<String> discover_apps_and_categories()
  72. {
  73. HashTable<String> seen_app_categories;
  74. Desktop::AppFile::for_each([&](auto af) {
  75. g_apps.append({ af->executable(), af->name(), af->category() });
  76. seen_app_categories.set(af->category());
  77. });
  78. quick_sort(g_apps, [](auto& a, auto& b) { return a.name < b.name; });
  79. Vector<String> sorted_app_categories;
  80. for (auto& category : seen_app_categories) {
  81. sorted_app_categories.append(category);
  82. }
  83. quick_sort(sorted_app_categories);
  84. return sorted_app_categories;
  85. }
  86. NonnullRefPtr<GUI::Menu> build_system_menu()
  87. {
  88. const Vector<String> sorted_app_categories = discover_apps_and_categories();
  89. auto system_menu = GUI::Menu::construct("\xE2\x9A\xA1"); // HIGH VOLTAGE SIGN
  90. system_menu->add_action(GUI::Action::create("About SerenityOS", Gfx::Bitmap::load_from_file("/res/icons/16x16/ladyball.png"), [](auto&) {
  91. pid_t child_pid;
  92. const char* argv[] = { "/bin/About", nullptr };
  93. if ((errno = posix_spawn(&child_pid, "/bin/About", nullptr, nullptr, const_cast<char**>(argv), environ))) {
  94. perror("posix_spawn");
  95. } else {
  96. if (disown(child_pid) < 0)
  97. perror("disown");
  98. }
  99. }));
  100. system_menu->add_separator();
  101. // First we construct all the necessary app category submenus.
  102. HashMap<String, NonnullRefPtr<GUI::Menu>> app_category_menus;
  103. auto category_icons = Core::ConfigFile::open("/res/icons/SystemMenu.ini");
  104. for (const auto& category : sorted_app_categories) {
  105. if (app_category_menus.contains(category))
  106. continue;
  107. auto& category_menu = system_menu->add_submenu(category);
  108. auto category_icon_path = category_icons->read_entry("16x16", category);
  109. if (!category_icon_path.is_empty()) {
  110. auto icon = Gfx::Bitmap::load_from_file(category_icon_path);
  111. category_menu.set_icon(icon);
  112. }
  113. app_category_menus.set(category, category_menu);
  114. }
  115. // Then we create and insert all the app menu items into the right place.
  116. int app_identifier = 0;
  117. for (const auto& app : g_apps) {
  118. auto icon = GUI::FileIconProvider::icon_for_executable(app.executable).bitmap_for_size(16);
  119. if constexpr (SYSTEM_MENU_DEBUG) {
  120. if (icon)
  121. dbgln("App {} has icon with size {}", app.name, icon->size());
  122. }
  123. auto parent_menu = app_category_menus.get(app.category).value_or(system_menu.ptr());
  124. parent_menu->add_action(GUI::Action::create(app.name, icon, [app_identifier](auto&) {
  125. dbgln("Activated app with ID {}", app_identifier);
  126. const auto& bin = g_apps[app_identifier].executable;
  127. pid_t child_pid;
  128. const char* argv[] = { bin.characters(), nullptr };
  129. if ((errno = posix_spawn(&child_pid, bin.characters(), nullptr, nullptr, const_cast<char**>(argv), environ))) {
  130. perror("posix_spawn");
  131. } else {
  132. if (disown(child_pid) < 0)
  133. perror("disown");
  134. }
  135. }));
  136. ++app_identifier;
  137. }
  138. system_menu->add_separator();
  139. g_themes_group.set_exclusive(true);
  140. g_themes_group.set_unchecking_allowed(false);
  141. g_themes_menu = &system_menu->add_submenu("Themes");
  142. g_themes_menu->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/themes.png"));
  143. {
  144. Core::DirIterator dt("/res/themes", Core::DirIterator::SkipDots);
  145. while (dt.has_next()) {
  146. auto theme_name = dt.next_path();
  147. auto theme_path = String::formatted("/res/themes/{}", theme_name);
  148. g_themes.append({ LexicalPath(theme_name).title(), theme_path });
  149. }
  150. quick_sort(g_themes, [](auto& a, auto& b) { return a.name < b.name; });
  151. }
  152. auto current_theme_name = GUI::WindowServerConnection::the().get_system_theme();
  153. {
  154. int theme_identifier = 0;
  155. for (auto& theme : g_themes) {
  156. auto action = GUI::Action::create_checkable(theme.name, [theme_identifier](auto&) {
  157. auto& theme = g_themes[theme_identifier];
  158. dbgln("Theme switched to {} at path {}", theme.name, theme.path);
  159. auto success = GUI::WindowServerConnection::the().set_system_theme(theme.path, theme.name);
  160. VERIFY(success);
  161. });
  162. if (theme.name == current_theme_name)
  163. action->set_checked(true);
  164. g_themes_group.add_action(action);
  165. g_themes_menu->add_action(action);
  166. ++theme_identifier;
  167. }
  168. }
  169. system_menu->add_separator();
  170. system_menu->add_action(GUI::Action::create("Help", Gfx::Bitmap::load_from_file("/res/icons/16x16/app-help.png"), [](auto&) {
  171. pid_t child_pid;
  172. const char* argv[] = { "/bin/Help", nullptr };
  173. if ((errno = posix_spawn(&child_pid, "/bin/Help", nullptr, nullptr, const_cast<char**>(argv), environ))) {
  174. perror("posix_spawn");
  175. } else {
  176. if (disown(child_pid) < 0)
  177. perror("disown");
  178. }
  179. }));
  180. system_menu->add_action(GUI::Action::create("Run...", Gfx::Bitmap::load_from_file("/res/icons/16x16/app-run.png"), [](auto&) {
  181. pid_t child_pid;
  182. const char* argv[] = { "/bin/Run", nullptr };
  183. if ((errno = posix_spawn(&child_pid, "/bin/Run", nullptr, nullptr, const_cast<char**>(argv), environ))) {
  184. perror("posix_spawn");
  185. } else {
  186. if (disown(child_pid) < 0)
  187. perror("disown");
  188. }
  189. }));
  190. system_menu->add_separator();
  191. system_menu->add_action(GUI::Action::create("Exit...", Gfx::Bitmap::load_from_file("/res/icons/16x16/power.png"), [](auto&) {
  192. auto command = ShutdownDialog::show();
  193. if (command.size() == 0)
  194. return;
  195. pid_t child_pid;
  196. if ((errno = posix_spawn(&child_pid, command[0], nullptr, nullptr, const_cast<char**>(command.data()), environ))) {
  197. perror("posix_spawn");
  198. } else {
  199. if (disown(child_pid) < 0)
  200. perror("disown");
  201. }
  202. }));
  203. return system_menu;
  204. }