RunWindow.cpp 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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 "MainWidget.h"
  9. #include <AK/LexicalPath.h>
  10. #include <LibCore/Process.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 <LibURL/URL.h>
  22. #include <spawn.h>
  23. #include <stdio.h>
  24. #include <string.h>
  25. #include <sys/wait.h>
  26. #include <unistd.h>
  27. namespace Run {
  28. RunWindow::RunWindow()
  29. : m_path_history()
  30. , m_path_history_model(GUI::ItemListModel<ByteString>::create(m_path_history))
  31. {
  32. // FIXME: Handle failure to load history somehow.
  33. (void)load_history();
  34. auto app_icon = GUI::Icon::default_icon("app-run"sv);
  35. set_title("Run");
  36. set_icon(app_icon.bitmap_for_size(16));
  37. resize(345, 100);
  38. set_resizable(false);
  39. set_minimizable(false);
  40. auto main_widget = MUST(Run::MainWidget::try_create());
  41. set_main_widget(main_widget);
  42. m_icon_image_widget = *main_widget->find_descendant_of_type_named<GUI::ImageWidget>("icon");
  43. m_icon_image_widget->set_bitmap(app_icon.bitmap_for_size(32));
  44. m_path_combo_box = *main_widget->find_descendant_of_type_named<GUI::ComboBox>("path");
  45. m_path_combo_box->set_model(m_path_history_model);
  46. if (!m_path_history.is_empty())
  47. m_path_combo_box->set_selected_index(0);
  48. m_ok_button = *main_widget->find_descendant_of_type_named<GUI::DialogButton>("ok_button");
  49. m_ok_button->on_click = [this](auto) {
  50. do_run();
  51. };
  52. m_ok_button->set_default(true);
  53. m_cancel_button = *main_widget->find_descendant_of_type_named<GUI::DialogButton>("cancel_button");
  54. m_cancel_button->on_click = [this](auto) {
  55. close();
  56. };
  57. m_browse_button = *main_widget->find_descendant_of_type_named<GUI::DialogButton>("browse_button");
  58. m_browse_button->on_click = [this](auto) {
  59. Optional<ByteString> path = GUI::FilePicker::get_open_filepath(this, {}, Core::StandardPaths::home_directory(), false, GUI::Dialog::ScreenPosition::Center);
  60. if (path.has_value())
  61. m_path_combo_box->set_text(path.value().view());
  62. };
  63. }
  64. void RunWindow::event(Core::Event& event)
  65. {
  66. if (event.type() == GUI::Event::KeyDown) {
  67. auto& key_event = static_cast<GUI::KeyEvent&>(event);
  68. if (key_event.key() == Key_Escape) {
  69. // Escape key pressed, close dialog
  70. close();
  71. return;
  72. } else if ((key_event.key() == Key_Up || key_event.key() == Key_Down) && m_path_history.is_empty()) {
  73. return;
  74. }
  75. }
  76. Window::event(event);
  77. }
  78. void RunWindow::do_run()
  79. {
  80. auto run_input = m_path_combo_box->text().trim_whitespace();
  81. hide();
  82. if (run_via_launch(run_input) || run_as_command(run_input)) {
  83. // Remove any existing history entry, prepend the successful run string to history and save.
  84. m_path_history.remove_all_matching([&](ByteString v) { return v == run_input; });
  85. m_path_history.prepend(run_input);
  86. // FIXME: Handle failure to save history somehow.
  87. (void)save_history();
  88. close();
  89. return;
  90. }
  91. GUI::MessageBox::show_error(this, "Failed to run. Please check your command, path, or address, and try again."sv);
  92. show();
  93. }
  94. bool RunWindow::run_as_command(ByteString const& run_input)
  95. {
  96. // TODO: Query and use the user's preferred shell.
  97. auto maybe_child_pid = Core::Process::spawn("/bin/Shell"sv, Array { "-c", run_input.characters() }, {}, Core::Process::KeepAsChild::Yes);
  98. if (maybe_child_pid.is_error())
  99. return false;
  100. pid_t child_pid = maybe_child_pid.release_value();
  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(ByteString const& run_input)
  115. {
  116. auto url = URL::create_with_url_or_path(run_input);
  117. if (url.scheme() == "file") {
  118. auto file_path = url.serialize_path();
  119. auto real_path_or_error = FileSystem::real_path(file_path);
  120. if (real_path_or_error.is_error()) {
  121. warnln("Failed to launch '{}': {}", file_path, real_path_or_error.error());
  122. return false;
  123. }
  124. url = URL::create_with_url_or_path(real_path_or_error.release_value());
  125. }
  126. if (!Desktop::Launcher::open(url)) {
  127. warnln("Failed to launch '{}'", url);
  128. return false;
  129. }
  130. dbgln("Ran via URL launch.");
  131. return true;
  132. }
  133. ByteString RunWindow::history_file_path()
  134. {
  135. return LexicalPath::canonicalized_path(ByteString::formatted("{}/{}", Core::StandardPaths::config_directory(), "RunHistory.txt"));
  136. }
  137. ErrorOr<void> RunWindow::load_history()
  138. {
  139. m_path_history.clear();
  140. auto file = TRY(Core::File::open(history_file_path(), Core::File::OpenMode::Read));
  141. auto buffered_file = TRY(Core::InputBufferedFile::create(move(file)));
  142. Array<u8, PAGE_SIZE> line_buffer;
  143. while (!buffered_file->is_eof()) {
  144. StringView line = TRY(buffered_file->read_line(line_buffer));
  145. if (!line.is_empty() && !line.is_whitespace())
  146. m_path_history.append(line);
  147. }
  148. return {};
  149. }
  150. ErrorOr<void> RunWindow::save_history()
  151. {
  152. auto file = TRY(Core::File::open(history_file_path(), Core::File::OpenMode::Write));
  153. // Write the first 25 items of history
  154. for (int i = 0; i < min(static_cast<int>(m_path_history.size()), 25); i++)
  155. TRY(file->write_until_depleted(ByteString::formatted("{}\n", m_path_history[i]).bytes()));
  156. return {};
  157. }
  158. }