RunWindow.cpp 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. /*
  2. * Copyright (c) 2021, Nick Vella <nick@nxk.io>
  3. * Copyright (c) 2022, the SerenityOS developers.
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include "RunWindow.h"
  8. #include <AK/LexicalPath.h>
  9. #include <AK/URL.h>
  10. #include <Applications/Run/RunGML.h>
  11. #include <LibCore/StandardPaths.h>
  12. #include <LibDesktop/Launcher.h>
  13. #include <LibFileSystem/FileSystem.h>
  14. #include <LibGUI/Button.h>
  15. #include <LibGUI/Event.h>
  16. #include <LibGUI/FilePicker.h>
  17. #include <LibGUI/Icon.h>
  18. #include <LibGUI/ImageWidget.h>
  19. #include <LibGUI/MessageBox.h>
  20. #include <LibGUI/Widget.h>
  21. #include <spawn.h>
  22. #include <stdio.h>
  23. #include <string.h>
  24. #include <sys/wait.h>
  25. #include <unistd.h>
  26. RunWindow::RunWindow()
  27. : m_path_history()
  28. , m_path_history_model(GUI::ItemListModel<DeprecatedString>::create(m_path_history))
  29. {
  30. // FIXME: Handle failure to load history somehow.
  31. (void)load_history();
  32. auto app_icon = GUI::Icon::default_icon("app-run"sv);
  33. set_title("Run");
  34. set_icon(app_icon.bitmap_for_size(16));
  35. resize(345, 100);
  36. set_resizable(false);
  37. set_minimizable(false);
  38. auto main_widget = set_main_widget<GUI::Widget>().release_value_but_fixme_should_propagate_errors();
  39. main_widget->load_from_gml(run_gml).release_value_but_fixme_should_propagate_errors();
  40. m_icon_image_widget = *main_widget->find_descendant_of_type_named<GUI::ImageWidget>("icon");
  41. m_icon_image_widget->set_bitmap(app_icon.bitmap_for_size(32));
  42. m_path_combo_box = *main_widget->find_descendant_of_type_named<GUI::ComboBox>("path");
  43. m_path_combo_box->set_model(m_path_history_model);
  44. if (!m_path_history.is_empty())
  45. m_path_combo_box->set_selected_index(0);
  46. m_ok_button = *main_widget->find_descendant_of_type_named<GUI::DialogButton>("ok_button");
  47. m_ok_button->on_click = [this](auto) {
  48. do_run();
  49. };
  50. m_ok_button->set_default(true);
  51. m_cancel_button = *main_widget->find_descendant_of_type_named<GUI::DialogButton>("cancel_button");
  52. m_cancel_button->on_click = [this](auto) {
  53. close();
  54. };
  55. m_browse_button = *find_descendant_of_type_named<GUI::DialogButton>("browse_button");
  56. m_browse_button->on_click = [this](auto) {
  57. Optional<DeprecatedString> path = GUI::FilePicker::get_open_filepath(this, {}, Core::StandardPaths::home_directory(), false, GUI::Dialog::ScreenPosition::Center);
  58. if (path.has_value())
  59. m_path_combo_box->set_text(path.value().view());
  60. };
  61. }
  62. void RunWindow::event(Core::Event& event)
  63. {
  64. if (event.type() == GUI::Event::KeyDown) {
  65. auto& key_event = static_cast<GUI::KeyEvent&>(event);
  66. if (key_event.key() == Key_Escape) {
  67. // Escape key pressed, close dialog
  68. close();
  69. return;
  70. } else if ((key_event.key() == Key_Up || key_event.key() == Key_Down) && m_path_history.is_empty()) {
  71. return;
  72. }
  73. }
  74. Window::event(event);
  75. }
  76. void RunWindow::do_run()
  77. {
  78. auto run_input = m_path_combo_box->text().trim_whitespace();
  79. hide();
  80. if (run_via_launch(run_input) || run_as_command(run_input)) {
  81. // Remove any existing history entry, prepend the successful run string to history and save.
  82. m_path_history.remove_all_matching([&](DeprecatedString v) { return v == run_input; });
  83. m_path_history.prepend(run_input);
  84. // FIXME: Handle failure to save history somehow.
  85. (void)save_history();
  86. close();
  87. return;
  88. }
  89. GUI::MessageBox::show_error(this, "Failed to run. Please check your command, path, or address, and try again."sv);
  90. show();
  91. }
  92. bool RunWindow::run_as_command(DeprecatedString const& run_input)
  93. {
  94. pid_t child_pid;
  95. char const* shell_executable = "/bin/Shell"; // TODO query and use the user's preferred shell.
  96. char const* argv[] = { shell_executable, "-c", run_input.characters(), nullptr };
  97. if ((errno = posix_spawn(&child_pid, shell_executable, nullptr, nullptr, const_cast<char**>(argv), environ))) {
  98. perror("posix_spawn");
  99. return false;
  100. }
  101. // Command spawned in child shell. Hide and wait for exit code.
  102. int status;
  103. if (waitpid(child_pid, &status, 0) < 0)
  104. return false;
  105. int child_error = WEXITSTATUS(status);
  106. dbgln("Child shell exited with code {}", child_error);
  107. // 127 is typically the shell indicating command not found. 126 for all other errors.
  108. if (child_error == 126 || child_error == 127) {
  109. return false;
  110. }
  111. dbgln("Ran via command shell.");
  112. return true;
  113. }
  114. bool RunWindow::run_via_launch(DeprecatedString const& run_input)
  115. {
  116. auto url = URL::create_with_url_or_path(run_input);
  117. if (url.scheme() == "file") {
  118. auto real_path_or_error = FileSystem::real_path(url.path());
  119. if (real_path_or_error.is_error()) {
  120. warnln("Failed to launch '{}': {}", url.path(), real_path_or_error.error());
  121. return false;
  122. }
  123. url = URL::create_with_url_or_path(real_path_or_error.release_value().to_deprecated_string());
  124. }
  125. if (!Desktop::Launcher::open(url)) {
  126. warnln("Failed to launch '{}'", url);
  127. return false;
  128. }
  129. dbgln("Ran via URL launch.");
  130. return true;
  131. }
  132. DeprecatedString RunWindow::history_file_path()
  133. {
  134. return LexicalPath::canonicalized_path(DeprecatedString::formatted("{}/{}", Core::StandardPaths::config_directory(), "RunHistory.txt"));
  135. }
  136. ErrorOr<void> RunWindow::load_history()
  137. {
  138. m_path_history.clear();
  139. auto file = TRY(Core::File::open(history_file_path(), Core::File::OpenMode::Read));
  140. auto buffered_file = TRY(Core::BufferedFile::create(move(file)));
  141. Array<u8, PAGE_SIZE> line_buffer;
  142. while (!buffered_file->is_eof()) {
  143. StringView line = TRY(buffered_file->read_line(line_buffer));
  144. if (!line.is_empty() && !line.is_whitespace())
  145. m_path_history.append(line);
  146. }
  147. return {};
  148. }
  149. ErrorOr<void> RunWindow::save_history()
  150. {
  151. auto file = TRY(Core::File::open(history_file_path(), Core::File::OpenMode::Write));
  152. // Write the first 25 items of history
  153. for (int i = 0; i < min(static_cast<int>(m_path_history.size()), 25); i++)
  154. TRY(file->write_until_depleted(DeprecatedString::formatted("{}\n", m_path_history[i]).bytes()));
  155. return {};
  156. }